diff --git a/Makefile b/Makefile
index 3cc423350..d4457e08f 100644
--- a/Makefile
+++ b/Makefile
@@ -20,6 +20,7 @@ all: bundle
PDF_JS_FILES = \
core.js \
util.js \
+ api.js \
canvas.js \
obj.js \
function.js \
diff --git a/examples/acroforms/forms.js b/examples/acroforms/forms.js
index 6ec92766d..868825fc7 100644
--- a/examples/acroforms/forms.js
+++ b/examples/acroforms/forms.js
@@ -9,7 +9,7 @@
var formFields = {};
-function setupForm(div, content, scale) {
+function setupForm(div, content, viewport) {
function bindInputItem(input, item) {
if (input.name in formFields) {
var value = formFields[input.name];
@@ -27,16 +27,20 @@ function setupForm(div, content, scale) {
}
function createElementWithStyle(tagName, item) {
var element = document.createElement(tagName);
- element.style.left = (item.x * scale) + 'px';
- element.style.top = (item.y * scale) + 'px';
- element.style.width = Math.ceil(item.width * scale) + 'px';
- element.style.height = Math.ceil(item.height * scale) + 'px';
+ var rect = Util.normalizeRect(
+ viewport.convertToViewportRectangle(item.rect));
+ element.style.left = Math.floor(rect[0]) + 'px';
+ element.style.top = Math.floor(rect[1]) + 'px';
+ element.style.width = Math.ceil(rect[2] - rect[0]) + 'px';
+ element.style.height = Math.ceil(rect[3] - rect[1]) + 'px';
return element;
}
function assignFontStyle(element, item) {
var fontStyles = '';
- if ('fontSize' in item)
- fontStyles += 'font-size: ' + Math.round(item.fontSize * scale) + 'px;';
+ if ('fontSize' in item) {
+ fontStyles += 'font-size: ' + Math.round(item.fontSize *
+ viewport.fontScale) + 'px;';
+ }
switch (item.textAlignment) {
case 0:
fontStyles += 'text-align: left;';
@@ -51,83 +55,88 @@ function setupForm(div, content, scale) {
element.setAttribute('style', element.getAttribute('style') + fontStyles);
}
- var items = content.getAnnotations();
- for (var i = 0; i < items.length; i++) {
- var item = items[i];
- switch (item.type) {
- case 'Widget':
- if (item.fieldType != 'Tx' && item.fieldType != 'Btn' &&
- item.fieldType != 'Ch')
- break;
- var inputDiv = createElementWithStyle('div', item);
- inputDiv.className = 'inputHint';
- div.appendChild(inputDiv);
- var input;
- if (item.fieldType == 'Tx') {
- input = createElementWithStyle('input', item);
- }
- if (item.fieldType == 'Btn') {
- input = createElementWithStyle('input', item);
- if (item.flags & 32768) {
- input.type = 'radio';
- // radio button is not supported
- } else if (item.flags & 65536) {
- input.type = 'button';
- // pushbutton is not supported
- } else {
- input.type = 'checkbox';
+ content.getAnnotations().then(function(items) {
+ for (var i = 0; i < items.length; i++) {
+ var item = items[i];
+ switch (item.type) {
+ case 'Widget':
+ if (item.fieldType != 'Tx' && item.fieldType != 'Btn' &&
+ item.fieldType != 'Ch')
+ break;
+ var inputDiv = createElementWithStyle('div', item);
+ inputDiv.className = 'inputHint';
+ div.appendChild(inputDiv);
+ var input;
+ if (item.fieldType == 'Tx') {
+ input = createElementWithStyle('input', item);
}
- }
- if (item.fieldType == 'Ch') {
- input = createElementWithStyle('select', item);
- // select box is not supported
- }
- input.className = 'inputControl';
- input.name = item.fullName;
- input.title = item.alternativeText;
- assignFontStyle(input, item);
- bindInputItem(input, item);
- div.appendChild(input);
- break;
+ if (item.fieldType == 'Btn') {
+ input = createElementWithStyle('input', item);
+ if (item.flags & 32768) {
+ input.type = 'radio';
+ // radio button is not supported
+ } else if (item.flags & 65536) {
+ input.type = 'button';
+ // pushbutton is not supported
+ } else {
+ input.type = 'checkbox';
+ }
+ }
+ if (item.fieldType == 'Ch') {
+ input = createElementWithStyle('select', item);
+ // select box is not supported
+ }
+ input.className = 'inputControl';
+ input.name = item.fullName;
+ input.title = item.alternativeText;
+ assignFontStyle(input, item);
+ bindInputItem(input, item);
+ div.appendChild(input);
+ break;
+ }
}
- }
+ });
}
function renderPage(div, pdf, pageNumber, callback) {
- var page = pdf.getPage(pageNumber);
- var scale = 1.5;
+ pdf.getPage(pageNumber).then(function(page) {
+ var scale = 1.5;
+ var viewport = page.getViewport(scale);
- var pageDisplayWidth = page.width * scale;
- var pageDisplayHeight = page.height * scale;
+ var pageDisplayWidth = viewport.width;
+ var pageDisplayHeight = viewport.height;
- var pageDivHolder = document.createElement('div');
- pageDivHolder.className = 'pdfpage';
- pageDivHolder.style.width = pageDisplayWidth + 'px';
- pageDivHolder.style.height = pageDisplayHeight + 'px';
- div.appendChild(pageDivHolder);
+ var pageDivHolder = document.createElement('div');
+ pageDivHolder.className = 'pdfpage';
+ pageDivHolder.style.width = pageDisplayWidth + 'px';
+ pageDivHolder.style.height = pageDisplayHeight + 'px';
+ div.appendChild(pageDivHolder);
- // Prepare canvas using PDF page dimensions
- var canvas = document.createElement('canvas');
- var context = canvas.getContext('2d');
- canvas.width = pageDisplayWidth;
- canvas.height = pageDisplayHeight;
- pageDivHolder.appendChild(canvas);
+ // Prepare canvas using PDF page dimensions
+ var canvas = document.createElement('canvas');
+ var context = canvas.getContext('2d');
+ canvas.width = pageDisplayWidth;
+ canvas.height = pageDisplayHeight;
+ pageDivHolder.appendChild(canvas);
- // Render PDF page into canvas context
- page.startRendering(context, callback);
+ // Render PDF page into canvas context
+ var renderContext = {
+ canvasContext: context,
+ viewport: viewport
+ };
+ page.render(renderContext).then(callback);
- // Prepare and populate form elements layer
- var formDiv = document.createElement('div');
- pageDivHolder.appendChild(formDiv);
+ // Prepare and populate form elements layer
+ var formDiv = document.createElement('div');
+ pageDivHolder.appendChild(formDiv);
- setupForm(formDiv, page, scale);
+ setupForm(formDiv, page, viewport);
+ });
}
-PDFJS.getPdf(pdfWithFormsPath, function getPdfForm(data) {
- // Instantiate PDFDoc with PDF data
- var pdf = new PDFJS.PDFDoc(data);
-
+// Fetch the PDF document from the URL using promices
+PDFJS.getDocument(pdfWithFormsPath).then(function getPdfForm(pdf) {
// Rendering all pages starting from first
var viewer = document.getElementById('viewer');
var pageNumber = 1;
diff --git a/examples/acroforms/index.html b/examples/acroforms/index.html
index 8a9053f78..858ad649f 100644
--- a/examples/acroforms/index.html
+++ b/examples/acroforms/index.html
@@ -6,6 +6,7 @@
+
diff --git a/examples/helloworld/hello.js b/examples/helloworld/hello.js
index 45e61eb6f..7bf18d65b 100644
--- a/examples/helloworld/hello.js
+++ b/examples/helloworld/hello.js
@@ -7,25 +7,31 @@
'use strict';
-PDFJS.getPdf('helloworld.pdf', function getPdfHelloWorld(data) {
- //
- // Instantiate PDFDoc with PDF data
- //
- var pdf = new PDFJS.PDFDoc(data);
- var page = pdf.getPage(1);
- var scale = 1.5;
+//
+// Fetch the PDF document from the URL using promices
+//
+PDFJS.getDocument('helloworld.pdf').then(function(pdf) {
+ // Using promise to fetch the page
+ pdf.getPage(1).then(function(page) {
+ var scale = 1.5;
+ var viewport = page.getViewport(scale);
- //
- // Prepare canvas using PDF page dimensions
- //
- var canvas = document.getElementById('the-canvas');
- var context = canvas.getContext('2d');
- canvas.height = page.height * scale;
- canvas.width = page.width * scale;
+ //
+ // Prepare canvas using PDF page dimensions
+ //
+ var canvas = document.getElementById('the-canvas');
+ var context = canvas.getContext('2d');
+ canvas.height = viewport.height;
+ canvas.width = viewport.width;
- //
- // Render PDF page into canvas context
- //
- page.startRendering(context);
+ //
+ // Render PDF page into canvas context
+ //
+ var renderContext = {
+ canvasContext: context,
+ viewport: viewport
+ };
+ page.render(renderContext);
+ });
});
diff --git a/examples/helloworld/index.html b/examples/helloworld/index.html
index c6af616e6..c9df98809 100644
--- a/examples/helloworld/index.html
+++ b/examples/helloworld/index.html
@@ -6,6 +6,7 @@
+
diff --git a/make.js b/make.js
index e0975fec8..2ee0d4cbd 100755
--- a/make.js
+++ b/make.js
@@ -79,6 +79,7 @@ target.bundle = function() {
var SRC_FILES =
['core.js',
'util.js',
+ 'api.js',
'canvas.js',
'obj.js',
'function.js',
diff --git a/src/api.js b/src/api.js
new file mode 100644
index 000000000..18644ebe6
--- /dev/null
+++ b/src/api.js
@@ -0,0 +1,562 @@
+/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
+
+/**
+ * This is the main entry point for loading a PDF and interacting with it.
+ * NOTE: If a URL is used to fetch the PDF data a standard XMLHttpRequest(XHR)
+ * is used, which means it must follow the same origin rules that any XHR does
+ * e.g. No cross domain requests without CORS.
+ *
+ * @param {string|TypedAray} source Either a url to a PDF is located or a
+ * typed array already populated with data.
+ * @return {Promise} A promise that is resolved with {PDFDocumentProxy} object.
+ */
+PDFJS.getDocument = function getDocument(source) {
+ var promise = new PDFJS.Promise();
+ var transport = new WorkerTransport(promise);
+ if (typeof source === 'string') {
+ // fetch url
+ PDFJS.getPdf(
+ {
+ url: source,
+ progress: function getPDFProgress(evt) {
+ if (evt.lengthComputable)
+ promise.progress({
+ loaded: evt.loaded,
+ total: evt.total
+ });
+ },
+ error: function getPDFError(e) {
+ promise.reject('Unexpected server response of ' +
+ e.target.status + '.');
+ }
+ },
+ function getPDFLoad(data) {
+ transport.sendData(data);
+ });
+ } else {
+ // assuming the source is array, instantiating directly from it
+ transport.sendData(source);
+ }
+ return promise;
+};
+
+/**
+ * Proxy to a PDFDocument in the worker thread. Also, contains commonly used
+ * properties that can be read synchronously.
+ */
+var PDFDocumentProxy = (function() {
+ function PDFDocumentProxy(pdfInfo, transport) {
+ this.pdfInfo = pdfInfo;
+ this.transport = transport;
+ }
+ PDFDocumentProxy.prototype = {
+ /**
+ * @return {number} Total number of pages the PDF contains.
+ */
+ get numPages() {
+ return this.pdfInfo.numPages;
+ },
+ /**
+ * @return {string} A unique ID to identify a PDF. Not guaranteed to be
+ * unique.
+ */
+ get fingerprint() {
+ return this.pdfInfo.fingerprint;
+ },
+ /**
+ * @param {number} The page number to get. The first page is 1.
+ * @return {Promise} A promise that is resolved with a {PDFPageProxy}
+ * object.
+ */
+ getPage: function(number) {
+ return this.transport.getPage(number);
+ },
+ /**
+ * @return {Promise} A promise that is resolved with a lookup table for
+ * mapping named destinations to reference numbers.
+ */
+ getDestinations: function() {
+ var promise = new PDFJS.Promise();
+ var destinations = this.pdfInfo.destinations;
+ promise.resolve(destinations);
+ return promise;
+ },
+ /**
+ * @return {Promise} A promise that is resolved with an {array} that is a
+ * tree outline (if it has one) of the PDF. The tree is in the format of:
+ * [
+ * {
+ * title: string,
+ * bold: boolean,
+ * italic: boolean,
+ * color: rgb array,
+ * dest: dest obj,
+ * items: array of more items like this
+ * },
+ * ...
+ * ].
+ */
+ getOutline: function() {
+ var promise = new PDFJS.Promise();
+ var outline = this.pdfInfo.outline;
+ promise.resolve(outline);
+ return promise;
+ },
+ /**
+ * @return {Promise} A promise that is resolved with an {object} that has
+ * info and metadata properties. Info is an {object} filled with anything
+ * available in the information dictionary and similarly metadata is a
+ * {Metadata} object with information from the metadata section of the PDF.
+ */
+ getMetadata: function() {
+ var promise = new PDFJS.Promise();
+ var info = this.pdfInfo.info;
+ var metadata = this.pdfInfo.metadata;
+ promise.resolve({
+ info: info,
+ metadata: metadata ? new PDFJS.Metadata(metadata) : null
+ });
+ return promise;
+ },
+ destroy: function() {
+ this.transport.destroy();
+ }
+ };
+ return PDFDocumentProxy;
+})();
+
+var PDFPageProxy = (function PDFPageProxyClosure() {
+ function PDFPageProxy(pageInfo, transport) {
+ this.pageInfo = pageInfo;
+ this.transport = transport;
+ this.stats = new StatTimer();
+ this.stats.enabled = !!globalScope.PDFJS.enableStats;
+ this.objs = transport.objs;
+ }
+ PDFPageProxy.prototype = {
+ /**
+ * @return {number} Page number of the page. First page is 1.
+ */
+ get pageNumber() {
+ return this.pageInfo.pageIndex + 1;
+ },
+ /**
+ * @return {number} The number of degrees the page is rotated clockwise.
+ */
+ get rotate() {
+ return this.pageInfo.rotate;
+ },
+ /**
+ * @return {object} The reference that points to this page. It has 'num' and
+ * 'gen' properties.
+ */
+ get ref() {
+ return this.pageInfo.ref;
+ },
+ /**
+ * @return {array} An array of the visible portion of the PDF page in the
+ * user space units - [x1, y1, x2, y2].
+ */
+ get view() {
+ return this.pageInfo.view;
+ },
+ /**
+ * @param {number} scale The desired scale of the viewport.
+ * @param {number} rotate Degrees to rotate the viewport. If omitted this
+ * defaults to the page rotation.
+ * @return {PageViewport} Contains 'width' and 'height' properties along
+ * with transforms required for rendering.
+ */
+ getViewport: function(scale, rotate) {
+ if (arguments.length < 2)
+ rotate = this.rotate;
+ return new PDFJS.PageViewport(this.view, scale, rotate, 0, 0);
+ },
+ /**
+ * @return {Promise} A promise that is resolved with an {array} of the
+ * annotation objects.
+ */
+ getAnnotations: function() {
+ if (this.annotationsPromise)
+ return this.annotationsPromise;
+
+ var promise = new PDFJS.Promise();
+ this.annotationsPromise = promise;
+ this.transport.getAnnotations(this.pageInfo.pageIndex);
+ return promise;
+ },
+ /**
+ * Begins the process of rendering a page to the desired context.
+ * @param {object} params A parameter object that supports:
+ * {
+ * canvasContext(required): A 2D context of a DOM Canvas object.,
+ * textLayer(optional): An object that has beginLayout, endLayout, and
+ * appendText functions.
+ * }.
+ * @return {Promise} A promise that is resolved when the page finishes
+ * rendering.
+ */
+ render: function(params) {
+ var promise = new Promise();
+ var stats = this.stats;
+ stats.time('Overall');
+ // If there is no displayReadyPromise yet, then the operatorList was never
+ // requested before. Make the request and create the promise.
+ if (!this.displayReadyPromise) {
+ this.displayReadyPromise = new Promise();
+
+ this.stats.time('Page Request');
+ this.transport.messageHandler.send('RenderPageRequest', {
+ pageIndex: this.pageNumber - 1
+ });
+ }
+
+ function complete(error) {
+ if (error)
+ promise.reject(error);
+ else
+ promise.resolve();
+ };
+
+ // Once the operatorList and fonts are loaded, do the actual rendering.
+ this.displayReadyPromise.then(
+ function pageDisplayReadyPromise() {
+ var gfx = new CanvasGraphics(params.canvasContext,
+ this.objs, params.textLayer);
+ try {
+ this.display(gfx, params.viewport, complete);
+ } catch (e) {
+ complete(e);
+ }
+ }.bind(this),
+ function pageDisplayReadPromiseError(reason) {
+ complete(reason);
+ }
+ );
+
+ return promise;
+ },
+ /**
+ * For internal use only.
+ */
+ startRenderingFromOperatorList:
+ function PDFPageWrapper_startRenderingFromOperatorList(operatorList,
+ fonts) {
+ var self = this;
+ this.operatorList = operatorList;
+
+ var displayContinuation = function pageDisplayContinuation() {
+ // Always defer call to display() to work around bug in
+ // Firefox error reporting from XHR callbacks.
+ setTimeout(function pageSetTimeout() {
+ self.displayReadyPromise.resolve();
+ });
+ };
+
+ this.ensureFonts(fonts,
+ function pageStartRenderingFromOperatorListEnsureFonts() {
+ displayContinuation();
+ }
+ );
+ },
+ /**
+ * For internal use only.
+ */
+ ensureFonts: function PDFPageWrapper_ensureFonts(fonts, callback) {
+ this.stats.time('Font Loading');
+ // Convert the font names to the corresponding font obj.
+ for (var i = 0, ii = fonts.length; i < ii; i++) {
+ fonts[i] = this.objs.objs[fonts[i]].data;
+ }
+
+ // Load all the fonts
+ FontLoader.bind(
+ fonts,
+ function pageEnsureFontsFontObjs(fontObjs) {
+ this.stats.timeEnd('Font Loading');
+
+ callback.call(this);
+ }.bind(this)
+ );
+ },
+ /**
+ * For internal use only.
+ */
+ display: function PDFPageWrapper_display(gfx, viewport, callback) {
+ var stats = this.stats;
+ stats.time('Rendering');
+
+ gfx.beginDrawing(viewport);
+
+ var startIdx = 0;
+ var length = this.operatorList.fnArray.length;
+ var operatorList = this.operatorList;
+ var stepper = null;
+ if (PDFJS.pdfBug && StepperManager.enabled) {
+ stepper = StepperManager.create(this.pageNumber - 1);
+ stepper.init(operatorList);
+ stepper.nextBreakPoint = stepper.getNextBreakPoint();
+ }
+
+ var self = this;
+ function next() {
+ startIdx =
+ gfx.executeOperatorList(operatorList, startIdx, next, stepper);
+ if (startIdx == length) {
+ gfx.endDrawing();
+ delete this.operatorList;
+ stats.timeEnd('Rendering');
+ stats.timeEnd('Overall');
+ if (callback) callback();
+ }
+ }
+ next();
+ },
+ /**
+ * Stub for future feature.
+ */
+ getTextContent: function() {
+ var promise = new PDFJS.Promise();
+ var textContent = 'page text'; // not implemented
+ promise.resolve(textContent);
+ return promise;
+ },
+ /**
+ * Stub for future feature.
+ */
+ getOperationList: function() {
+ var promise = new PDFJS.Promise();
+ var operationList = { // not implemented
+ dependencyFontsID: null,
+ operatorList: null
+ };
+ promise.resolve(operationList);
+ return promise;
+ }
+ };
+ return PDFPageProxy;
+})();
+/**
+ * For internal use only.
+ */
+var WorkerTransport = (function WorkerTransportClosure() {
+ function WorkerTransport(promise) {
+ this.workerReadyPromise = promise;
+ this.objs = new PDFObjects();
+
+ this.pageCache = [];
+ this.pagePromises = [];
+ this.fontsLoading = {};
+
+ // If worker support isn't disabled explicit and the browser has worker
+ // support, create a new web worker and test if it/the browser fullfills
+ // all requirements to run parts of pdf.js in a web worker.
+ // Right now, the requirement is, that an Uint8Array is still an Uint8Array
+ // as it arrives on the worker. Chrome added this with version 15.
+ if (!globalScope.PDFJS.disableWorker && typeof Worker !== 'undefined') {
+ var workerSrc = PDFJS.workerSrc;
+ if (typeof workerSrc === 'undefined') {
+ error('No PDFJS.workerSrc specified');
+ }
+
+ try {
+ var worker;
+ if (PDFJS.isFirefoxExtension) {
+ // The firefox extension can't load the worker from the resource://
+ // url so we have to inline the script and then use the blob loader.
+ var bb = new MozBlobBuilder();
+ bb.append(document.querySelector('#PDFJS_SCRIPT_TAG').textContent);
+ var blobUrl = window.URL.createObjectURL(bb.getBlob());
+ worker = new Worker(blobUrl);
+ } else {
+ // Some versions of FF can't create a worker on localhost, see:
+ // https://bugzilla.mozilla.org/show_bug.cgi?id=683280
+ worker = new Worker(workerSrc);
+ }
+
+ var messageHandler = new MessageHandler('main', worker);
+ this.messageHandler = messageHandler;
+
+ messageHandler.on('test', function transportTest(supportTypedArray) {
+ if (supportTypedArray) {
+ this.worker = worker;
+ this.setupMessageHandler(messageHandler);
+ } else {
+ globalScope.PDFJS.disableWorker = true;
+ this.setupFakeWorker();
+ }
+ }.bind(this));
+
+ var testObj = new Uint8Array(1);
+ // Some versions of Opera throw a DATA_CLONE_ERR on
+ // serializing the typed array.
+ messageHandler.send('test', testObj);
+ return;
+ } catch (e) {
+ warn('The worker has been disabled.');
+ }
+ }
+ // Either workers are disabled, not supported or have thrown an exception.
+ // Thus, we fallback to a faked worker.
+ globalScope.PDFJS.disableWorker = true;
+ this.setupFakeWorker();
+ }
+ WorkerTransport.prototype = {
+ destroy: function WorkerTransport_destroy() {
+ if (this.worker)
+ this.worker.terminate();
+
+ this.pageCache = [];
+ this.pagePromises = [];
+ },
+ setupFakeWorker: function WorkerTransport_setupFakeWorker() {
+ // If we don't use a worker, just post/sendMessage to the main thread.
+ var fakeWorker = {
+ postMessage: function WorkerTransport_postMessage(obj) {
+ fakeWorker.onmessage({data: obj});
+ },
+ terminate: function WorkerTransport_terminate() {}
+ };
+
+ var messageHandler = new MessageHandler('main', fakeWorker);
+ this.setupMessageHandler(messageHandler);
+
+ // If the main thread is our worker, setup the handling for the messages
+ // the main thread sends to it self.
+ WorkerMessageHandler.setup(messageHandler);
+ },
+
+ setupMessageHandler:
+ function WorkerTransport_setupMessageHandler(messageHandler) {
+ this.messageHandler = messageHandler;
+
+ messageHandler.on('GetDoc', function transportDoc(data) {
+ var pdfInfo = data.pdfInfo;
+ var pdfDocument = new PDFDocumentProxy(pdfInfo, this);
+ this.pdfDocument = pdfDocument;
+ this.workerReadyPromise.resolve(pdfDocument);
+ }, this);
+
+ messageHandler.on('GetPage', function transportPage(data) {
+ var pageInfo = data.pageInfo;
+ var page = new PDFPageProxy(pageInfo, this);
+ this.pageCache[pageInfo.pageIndex] = page;
+ var promise = this.pagePromises[pageInfo.pageIndex];
+ promise.resolve(page);
+ }, this);
+
+ messageHandler.on('GetAnnotations', function transportAnnotations(data) {
+ var annotations = data.annotations;
+ var promise = this.pageCache[data.pageIndex].annotationsPromise;
+ promise.resolve(annotations);
+ }, this);
+
+ messageHandler.on('RenderPage', function transportRender(data) {
+ var page = this.pageCache[data.pageIndex];
+ var depFonts = data.depFonts;
+
+ page.stats.timeEnd('Page Request');
+ page.startRenderingFromOperatorList(data.operatorList, depFonts);
+ }, this);
+
+ messageHandler.on('obj', function transportObj(data) {
+ var id = data[0];
+ var type = data[1];
+
+ switch (type) {
+ case 'JpegStream':
+ var imageData = data[2];
+ loadJpegStream(id, imageData, this.objs);
+ break;
+ case 'Image':
+ var imageData = data[2];
+ this.objs.resolve(id, imageData);
+ break;
+ case 'Font':
+ var name = data[2];
+ var file = data[3];
+ var properties = data[4];
+
+ if (file) {
+ // Rewrap the ArrayBuffer in a stream.
+ var fontFileDict = new Dict();
+ file = new Stream(file, 0, file.length, fontFileDict);
+ }
+
+ // At this point, only the font object is created but the font is
+ // not yet attached to the DOM. This is done in `FontLoader.bind`.
+ var font = new Font(name, file, properties);
+ this.objs.resolve(id, font);
+ break;
+ default:
+ error('Got unkown object type ' + type);
+ }
+ }, this);
+
+ messageHandler.on('PageError', function transportError(data) {
+ var page = this.pageCache[data.pageNum - 1];
+ if (page.displayReadyPromise)
+ page.displayReadyPromise.reject(data.error);
+ else
+ error(data.error);
+ }, this);
+
+ messageHandler.on('JpegDecode', function(data, promise) {
+ var imageData = data[0];
+ var components = data[1];
+ if (components != 3 && components != 1)
+ error('Only 3 component or 1 component can be returned');
+
+ var img = new Image();
+ img.onload = (function messageHandler_onloadClosure() {
+ var width = img.width;
+ var height = img.height;
+ var size = width * height;
+ var rgbaLength = size * 4;
+ var buf = new Uint8Array(size * components);
+ var tmpCanvas = createScratchCanvas(width, height);
+ var tmpCtx = tmpCanvas.getContext('2d');
+ tmpCtx.drawImage(img, 0, 0);
+ var data = tmpCtx.getImageData(0, 0, width, height).data;
+
+ if (components == 3) {
+ for (var i = 0, j = 0; i < rgbaLength; i += 4, j += 3) {
+ buf[j] = data[i];
+ buf[j + 1] = data[i + 1];
+ buf[j + 2] = data[i + 2];
+ }
+ } else if (components == 1) {
+ for (var i = 0, j = 0; i < rgbaLength; i += 4, j++) {
+ buf[j] = data[i];
+ }
+ }
+ promise.resolve({ data: buf, width: width, height: height});
+ }).bind(this);
+ var src = 'data:image/jpeg;base64,' + window.btoa(imageData);
+ img.src = src;
+ });
+ },
+
+ sendData: function WorkerTransport_sendData(data) {
+ this.messageHandler.send('GetDocRequest', data);
+ },
+
+ getPage: function WorkerTransport_getPage(pageNumber, promise) {
+ var pageIndex = pageNumber - 1;
+ if (pageIndex in this.pagePromises)
+ return this.pagePromises[pageIndex];
+ var promise = new PDFJS.Promise('Page ' + pageNumber);
+ this.pagePromises[pageIndex] = promise;
+ this.messageHandler.send('GetPageRequest', { pageIndex: pageIndex });
+ return promise;
+ },
+
+ getAnnotations: function WorkerTransport_getAnnotations(pageIndex) {
+ this.messageHandler.send('GetAnnotationsRequest',
+ { pageIndex: pageIndex });
+ }
+ };
+ return WorkerTransport;
+
+})();
diff --git a/src/canvas.js b/src/canvas.js
index 8f29051fd..9d470fbec 100644
--- a/src/canvas.js
+++ b/src/canvas.js
@@ -241,27 +241,10 @@ var CanvasGraphics = (function CanvasGraphicsClosure() {
'shadingFill': true
},
- beginDrawing: function CanvasGraphics_beginDrawing(mediaBox) {
- var cw = this.ctx.canvas.width, ch = this.ctx.canvas.height;
+ beginDrawing: function CanvasGraphics_beginDrawing(viewport) {
+ var transform = viewport.transform;
this.ctx.save();
- switch (mediaBox.rotate) {
- case 0:
- this.ctx.transform(1, 0, 0, -1, 0, ch);
- break;
- case 90:
- this.ctx.transform(0, 1, 1, 0, 0, 0);
- break;
- case 180:
- this.ctx.transform(-1, 0, 0, 1, cw, 0);
- break;
- case 270:
- this.ctx.transform(0, -1, -1, 0, cw, ch);
- break;
- }
- // Scale so that canvas units are the same as PDF user space units
- this.ctx.scale(cw / mediaBox.width, ch / mediaBox.height);
- // Move the media left-top corner to the (0,0) canvas position
- this.ctx.translate(-mediaBox.x, -mediaBox.y);
+ this.ctx.transform.apply(this.ctx, transform);
if (this.textLayer)
this.textLayer.beginLayout();
diff --git a/src/core.js b/src/core.js
index 15cd147e2..41f9a9c61 100644
--- a/src/core.js
+++ b/src/core.js
@@ -63,8 +63,6 @@ var Page = (function PageClosure() {
function Page(xref, pageNumber, pageDict, ref) {
this.pageNumber = pageNumber;
this.pageDict = pageDict;
- this.stats = new StatTimer();
- this.stats.enabled = !!globalScope.PDFJS.enableStats;
this.xref = xref;
this.ref = ref;
@@ -100,18 +98,10 @@ var Page = (function PageClosure() {
return shadow(this, 'mediaBox', obj);
},
get view() {
- var cropBox = this.inheritPageProp('CropBox');
- var view = {
- x: 0,
- y: 0,
- width: this.width,
- height: this.height
- };
- if (!isArray(cropBox) || cropBox.length !== 4)
- return shadow(this, 'view', view);
-
var mediaBox = this.mediaBox;
- var offsetX = mediaBox[0], offsetY = mediaBox[1];
+ var cropBox = this.inheritPageProp('CropBox');
+ if (!isArray(cropBox) || cropBox.length !== 4)
+ return shadow(this, 'view', mediaBox);
// From the spec, 6th ed., p.963:
// "The crop, bleed, trim, and art boxes should not ordinarily
@@ -119,42 +109,13 @@ var Page = (function PageClosure() {
// effectively reduced to their intersection with the media box."
cropBox = Util.intersect(cropBox, mediaBox);
if (!cropBox)
- return shadow(this, 'view', view);
+ return shadow(this, 'view', mediaBox);
- var tl = this.rotatePoint(cropBox[0] - offsetX, cropBox[1] - offsetY);
- var br = this.rotatePoint(cropBox[2] - offsetX, cropBox[3] - offsetY);
- view.x = Math.min(tl.x, br.x);
- view.y = Math.min(tl.y, br.y);
- view.width = Math.abs(tl.x - br.x);
- view.height = Math.abs(tl.y - br.y);
-
- return shadow(this, 'view', view);
+ return shadow(this, 'view', cropBox);
},
get annotations() {
return shadow(this, 'annotations', this.inheritPageProp('Annots'));
},
- get width() {
- var mediaBox = this.mediaBox;
- var rotate = this.rotate;
- var width;
- if (rotate == 0 || rotate == 180) {
- width = (mediaBox[2] - mediaBox[0]);
- } else {
- width = (mediaBox[3] - mediaBox[1]);
- }
- return shadow(this, 'width', width);
- },
- get height() {
- var mediaBox = this.mediaBox;
- var rotate = this.rotate;
- var height;
- if (rotate == 0 || rotate == 180) {
- height = (mediaBox[3] - mediaBox[1]);
- } else {
- height = (mediaBox[2] - mediaBox[0]);
- }
- return shadow(this, 'height', height);
- },
get rotate() {
var rotate = this.inheritPageProp('Rotate') || 0;
// Normalize rotation so it's a multiple of 90 and between 0 and 270
@@ -170,34 +131,12 @@ var Page = (function PageClosure() {
return shadow(this, 'rotate', rotate);
},
- startRenderingFromOperatorList:
- function Page_startRenderingFromOperatorList(operatorList, fonts) {
- var self = this;
- this.operatorList = operatorList;
-
- var displayContinuation = function pageDisplayContinuation() {
- // Always defer call to display() to work around bug in
- // Firefox error reporting from XHR callbacks.
- setTimeout(function pageSetTimeout() {
- self.displayReadyPromise.resolve();
- });
- };
-
- this.ensureFonts(fonts,
- function pageStartRenderingFromOperatorListEnsureFonts() {
- displayContinuation();
- }
- );
- },
-
getOperatorList: function Page_getOperatorList(handler, dependency) {
if (this.operatorList) {
// content was compiled
return this.operatorList;
}
- this.stats.time('Build IR Queue');
-
var xref = this.xref;
var content = this.content;
var resources = this.resources;
@@ -216,81 +155,9 @@ var Page = (function PageClosure() {
xref, handler, 'p' + this.pageNumber + '_');
this.operatorList = pe.getOperatorList(content, resources, dependency);
- this.stats.timeEnd('Build IR Queue');
return this.operatorList;
},
- ensureFonts: function Page_ensureFonts(fonts, callback) {
- this.stats.time('Font Loading');
- // Convert the font names to the corresponding font obj.
- for (var i = 0, ii = fonts.length; i < ii; i++) {
- fonts[i] = this.objs.objs[fonts[i]].data;
- }
-
- // Load all the fonts
- FontLoader.bind(
- fonts,
- function pageEnsureFontsFontObjs(fontObjs) {
- this.stats.timeEnd('Font Loading');
-
- callback.call(this);
- }.bind(this)
- );
- },
-
- display: function Page_display(gfx, callback) {
- var stats = this.stats;
- stats.time('Rendering');
- var xref = this.xref;
- var resources = this.resources;
- var mediaBox = this.mediaBox;
- assertWellFormed(isDict(resources), 'invalid page resources');
-
- gfx.xref = xref;
- gfx.res = resources;
- gfx.beginDrawing({ x: mediaBox[0], y: mediaBox[1],
- width: this.width,
- height: this.height,
- rotate: this.rotate });
-
- var startIdx = 0;
- var length = this.operatorList.fnArray.length;
- var operatorList = this.operatorList;
- var stepper = null;
- if (PDFJS.pdfBug && StepperManager.enabled) {
- stepper = StepperManager.create(this.pageNumber);
- stepper.init(operatorList);
- stepper.nextBreakPoint = stepper.getNextBreakPoint();
- }
-
- var self = this;
- function next() {
- startIdx =
- gfx.executeOperatorList(operatorList, startIdx, next, stepper);
- if (startIdx == length) {
- gfx.endDrawing();
- stats.timeEnd('Rendering');
- stats.timeEnd('Overall');
- if (callback) callback();
- }
- }
- next();
- },
- rotatePoint: function Page_rotatePoint(x, y, reverse) {
- var rotate = reverse ? (360 - this.rotate) : this.rotate;
- switch (rotate) {
- case 180:
- return {x: this.width - x, y: y};
- case 90:
- return {x: this.width - y, y: this.height - x};
- case 270:
- return {x: y, y: x};
- case 360:
- case 0:
- default:
- return {x: x, y: this.height - y};
- }
- },
getLinks: function Page_getLinks() {
var links = [];
var annotations = pageGetAnnotations();
@@ -342,15 +209,10 @@ var Page = (function PageClosure() {
if (!isName(subtype))
continue;
var rect = annotation.get('Rect');
- var topLeftCorner = this.rotatePoint(rect[0], rect[1]);
- var bottomRightCorner = this.rotatePoint(rect[2], rect[3]);
var item = {};
item.type = subtype.name;
- item.x = Math.min(topLeftCorner.x, bottomRightCorner.x);
- item.y = Math.min(topLeftCorner.y, bottomRightCorner.y);
- item.width = Math.abs(topLeftCorner.x - bottomRightCorner.x);
- item.height = Math.abs(topLeftCorner.y - bottomRightCorner.y);
+ item.rect = rect;
switch (subtype.name) {
case 'Link':
var a = annotation.get('A');
@@ -433,37 +295,6 @@ var Page = (function PageClosure() {
items.push(item);
}
return items;
- },
- startRendering: function Page_startRendering(ctx, callback, textLayer) {
- var stats = this.stats;
- stats.time('Overall');
- // If there is no displayReadyPromise yet, then the operatorList was never
- // requested before. Make the request and create the promise.
- if (!this.displayReadyPromise) {
- this.pdf.startRendering(this);
- this.displayReadyPromise = new Promise();
- }
-
- // Once the operatorList and fonts are loaded, do the actual rendering.
- this.displayReadyPromise.then(
- function pageDisplayReadyPromise() {
- var gfx = new CanvasGraphics(ctx, this.objs, textLayer);
- try {
- this.display(gfx, callback);
- } catch (e) {
- if (callback)
- callback(e);
- else
- error(e);
- }
- }.bind(this),
- function pageDisplayReadPromiseError(reason) {
- if (callback)
- callback(reason);
- else
- error(reason);
- }
- );
}
};
@@ -471,20 +302,20 @@ var Page = (function PageClosure() {
})();
/**
- * The `PDFDocModel` holds all the data of the PDF file. Compared to the
+ * The `PDFDocument` holds all the data of the PDF file. Compared to the
* `PDFDoc`, this one doesn't have any job management code.
- * Right now there exists one PDFDocModel on the main thread + one object
+ * Right now there exists one PDFDocument on the main thread + one object
* for each worker. If there is no worker support enabled, there are two
- * `PDFDocModel` objects on the main thread created.
+ * `PDFDocument` objects on the main thread created.
*/
-var PDFDocModel = (function PDFDocModelClosure() {
- function PDFDocModel(arg, callback) {
+var PDFDocument = (function PDFDocumentClosure() {
+ function PDFDocument(arg, callback) {
if (isStream(arg))
init.call(this, arg);
else if (isArrayBuffer(arg))
init.call(this, new Stream(arg));
else
- error('PDFDocModel: Unknown argument type');
+ error('PDFDocument: Unknown argument type');
}
function init(stream) {
@@ -510,7 +341,7 @@ var PDFDocModel = (function PDFDocModelClosure() {
return true; /* found */
}
- PDFDocModel.prototype = {
+ PDFDocument.prototype = {
get linearization() {
var length = this.stream.length;
var linearization = false;
@@ -571,7 +402,7 @@ var PDFDocModel = (function PDFDocModelClosure() {
},
// Find the header, remove leading garbage and setup the stream
// starting from the header.
- checkHeader: function PDFDocModel_checkHeader() {
+ checkHeader: function PDFDocument_checkHeader() {
var stream = this.stream;
stream.reset();
if (find(stream, '%PDF-', 1024)) {
@@ -581,7 +412,7 @@ var PDFDocModel = (function PDFDocModelClosure() {
}
// May not be a PDF file, continue anyway.
},
- setup: function PDFDocModel_setup(ownerPassword, userPassword) {
+ setup: function PDFDocument_setup(ownerPassword, userPassword) {
this.checkHeader();
var xref = new XRef(this.stream,
this.startXRef,
@@ -595,7 +426,7 @@ var PDFDocModel = (function PDFDocModelClosure() {
// shadow the prototype getter
return shadow(this, 'numPages', num);
},
- getDocumentInfo: function PDFDocModel_getDocumentInfo() {
+ getDocumentInfo: function PDFDocument_getDocumentInfo() {
var info;
if (this.xref.trailer.has('Info')) {
var infoDict = this.xref.trailer.get('Info');
@@ -609,7 +440,7 @@ var PDFDocModel = (function PDFDocModelClosure() {
return shadow(this, 'getDocumentInfo', info);
},
- getFingerprint: function PDFDocModel_getFingerprint() {
+ getFingerprint: function PDFDocument_getFingerprint() {
var xref = this.xref, fileID;
if (xref.trailer.has('ID')) {
fileID = '';
@@ -630,251 +461,10 @@ var PDFDocModel = (function PDFDocModelClosure() {
return shadow(this, 'getFingerprint', fileID);
},
- getPage: function PDFDocModel_getPage(n) {
+ getPage: function PDFDocument_getPage(n) {
return this.catalog.getPage(n);
}
};
- return PDFDocModel;
+ return PDFDocument;
})();
-
-var PDFDoc = (function PDFDocClosure() {
- function PDFDoc(arg, callback) {
- var stream = null;
- var data = null;
-
- if (isStream(arg)) {
- stream = arg;
- data = arg.bytes;
- } else if (isArrayBuffer(arg)) {
- stream = new Stream(arg);
- data = arg;
- } else {
- error('PDFDoc: Unknown argument type');
- }
-
- this.data = data;
- this.stream = stream;
- this.pdfModel = new PDFDocModel(stream);
- this.fingerprint = this.pdfModel.getFingerprint();
- this.info = this.pdfModel.getDocumentInfo();
- this.catalog = this.pdfModel.catalog;
- this.objs = new PDFObjects();
-
- this.pageCache = [];
- this.fontsLoading = {};
- this.workerReadyPromise = new Promise('workerReady');
-
- // If worker support isn't disabled explicit and the browser has worker
- // support, create a new web worker and test if it/the browser fullfills
- // all requirements to run parts of pdf.js in a web worker.
- // Right now, the requirement is, that an Uint8Array is still an Uint8Array
- // as it arrives on the worker. Chrome added this with version 15.
- if (!globalScope.PDFJS.disableWorker && typeof Worker !== 'undefined') {
- var workerSrc = PDFJS.workerSrc;
- if (typeof workerSrc === 'undefined') {
- error('No PDFJS.workerSrc specified');
- }
-
- try {
- var worker;
- if (PDFJS.isFirefoxExtension) {
- // The firefox extension can't load the worker from the resource://
- // url so we have to inline the script and then use the blob loader.
- var bb = new MozBlobBuilder();
- bb.append(document.querySelector('#PDFJS_SCRIPT_TAG').textContent);
- var blobUrl = window.URL.createObjectURL(bb.getBlob());
- worker = new Worker(blobUrl);
- } else {
- // Some versions of FF can't create a worker on localhost, see:
- // https://bugzilla.mozilla.org/show_bug.cgi?id=683280
- worker = new Worker(workerSrc);
- }
-
- var messageHandler = new MessageHandler('main', worker);
-
- messageHandler.on('test', function pdfDocTest(supportTypedArray) {
- if (supportTypedArray) {
- this.worker = worker;
- this.setupMessageHandler(messageHandler);
- } else {
- globalScope.PDFJS.disableWorker = true;
- this.setupFakeWorker();
- }
- }.bind(this));
-
- var testObj = new Uint8Array(1);
- // Some versions of Opera throw a DATA_CLONE_ERR on
- // serializing the typed array.
- messageHandler.send('test', testObj);
- return;
- } catch (e) {
- warn('The worker has been disabled.');
- }
- }
- // Either workers are disabled, not supported or have thrown an exception.
- // Thus, we fallback to a faked worker.
- globalScope.PDFJS.disableWorker = true;
- this.setupFakeWorker();
- }
-
- PDFDoc.prototype = {
- setupFakeWorker: function PDFDoc_setupFakeWorker() {
- // If we don't use a worker, just post/sendMessage to the main thread.
- var fakeWorker = {
- postMessage: function PDFDoc_postMessage(obj) {
- fakeWorker.onmessage({data: obj});
- },
- terminate: function PDFDoc_terminate() {}
- };
-
- var messageHandler = new MessageHandler('main', fakeWorker);
- this.setupMessageHandler(messageHandler);
-
- // If the main thread is our worker, setup the handling for the messages
- // the main thread sends to it self.
- WorkerMessageHandler.setup(messageHandler);
- },
-
-
- setupMessageHandler: function PDFDoc_setupMessageHandler(messageHandler) {
- this.messageHandler = messageHandler;
-
- messageHandler.on('page', function pdfDocPage(data) {
- var pageNum = data.pageNum;
- var page = this.pageCache[pageNum];
- var depFonts = data.depFonts;
-
- page.stats.timeEnd('Page Request');
- page.startRenderingFromOperatorList(data.operatorList, depFonts);
- }, this);
-
- messageHandler.on('obj', function pdfDocObj(data) {
- var id = data[0];
- var type = data[1];
-
- switch (type) {
- case 'JpegStream':
- var imageData = data[2];
- loadJpegStream(id, imageData, this.objs);
- break;
- case 'Image':
- var imageData = data[2];
- this.objs.resolve(id, imageData);
- break;
- case 'Font':
- var name = data[2];
- var file = data[3];
- var properties = data[4];
-
- if (file) {
- // Rewrap the ArrayBuffer in a stream.
- var fontFileDict = new Dict();
- file = new Stream(file, 0, file.length, fontFileDict);
- }
-
- // At this point, only the font object is created but the font is
- // not yet attached to the DOM. This is done in `FontLoader.bind`.
- var font = new Font(name, file, properties);
- this.objs.resolve(id, font);
- break;
- default:
- error('Got unkown object type ' + type);
- }
- }, this);
-
- messageHandler.on('page_error', function pdfDocError(data) {
- var page = this.pageCache[data.pageNum];
- if (page.displayReadyPromise)
- page.displayReadyPromise.reject(data.error);
- else
- error(data.error);
- }, this);
-
- messageHandler.on('jpeg_decode', function(data, promise) {
- var imageData = data[0];
- var components = data[1];
- if (components != 3 && components != 1)
- error('Only 3 component or 1 component can be returned');
-
- var img = new Image();
- img.onload = (function messageHandler_onloadClosure() {
- var width = img.width;
- var height = img.height;
- var size = width * height;
- var rgbaLength = size * 4;
- var buf = new Uint8Array(size * components);
- var tmpCanvas = createScratchCanvas(width, height);
- var tmpCtx = tmpCanvas.getContext('2d');
- tmpCtx.drawImage(img, 0, 0);
- var data = tmpCtx.getImageData(0, 0, width, height).data;
-
- if (components == 3) {
- for (var i = 0, j = 0; i < rgbaLength; i += 4, j += 3) {
- buf[j] = data[i];
- buf[j + 1] = data[i + 1];
- buf[j + 2] = data[i + 2];
- }
- } else if (components == 1) {
- for (var i = 0, j = 0; i < rgbaLength; i += 4, j++) {
- buf[j] = data[i];
- }
- }
- promise.resolve({ data: buf, width: width, height: height});
- }).bind(this);
- var src = 'data:image/jpeg;base64,' + window.btoa(imageData);
- img.src = src;
- });
-
- setTimeout(function pdfDocFontReadySetTimeout() {
- messageHandler.send('doc', this.data);
- this.workerReadyPromise.resolve(true);
- }.bind(this));
- },
-
- get numPages() {
- return this.pdfModel.numPages;
- },
-
- startRendering: function PDFDoc_startRendering(page) {
- // The worker might not be ready to receive the page request yet.
- this.workerReadyPromise.then(function pdfDocStartRenderingThen() {
- page.stats.time('Page Request');
- this.messageHandler.send('page_request', page.pageNumber + 1);
- }.bind(this));
- },
-
- getPage: function PDFDoc_getPage(n) {
- if (this.pageCache[n])
- return this.pageCache[n];
-
- var page = this.pdfModel.getPage(n);
- // Add a reference to the objects such that Page can forward the reference
- // to the CanvasGraphics and so on.
- page.objs = this.objs;
- page.pdf = this;
- return (this.pageCache[n] = page);
- },
-
- destroy: function PDFDoc_destroy() {
- if (this.worker)
- this.worker.terminate();
-
- if (this.fontWorker)
- this.fontWorker.terminate();
-
- for (var n in this.pageCache)
- delete this.pageCache[n];
-
- delete this.data;
- delete this.stream;
- delete this.pdf;
- delete this.catalog;
- }
- };
-
- return PDFDoc;
-})();
-
-globalScope.PDFJS.PDFDoc = PDFDoc;
-
diff --git a/src/evaluator.js b/src/evaluator.js
index 350ab20b2..c57e291c0 100644
--- a/src/evaluator.js
+++ b/src/evaluator.js
@@ -466,7 +466,7 @@ var PartialEvaluator = (function PartialEvaluatorClosure() {
args = [];
} else if (obj != null) {
assertWellFormed(args.length <= 33, 'Too many arguments');
- args.push(obj);
+ args.push(obj instanceof Dict ? obj.getAll() : obj);
}
}
@@ -862,7 +862,6 @@ var PartialEvaluator = (function PartialEvaluatorClosure() {
properties.coded = true;
var charProcs = dict.get('CharProcs').getAll();
var fontResources = dict.get('Resources') || resources;
- properties.resources = fontResources;
properties.charProcOperatorList = {};
for (var key in charProcs) {
var glyphStream = charProcs[key];
diff --git a/src/fonts.js b/src/fonts.js
index 7fdab8fbb..7693f5839 100644
--- a/src/fonts.js
+++ b/src/fonts.js
@@ -766,7 +766,6 @@ var Font = (function FontClosure() {
this.name = name;
this.coded = properties.coded;
this.charProcOperatorList = properties.charProcOperatorList;
- this.resources = properties.resources;
this.sizes = [];
var names = name.split('+');
diff --git a/src/image.js b/src/image.js
index 035e2f754..c8c19f9e5 100644
--- a/src/image.js
+++ b/src/image.js
@@ -15,7 +15,7 @@ var PDFImage = (function PDFImageClosure() {
var colorSpace = dict.get('ColorSpace', 'CS');
colorSpace = ColorSpace.parse(colorSpace, xref, res);
var numComps = colorSpace.numComps;
- handler.send('jpeg_decode', [image.getIR(), numComps], function(message) {
+ handler.send('JpegDecode', [image.getIR(), numComps], function(message) {
var data = message.data;
var stream = new Stream(data, 0, data.length, image.dict);
promise.resolve(stream);
diff --git a/src/obj.js b/src/obj.js
index 200b40a7f..c905a7dc5 100644
--- a/src/obj.js
+++ b/src/obj.js
@@ -37,51 +37,55 @@ var Dict = (function DictClosure() {
// xref is optional
function Dict(xref) {
// Map should only be used internally, use functions below to access.
- this.map = Object.create(null);
- this.xref = xref;
- }
+ var map = Object.create(null);
+
+ this.assignXref = function Dict_assingXref(newXref) {
+ xref = newXref;
+ };
- Dict.prototype = {
// automatically dereferences Ref objects
- get: function Dict_get(key1, key2, key3) {
+ this.get = function Dict_get(key1, key2, key3) {
var value;
- var xref = this.xref;
- if (typeof (value = this.map[key1]) != 'undefined' || key1 in this.map ||
+ if (typeof (value = map[key1]) != 'undefined' || key1 in map ||
typeof key2 == 'undefined') {
- return xref ? this.xref.fetchIfRef(value) : value;
+ return xref ? xref.fetchIfRef(value) : value;
}
- if (typeof (value = this.map[key2]) != 'undefined' || key2 in this.map ||
+ if (typeof (value = map[key2]) != 'undefined' || key2 in map ||
typeof key3 == 'undefined') {
- return xref ? this.xref.fetchIfRef(value) : value;
+ return xref ? xref.fetchIfRef(value) : value;
}
- value = this.map[key3] || null;
- return xref ? this.xref.fetchIfRef(value) : value;
- },
+ value = map[key3] || null;
+ return xref ? xref.fetchIfRef(value) : value;
+ };
+
// no dereferencing
- getRaw: function Dict_getRaw(key) {
- return this.map[key];
- },
+ this.getRaw = function Dict_getRaw(key) {
+ return map[key];
+ };
+
// creates new map and dereferences all Refs
- getAll: function Dict_getAll() {
+ this.getAll = function Dict_getAll() {
var all = {};
- for (var key in this.map)
- all[key] = this.get(key);
+ for (var key in map) {
+ var obj = this.get(key);
+ all[key] = obj instanceof Dict ? obj.getAll() : obj;
+ }
return all;
- },
+ };
- set: function Dict_set(key, value) {
- this.map[key] = value;
- },
+ this.set = function Dict_set(key, value) {
+ map[key] = value;
+ };
- has: function Dict_has(key) {
- return key in this.map;
- },
+ this.has = function Dict_has(key) {
+ return key in map;
+ };
- forEach: function Dict_forEach(callback) {
- for (var key in this.map) {
+ this.forEach = function Dict_forEach(callback) {
+ for (var key in map) {
callback(key, this.get(key));
}
- }
+ };
};
return Dict;
@@ -299,7 +303,7 @@ var XRef = (function XRefClosure() {
this.entries = [];
this.xrefstms = {};
var trailerDict = this.readXRef(startXRef);
- trailerDict.xref = this;
+ trailerDict.assignXref(this);
this.trailer = trailerDict;
// prepare the XRef cache
this.cache = [];
diff --git a/src/util.js b/src/util.js
index de7f3c1d5..63f6115a7 100644
--- a/src/util.js
+++ b/src/util.js
@@ -76,7 +76,7 @@ function stringToBytes(str) {
var IDENTITY_MATRIX = [1, 0, 0, 1, 0, 0];
-var Util = (function UtilClosure() {
+var Util = PDFJS.Util = (function UtilClosure() {
function Util() {}
Util.makeCssRgb = function Util_makeCssRgb(r, g, b) {
@@ -97,6 +97,19 @@ var Util = (function UtilClosure() {
return [xt, yt];
};
+ Util.applyInverseTransform = function Util_applyTransform(p, m) {
+ var d = m[0] * m[3] - m[1] * m[2];
+ var xt = (p[0] * m[3] - p[1] * m[2] + m[2] * m[5] - m[4] * m[3]) / d;
+ var yt = (-p[0] * m[1] + p[1] * m[0] + m[4] * m[1] - m[5] * m[0]) / d;
+ return [xt, yt];
+ };
+
+ Util.inverseTransform = function Util_inverseTransform(m) {
+ var d = m[0] * m[3] - m[1] * m[2];
+ return [m[3] / d, -m[1] / d, -m[2] / d, m[0] / d,
+ (m[2] * m[5] - m[4] * m[3]) / d, (m[4] * m[1] - m[5] * m[0]) / d];
+ };
+
// Apply a generic 3d matrix M on a 3-vector v:
// | a b c | | X |
// | d e f | x | Y |
@@ -165,7 +178,7 @@ var Util = (function UtilClosure() {
}
return result;
- }
+ };
Util.sign = function Util_sign(num) {
return num < 0 ? -1 : 1;
@@ -174,6 +187,80 @@ var Util = (function UtilClosure() {
return Util;
})();
+var PageViewport = PDFJS.PageViewport = (function PageViewportClosure() {
+ function PageViewport(viewBox, scale, rotate, offsetX, offsetY) {
+ // creating transform to convert pdf coordinate system to the normal
+ // canvas like coordinates taking in account scale and rotation
+ var centerX = (viewBox[2] + viewBox[0]) / 2;
+ var centerY = (viewBox[3] + viewBox[1]) / 2;
+ var rotateA, rotateB, rotateC, rotateD;
+ switch (rotate) {
+ case -180:
+ case 180:
+ rotateA = -1; rotateB = 0; rotateC = 0; rotateD = 1;
+ break;
+ case -270:
+ case 90:
+ rotateA = 0; rotateB = 1; rotateC = 1; rotateD = 0;
+ break;
+ case -90:
+ case 270:
+ rotateA = 0; rotateB = -1; rotateC = -1; rotateD = 0;
+ break;
+ case 360:
+ case 0:
+ default:
+ rotateA = 1; rotateB = 0; rotateC = 0; rotateD = -1;
+ break;
+ }
+ var offsetCanvasX, offsetCanvasY;
+ var width, height;
+ if (rotateA == 0) {
+ offsetCanvasX = Math.abs(centerY - viewBox[1]) * scale + offsetX;
+ offsetCanvasY = Math.abs(centerX - viewBox[0]) * scale + offsetY;
+ width = Math.abs(viewBox[3] - viewBox[1]) * scale;
+ height = Math.abs(viewBox[2] - viewBox[0]) * scale;
+ } else {
+ offsetCanvasX = Math.abs(centerX - viewBox[0]) * scale + offsetX;
+ offsetCanvasY = Math.abs(centerY - viewBox[1]) * scale + offsetY;
+ width = Math.abs(viewBox[2] - viewBox[0]) * scale;
+ height = Math.abs(viewBox[3] - viewBox[1]) * scale;
+ }
+ // creating transform for the following operations:
+ // translate(-centerX, -centerY), rotate and flip vertically,
+ // scale, and translate(offsetCanvasX, offsetCanvasY)
+ this.transform = [
+ rotateA * scale,
+ rotateB * scale,
+ rotateC * scale,
+ rotateD * scale,
+ offsetCanvasX - rotateA * scale * centerX - rotateC * scale * centerY,
+ offsetCanvasY - rotateB * scale * centerX - rotateD * scale * centerY
+ ];
+
+ this.offsetX = offsetX;
+ this.offsetY = offsetY;
+ this.width = width;
+ this.height = height;
+ this.fontScale = scale;
+ }
+ PageViewport.prototype = {
+ convertToViewportPoint: function PageViewport_convertToViewportPoint(x, y) {
+ return Util.applyTransform([x, y], this.transform);
+ },
+ convertToViewportRectangle:
+ function PageViewport_convertToViewportRectangle(rect) {
+ var tl = Util.applyTransform([rect[0], rect[1]], this.transform);
+ var br = Util.applyTransform([rect[2], rect[3]], this.transform);
+ return [tl[0], tl[1], br[0], br[1]];
+ },
+ convertToPdfPoint: function PageViewport_convertToPdfPoint(x, y) {
+ return Util.applyInverseTransform([x, y], this.transform);
+ }
+ };
+ return PageViewport;
+})();
+
var PDFStringTranslateTable = [
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0x2D8, 0x2C7, 0x2C6, 0x2D9, 0x2DD, 0x2DB, 0x2DA, 0x2DC, 0, 0, 0, 0, 0, 0, 0,
@@ -275,7 +362,7 @@ function isPDFFunction(v) {
* can be set. If any of these happens twice or the data is required before
* it was set, an exception is throw.
*/
-var Promise = (function PromiseClosure() {
+var Promise = PDFJS.Promise = (function PromiseClosure() {
var EMPTY_PROMISE = {};
/**
@@ -297,6 +384,7 @@ var Promise = (function PromiseClosure() {
}
this.callbacks = [];
this.errbacks = [];
+ this.progressbacks = [];
};
/**
* Builds a promise that is resolved when all the passed in promises are
@@ -312,7 +400,7 @@ var Promise = (function PromiseClosure() {
deferred.resolve(results);
return deferred;
}
- for (var i = 0; i < unresolved; ++i) {
+ for (var i = 0, ii = promises.length; i < ii; ++i) {
var promise = promises[i];
promise.then((function(i) {
return function(value) {
@@ -376,6 +464,13 @@ var Promise = (function PromiseClosure() {
}
},
+ progress: function Promise_progress(data) {
+ var callbacks = this.progressbacks;
+ for (var i = 0, ii = callbacks.length; i < ii; i++) {
+ callbacks[i].call(null, data);
+ }
+ },
+
reject: function Promise_reject(reason) {
if (this.isRejected) {
error('A Promise can be rejected only once ' + this.name);
@@ -393,7 +488,7 @@ var Promise = (function PromiseClosure() {
}
},
- then: function Promise_then(callback, errback) {
+ then: function Promise_then(callback, errback, progressback) {
if (!callback) {
error('Requiring callback' + this.name);
}
@@ -410,6 +505,9 @@ var Promise = (function PromiseClosure() {
if (errback)
this.errbacks.push(errback);
}
+
+ if (progressback)
+ this.progressbacks.push(progressback);
}
};
diff --git a/src/worker.js b/src/worker.js
index 42bd61050..25f3f52cd 100644
--- a/src/worker.js
+++ b/src/worker.js
@@ -85,14 +85,43 @@ var WorkerMessageHandler = {
handler.send('test', data instanceof Uint8Array);
});
- handler.on('doc', function wphSetupDoc(data) {
+ handler.on('GetDocRequest', function wphSetupDoc(data) {
// Create only the model of the PDFDoc, which is enough for
// processing the content of the pdf.
- pdfModel = new PDFDocModel(new Stream(data));
+ pdfModel = new PDFDocument(new Stream(data));
+ var doc = {
+ numPages: pdfModel.numPages,
+ fingerprint: pdfModel.getFingerprint(),
+ destinations: pdfModel.catalog.destinations,
+ outline: pdfModel.catalog.documentOutline,
+ info: pdfModel.getDocumentInfo(),
+ metadata: pdfModel.catalog.metadata
+ };
+ handler.send('GetDoc', {pdfInfo: doc});
});
- handler.on('page_request', function wphSetupPageRequest(pageNum) {
- pageNum = parseInt(pageNum);
+ handler.on('GetPageRequest', function wphSetupGetPage(data) {
+ var pageNumber = data.pageIndex + 1;
+ var pdfPage = pdfModel.getPage(pageNumber);
+ var page = {
+ pageIndex: data.pageIndex,
+ rotate: pdfPage.rotate,
+ ref: pdfPage.ref,
+ view: pdfPage.view
+ };
+ handler.send('GetPage', {pageInfo: page});
+ });
+
+ handler.on('GetAnnotationsRequest', function wphSetupGetAnnotations(data) {
+ var pdfPage = pdfModel.getPage(data.pageIndex + 1);
+ handler.send('GetAnnotations', {
+ pageIndex: data.pageIndex,
+ annotations: pdfPage.getAnnotations()
+ });
+ });
+
+ handler.on('RenderPageRequest', function wphSetupRenderPage(data) {
+ var pageNum = data.pageIndex + 1;
// The following code does quite the same as
@@ -130,7 +159,7 @@ var WorkerMessageHandler = {
};
}
- handler.send('page_error', {
+ handler.send('PageError', {
pageNum: pageNum,
error: e
});
@@ -148,9 +177,8 @@ var WorkerMessageHandler = {
fonts[dep] = true;
}
}
-
- handler.send('page', {
- pageNum: pageNum,
+ handler.send('RenderPage', {
+ pageIndex: data.pageIndex,
operatorList: operatorList,
depFonts: Object.keys(fonts)
});
diff --git a/test/driver.js b/test/driver.js
index a1dc4b242..26c5a156a 100644
--- a/test/driver.js
+++ b/test/driver.js
@@ -10,7 +10,7 @@
// Disable worker support for running test as
// https://github.com/mozilla/pdf.js/pull/764#issuecomment-2638944
// "firefox-bin: Fatal IO error 12 (Cannot allocate memory) on X server :1."
-PDFJS.disableWorker = true;
+// PDFJS.disableWorker = true;
var appPath, browser, canvas, currentTaskIdx, manifest, stdout;
var inFlightRequests = 0;
@@ -100,13 +100,24 @@ function nextTask() {
getPdf(task.file, function nextTaskGetPdf(data) {
var failure;
+ function continuation() {
+ task.pageNum = task.firstPage || 1;
+ nextPage(task, failure);
+ }
try {
- task.pdfDoc = new PDFJS.PDFDoc(data);
+ var promise = PDFJS.getDocument(data);
+ promise.then(function(doc) {
+ task.pdfDoc = doc;
+ continuation();
+ }, function(e) {
+ failure = 'load PDF doc : ' + e;
+ continuation();
+ });
+ return;
} catch (e) {
failure = 'load PDF doc : ' + exceptionToString(e);
}
- task.pageNum = task.firstPage || 1;
- nextPage(task, failure);
+ continuation();
});
}
@@ -163,45 +174,41 @@ function nextPage(task, loadError) {
log(' loading page ' + task.pageNum + '/' + task.pdfDoc.numPages +
'... ');
var ctx = canvas.getContext('2d');
- page = task.pdfDoc.getPage(task.pageNum);
+ task.pdfDoc.getPage(task.pageNum).then(function(page) {
+ var pdfToCssUnitsCoef = 96.0 / 72.0;
+ var viewport = page.getViewport(pdfToCssUnitsCoef);
+ canvas.width = viewport.width;
+ canvas.height = viewport.height;
+ clear(ctx);
- var pdfToCssUnitsCoef = 96.0 / 72.0;
- // using mediaBox for the canvas size
- var pageWidth = page.width;
- var pageHeight = page.height;
- canvas.width = pageWidth * pdfToCssUnitsCoef;
- canvas.height = pageHeight * pdfToCssUnitsCoef;
- clear(ctx);
-
- // using the text layer builder that does nothing to test
- // text layer creation operations
- var textLayerBuilder = {
- beginLayout: function nullTextLayerBuilderBeginLayout() {},
- endLayout: function nullTextLayerBuilderEndLayout() {},
- appendText: function nullTextLayerBuilderAppendText(text, fontName,
- fontSize) {}
- };
-
- page.startRendering(
- ctx,
- function nextPageStartRendering(error) {
- var failureMessage = false;
- if (error)
- failureMessage = 'render : ' + error.message;
- snapshotCurrentPage(task, failureMessage);
+ // using the text layer builder that does nothing to test
+ // text layer creation operations
+ var textLayerBuilder = {
+ beginLayout: function nullTextLayerBuilderBeginLayout() {},
+ endLayout: function nullTextLayerBuilderEndLayout() {},
+ appendText: function nullTextLayerBuilderAppendText(text, fontName,
+ fontSize) {}
+ };
+ var renderContext = {
+ canvasContext: ctx,
+ textLayer: textLayerBuilder,
+ viewport: viewport
+ };
+ page.render(renderContext).then(function() {
+ snapshotCurrentPage(task, false);
},
- textLayerBuilder
- );
+ function(error) {
+ snapshotCurrentPage(task, 'render : ' + error);
+ });
+ },
+ function(error) {
+ snapshotCurrentPage(task, 'render : ' + error);
+ });
} catch (e) {
failure = 'page setup : ' + exceptionToString(e);
+ snapshotCurrentPage(task, failure);
}
}
-
- if (failure) {
- // Skip right to snapshotting if there was a failure, since the
- // fonts might be in an inconsistent state.
- snapshotCurrentPage(task, failure);
- }
}
function snapshotCurrentPage(task, failure) {
diff --git a/test/pdfs/.gitignore b/test/pdfs/.gitignore
index 2a7c27875..e4b9fb10c 100644
--- a/test/pdfs/.gitignore
+++ b/test/pdfs/.gitignore
@@ -31,4 +31,4 @@
!issue1002.pdf
!issue925.pdf
!gradientfill.pdf
-
+!basicapi.pdf
diff --git a/test/pdfs/basicapi.pdf b/test/pdfs/basicapi.pdf
new file mode 100644
index 000000000..31ffcfe9f
Binary files /dev/null and b/test/pdfs/basicapi.pdf differ
diff --git a/test/test_manifest.json b/test/test_manifest.json
index 6a083bdf7..207c41c22 100644
--- a/test/test_manifest.json
+++ b/test/test_manifest.json
@@ -29,6 +29,7 @@
"file": "pdfs/pdf.pdf",
"md5": "dbdb23c939d2be09b43126c3c56060c7",
"link": true,
+ "pageLimit": 500,
"rounds": 1,
"type": "load"
},
diff --git a/test/test_slave.html b/test/test_slave.html
index 50bb067e1..7c2097110 100644
--- a/test/test_slave.html
+++ b/test/test_slave.html
@@ -5,6 +5,7 @@
+
diff --git a/test/unit/api_spec.js b/test/unit/api_spec.js
new file mode 100644
index 000000000..318dbb42a
--- /dev/null
+++ b/test/unit/api_spec.js
@@ -0,0 +1,109 @@
+/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
+
+'use strict';
+
+describe('api', function() {
+ // TODO run with worker enabled
+ PDFJS.disableWorker = true;
+ var basicApiUrl = '/basicapi.pdf';
+ function waitsForPromise(promise) {
+ waitsFor(function() {
+ return promise.isResolved || promise.isRejected;
+ }, 250);
+ }
+ function expectAfterPromise(promise, successCallback) {
+ waitsForPromise(promise);
+ runs(function() {
+ promise.then(successCallback,
+ function(error, e) {
+ // Shouldn't get here.
+ expect(false).toEqual(true);
+ });
+ });
+ }
+ describe('PDFJS', function() {
+ describe('getDocument', function() {
+ it('creates pdf doc from URL', function() {
+ console.log('what is');
+ debugger;
+ var promise = PDFJS.getDocument(basicApiUrl);
+ expectAfterPromise(promise, function(data) {
+ expect(true).toEqual(true);
+ });
+ });
+ /*
+ it('creates pdf doc from typed array', function() {
+ // TODO
+ });
+ */
+ });
+ });
+ describe('PDFDocument', function() {
+ var promise = PDFJS.getDocument(basicApiUrl);
+ waitsForPromise(promise);
+ var doc;
+ runs(function() {
+ promise.then(function(data) { doc = data; });
+ });
+ it('gets number of pages', function() {
+ expect(doc.numPages).toEqual(3);
+ });
+ it('gets fingerprint', function() {
+ expect(typeof doc.fingerprint).toEqual('string');
+ });
+ it('gets page', function() {
+ var promise = doc.getPage(1);
+ expectAfterPromise(promise, function(data) {
+ expect(true).toEqual(true);
+ });
+ });
+ it('gets destinations', function() {
+ var promise = doc.getDestinations();
+ expectAfterPromise(promise, function(data) {
+ // TODO this seems to be broken for the test pdf
+ });
+ });
+ it('gets outline', function() {
+ var promise = doc.getOutline();
+ expectAfterPromise(promise, function(outline) {
+ // Two top level entries.
+ expect(outline.length).toEqual(2);
+ // Make sure some basic attributes are set.
+ expect(outline[1].title).toEqual('Chapter 1');
+ expect(outline[1].items.length).toEqual(1);
+ expect(outline[1].items[0].title).toEqual('Paragraph 1.1');
+ });
+ });
+ it('gets metadata', function() {
+ var promise = doc.getMetadata();
+ expectAfterPromise(promise, function(metadata) {
+ expect(metadata.info['Title']).toEqual('Basic API Test');
+ expect(metadata.metadata.get('dc:title')).toEqual('Basic API Test');
+ });
+ });
+ });
+ describe('Page', function() {
+ var promise = new Promise();
+ PDFJS.getDocument(basicApiUrl).then(function(doc) {
+ doc.getPage(1).then(function(data) {
+ promise.resolve(data);
+ });
+ });
+ waitsForPromise(promise);
+ var page;
+ runs(function() {
+ promise.then(function(data) {
+ page = data;
+ });
+ });
+ it('gets ref', function() {
+ expect(page.ref).toEqual({num: 15, gen: 0});
+ });
+ // TODO rotate
+ // TODO viewport
+ // TODO annotaions
+ // TOOD text content
+ // TODO operation list
+ });
+});
diff --git a/test/unit/jsTestDriver.conf b/test/unit/jsTestDriver.conf
index 9a26df6a4..b0f917b66 100644
--- a/test/unit/jsTestDriver.conf
+++ b/test/unit/jsTestDriver.conf
@@ -1,32 +1,39 @@
server: http://localhost:4224
-load:
- - ../../external/jasmine/jasmine.js
- - ../../external/jasmineAdapter/JasmineAdapter.js
- - ../../src/obj.js
- - ../../src/core.js
- - ../../src/util.js
- - ../../src/canvas.js
- - ../../src/obj.js
- - ../../src/function.js
- - ../../src/charsets.js
- - ../../src/cidmaps.js
- - ../../src/colorspace.js
- - ../../src/crypto.js
- - ../../src/evaluator.js
- - ../../src/fonts.js
- - ../../src/glyphlist.js
- - ../../src/image.js
- - ../../src/metrics.js
- - ../../src/parser.js
- - ../../src/pattern.js
- - ../../src/stream.js
- - ../../src/worker.js
- - ../../src/bidi.js
- - ../../external/jpgjs/jpg.js
- - ../unit/obj_spec.js
- - ../unit/font_spec.js
- - ../unit/function_spec.js
- - ../unit/crypto_spec.js
- - ../unit/stream_spec.js
+basepath: ..
+load:
+ - ../external/jasmine/jasmine.js
+ - ../external/jasmineAdapter/JasmineAdapter.js
+ - ../src/obj.js
+ - ../src/core.js
+ - ../src/util.js
+ - ../src/api.js
+ - ../src/canvas.js
+ - ../src/obj.js
+ - ../src/function.js
+ - ../src/charsets.js
+ - ../src/cidmaps.js
+ - ../src/colorspace.js
+ - ../src/crypto.js
+ - ../src/evaluator.js
+ - ../src/fonts.js
+ - ../src/glyphlist.js
+ - ../src/image.js
+ - ../src/metrics.js
+ - ../src/parser.js
+ - ../src/pattern.js
+ - ../src/stream.js
+ - ../src/worker.js
+ - ../src/bidi.js
+ - ../src/metadata.js
+ - ../external/jpgjs/jpg.js
+ - unit/obj_spec.js
+ - unit/font_spec.js
+ - unit/function_spec.js
+ - unit/crypto_spec.js
+ - unit/stream_spec.js
+ - unit/api_spec.js
+
+gateway:
+ - {matcher: "*.pdf", server: "http://localhost:8888/test/pdfs/"}
diff --git a/web/debugger.js b/web/debugger.js
index 00f5f6fd4..610a63854 100644
--- a/web/debugger.js
+++ b/web/debugger.js
@@ -163,29 +163,29 @@ var StepperManager = (function StepperManagerClosure() {
enabled: false,
active: false,
// Stepper specific functions.
- create: function create(pageNumber) {
+ create: function create(pageIndex) {
var debug = document.createElement('div');
- debug.id = 'stepper' + pageNumber;
+ debug.id = 'stepper' + pageIndex;
debug.setAttribute('hidden', true);
debug.className = 'stepper';
stepperDiv.appendChild(debug);
var b = document.createElement('option');
- b.textContent = 'Page ' + (pageNumber + 1);
- b.value = pageNumber;
+ b.textContent = 'Page ' + (pageIndex + 1);
+ b.value = pageIndex;
stepperChooser.appendChild(b);
- var initBreakPoints = breakPoints[pageNumber] || [];
- var stepper = new Stepper(debug, pageNumber, initBreakPoints);
+ var initBreakPoints = breakPoints[pageIndex] || [];
+ var stepper = new Stepper(debug, pageIndex, initBreakPoints);
steppers.push(stepper);
if (steppers.length === 1)
- this.selectStepper(pageNumber, false);
+ this.selectStepper(pageIndex, false);
return stepper;
},
- selectStepper: function selectStepper(pageNumber, selectPanel) {
+ selectStepper: function selectStepper(pageIndex, selectPanel) {
if (selectPanel)
this.manager.selectPanel(1);
for (var i = 0; i < steppers.length; ++i) {
var stepper = steppers[i];
- if (stepper.pageNumber == pageNumber)
+ if (stepper.pageIndex == pageIndex)
stepper.panel.removeAttribute('hidden');
else
stepper.panel.setAttribute('hidden', true);
@@ -193,11 +193,11 @@ var StepperManager = (function StepperManagerClosure() {
var options = stepperChooser.options;
for (var i = 0; i < options.length; ++i) {
var option = options[i];
- option.selected = option.value == pageNumber;
+ option.selected = option.value == pageIndex;
}
},
- saveBreakPoints: function saveBreakPoints(pageNumber, bps) {
- breakPoints[pageNumber] = bps;
+ saveBreakPoints: function saveBreakPoints(pageIndex, bps) {
+ breakPoints[pageIndex] = bps;
sessionStorage.setItem('pdfjsBreakPoints', JSON.stringify(breakPoints));
}
};
@@ -205,12 +205,12 @@ var StepperManager = (function StepperManagerClosure() {
// The stepper for each page's IRQueue.
var Stepper = (function StepperClosure() {
- function Stepper(panel, pageNumber, initialBreakPoints) {
+ function Stepper(panel, pageIndex, initialBreakPoints) {
this.panel = panel;
this.len;
this.breakPoint = 0;
this.nextBreakPoint = null;
- this.pageNumber = pageNumber;
+ this.pageIndex = pageIndex;
this.breakPoints = initialBreakPoints;
this.currentIdx = -1;
}
@@ -256,7 +256,7 @@ var Stepper = (function StepperClosure() {
self.breakPoints.push(x);
else
self.breakPoints.splice(self.breakPoints.indexOf(x), 1);
- StepperManager.saveBreakPoints(self.pageNumber, self.breakPoints);
+ StepperManager.saveBreakPoints(self.pageIndex, self.breakPoints);
}
})(i);
@@ -278,7 +278,7 @@ var Stepper = (function StepperClosure() {
return null;
},
breakIt: function breakIt(idx, callback) {
- StepperManager.selectStepper(this.pageNumber, true);
+ StepperManager.selectStepper(this.pageIndex, true);
var self = this;
var dom = document;
self.currentIdx = idx;
diff --git a/web/viewer.html b/web/viewer.html
index d275f77c1..ef61ce697 100644
--- a/web/viewer.html
+++ b/web/viewer.html
@@ -11,6 +11,7 @@
+
diff --git a/web/viewer.js b/web/viewer.js
index 3587c96bd..68f0a6a33 100644
--- a/web/viewer.js
+++ b/web/viewer.js
@@ -318,27 +318,25 @@ var PDFView = {
}
var self = this;
- PDFJS.getPdf(
- {
- url: url,
- progress: function getPdfProgress(evt) {
- if (evt.lengthComputable)
- self.progress(evt.loaded / evt.total);
- },
- error: function getPdfError(e) {
- var loadingIndicator = document.getElementById('loading');
- loadingIndicator.textContent = 'Error';
- var moreInfo = {
- message: 'Unexpected server response of ' + e.target.status + '.'
- };
- self.error('An error occurred while loading the PDF.', moreInfo);
- }
- },
- function getPdfLoad(data) {
- self.loading = true;
- self.load(data, scale);
+ self.loading = true;
+ PDFJS.getDocument(url).then(
+ function getDocumentCallback(pdfDocument) {
+ self.load(pdfDocument, scale);
self.loading = false;
- });
+ },
+ function getDocumentError(message, exception) {
+ var loadingIndicator = document.getElementById('loading');
+ loadingIndicator.textContent = 'Error';
+ var moreInfo = {
+ message: message
+ };
+ self.error('An error occurred while loading the PDF.', moreInfo);
+ self.loading = false;
+ },
+ function getDocumentProgress(progressData) {
+ self.progress(progressData.loaded / progressData.total);
+ }
+ );
},
download: function pdfViewDownload() {
@@ -360,6 +358,8 @@ var PDFView = {
var destRef = dest[0];
var pageNumber = destRef instanceof Object ?
this.pagesRefMap[destRef.num + ' ' + destRef.gen + ' R'] : (destRef + 1);
+ if (pageNumber > this.pages.length)
+ pageNumber = this.pages.length;
if (pageNumber) {
this.page = pageNumber;
var currentPage = this.pages[pageNumber - 1];
@@ -461,7 +461,7 @@ var PDFView = {
PDFView.loadingBar.percent = percent;
},
- load: function pdfViewLoad(data, scale) {
+ load: function pdfViewLoad(pdfDocument, scale) {
function bindOnAfterDraw(pageView, thumbnailView) {
// when page is painted, using the image as thumbnail base
pageView.onAfterDraw = function pdfViewLoadOnAfterDraw() {
@@ -489,14 +489,8 @@ var PDFView = {
while (container.hasChildNodes())
container.removeChild(container.lastChild);
- var pdf;
- try {
- pdf = new PDFJS.PDFDoc(data);
- } catch (e) {
- this.error('An error occurred while reading the PDF.', e);
- }
- var pagesCount = pdf.numPages;
- var id = pdf.fingerprint;
+ var pagesCount = pdfDocument.numPages;
+ var id = pdfDocument.fingerprint;
var storedHash = null;
document.getElementById('numPages').textContent = pagesCount;
document.getElementById('pageNumber').max = pagesCount;
@@ -514,30 +508,68 @@ var PDFView = {
var pages = this.pages = [];
var pagesRefMap = {};
var thumbnails = this.thumbnails = [];
- for (var i = 1; i <= pagesCount; i++) {
- var page = pdf.getPage(i);
- var pageView = new PageView(container, page, i, page.width, page.height,
- page.stats, this.navigateTo.bind(this));
- var thumbnailView = new ThumbnailView(sidebar, page, i,
- page.width / page.height);
- bindOnAfterDraw(pageView, thumbnailView);
+ var pagePromises = [];
+ for (var i = 1; i <= pagesCount; i++)
+ pagePromises.push(pdfDocument.getPage(i));
+ var self = this;
+ var pagesPromise = PDFJS.Promise.all(pagePromises);
+ pagesPromise.then(function(promisedPages) {
+ for (var i = 1; i <= pagesCount; i++) {
+ var page = promisedPages[i - 1];
+ var pageView = new PageView(container, page, i, scale,
+ page.stats, self.navigateTo.bind(self));
+ var thumbnailView = new ThumbnailView(sidebar, page, i);
+ bindOnAfterDraw(pageView, thumbnailView);
- pages.push(pageView);
- thumbnails.push(thumbnailView);
- var pageRef = page.ref;
- pagesRefMap[pageRef.num + ' ' + pageRef.gen + ' R'] = i;
- }
+ pages.push(pageView);
+ thumbnails.push(thumbnailView);
+ var pageRef = page.ref;
+ pagesRefMap[pageRef.num + ' ' + pageRef.gen + ' R'] = i;
+ }
- this.pagesRefMap = pagesRefMap;
- this.destinations = pdf.catalog.destinations;
+ self.pagesRefMap = pagesRefMap;
+ });
- if (pdf.catalog.documentOutline) {
- this.outline = new DocumentOutlineView(pdf.catalog.documentOutline);
- var outlineSwitchButton = document.getElementById('outlineSwitch');
- outlineSwitchButton.removeAttribute('disabled');
- this.switchSidebarView('outline');
- }
+ var destinationsPromise = pdfDocument.getDestinations();
+ destinationsPromise.then(function(destinations) {
+ self.destinations = destinations;
+ });
+ // outline and initial view depends on destinations and pagesRefMap
+ PDFJS.Promise.all([pagesPromise, destinationsPromise]).then(function() {
+ pdfDocument.getOutline().then(function(outline) {
+ if (!outline)
+ return;
+
+ self.outline = new DocumentOutlineView(outline);
+ var outlineSwitchButton = document.getElementById('outlineSwitch');
+ outlineSwitchButton.removeAttribute('disabled');
+ self.switchSidebarView('outline');
+ });
+
+ self.setInitialView(storedHash, scale);
+ });
+
+ pdfDocument.getMetadata().then(function(data) {
+ var info = data.info, metadata = data.metadata;
+ self.documentInfo = info;
+ self.metadata = metadata;
+
+ var pdfTitle;
+ if (metadata) {
+ if (metadata.has('dc:title'))
+ pdfTitle = metadata.get('dc:title');
+ }
+
+ if (!pdfTitle && info && info['Title'])
+ pdfTitle = info['Title'];
+
+ if (pdfTitle)
+ document.title = pdfTitle + ' - ' + document.title;
+ });
+ },
+
+ setInitialView: function pdfViewSetInitialView(storedHash, scale) {
// Reset the current scale, as otherwise the page's scale might not get
// updated if the zoom level stayed the same.
this.currentScale = 0;
@@ -558,24 +590,6 @@ var PDFView = {
// Setting the default one.
this.parseScale(kDefaultScale, true);
}
-
- this.metadata = null;
- var metadata = pdf.catalog.metadata;
- var info = this.documentInfo = pdf.info;
- var pdfTitle;
-
- if (metadata) {
- this.metadata = metadata = new PDFJS.Metadata(metadata);
-
- if (metadata.has('dc:title'))
- pdfTitle = metadata.get('dc:title');
- }
-
- if (!pdfTitle && info && info['Title'])
- pdfTitle = info['Title'];
-
- if (pdfTitle)
- document.title = pdfTitle + ' - ' + document.title;
},
setHash: function pdfViewSetHash(hash) {
@@ -652,7 +666,7 @@ var PDFView = {
var windowTop = window.pageYOffset;
for (var i = 1; i <= pages.length; ++i) {
var page = pages[i - 1];
- var pageHeight = page.height * page.scale + kBottomMargin;
+ var pageHeight = page.height + kBottomMargin;
if (currentHeight + pageHeight > windowTop)
break;
@@ -711,16 +725,13 @@ var PDFView = {
}
};
-var PageView = function pageView(container, content, id, pageWidth, pageHeight,
+var PageView = function pageView(container, pdfPage, id, scale,
stats, navigateTo) {
this.id = id;
- this.content = content;
+ this.pdfPage = pdfPage;
- var view = this.content.view;
- this.x = view.x;
- this.y = view.y;
- this.width = view.width;
- this.height = view.height;
+ this.scale = scale || 1.0;
+ this.viewport = this.pdfPage.getViewport(this.scale);
var anchor = document.createElement('a');
anchor.name = '' + this.id;
@@ -734,8 +745,11 @@ var PageView = function pageView(container, content, id, pageWidth, pageHeight,
this.update = function pageViewUpdate(scale) {
this.scale = scale || this.scale;
- div.style.width = (this.width * this.scale) + 'px';
- div.style.height = (this.height * this.scale) + 'px';
+ var viewport = this.pdfPage.getViewport(this.scale);
+
+ this.viewport = viewport;
+ div.style.width = viewport.width + 'px';
+ div.style.height = viewport.height + 'px';
while (div.hasChildNodes())
div.removeChild(div.lastChild);
@@ -748,7 +762,21 @@ var PageView = function pageView(container, content, id, pageWidth, pageHeight,
div.appendChild(this.loadingIconDiv);
};
- function setupAnnotations(content, scale) {
+ Object.defineProperty(this, 'width', {
+ get: function PageView_getWidth() {
+ return this.viewport.width;
+ },
+ enumerable: true
+ });
+
+ Object.defineProperty(this, 'height', {
+ get: function PageView_getHeight() {
+ return this.viewport.height;
+ },
+ enumerable: true
+ });
+
+ function setupAnnotations(pdfPage, viewport) {
function bindLink(link, dest) {
link.href = PDFView.getDestinationHash(dest);
link.onclick = function pageViewSetupLinksOnclick() {
@@ -758,11 +786,13 @@ var PageView = function pageView(container, content, id, pageWidth, pageHeight,
};
}
function createElementWithStyle(tagName, item) {
+ var rect = viewport.convertToViewportRectangle(item.rect);
+ rect = PDFJS.Util.normalizeRect(rect);
var element = document.createElement(tagName);
- element.style.left = (Math.floor(item.x - view.x) * scale) + 'px';
- element.style.top = (Math.floor(item.y - view.y) * scale) + 'px';
- element.style.width = Math.ceil(item.width * scale) + 'px';
- element.style.height = Math.ceil(item.height * scale) + 'px';
+ element.style.left = Math.floor(rect[0]) + 'px';
+ element.style.top = Math.floor(rect[1]) + 'px';
+ element.style.width = Math.ceil(rect[2] - rect[0]) + 'px';
+ element.style.height = Math.ceil(rect[3] - rect[1]) + 'px';
return element;
}
function createCommentAnnotation(type, item) {
@@ -809,29 +839,29 @@ var PageView = function pageView(container, content, id, pageWidth, pageHeight,
return container;
}
- var items = content.getAnnotations();
- for (var i = 0; i < items.length; i++) {
- var item = items[i];
- switch (item.type) {
- case 'Link':
- var link = createElementWithStyle('a', item);
- link.href = item.url || '';
- if (!item.url)
- bindLink(link, ('dest' in item) ? item.dest : null);
- div.appendChild(link);
- break;
- case 'Text':
- var comment = createCommentAnnotation(item.name, item);
- if (comment)
- div.appendChild(comment);
- break;
+ pdfPage.getAnnotations().then(function(items) {
+ for (var i = 0; i < items.length; i++) {
+ var item = items[i];
+ switch (item.type) {
+ case 'Link':
+ var link = createElementWithStyle('a', item);
+ link.href = item.url || '';
+ if (!item.url)
+ bindLink(link, ('dest' in item) ? item.dest : null);
+ div.appendChild(link);
+ break;
+ case 'Text':
+ var comment = createCommentAnnotation(item.name, item);
+ if (comment)
+ div.appendChild(comment);
+ break;
+ }
}
- }
+ });
}
this.getPagePoint = function pageViewGetPagePoint(x, y) {
- var scale = PDFView.currentScale;
- return this.content.rotatePoint(x / scale, y / scale);
+ return this.viewport.convertToPdfPoint(x, y);
};
this.scrollIntoView = function pageViewScrollIntoView(dest) {
@@ -879,8 +909,8 @@ var PageView = function pageView(container, content, id, pageWidth, pageHeight,
}
var boundingRect = [
- this.content.rotatePoint(x, y),
- this.content.rotatePoint(x + width, y + height)
+ this.viewport.convertToViewportPoint(x, y),
+ this.viewport.convertToViewportPoint(x + width, y + height)
];
if (scale && scale !== PDFView.currentScale)
@@ -891,18 +921,18 @@ var PageView = function pageView(container, content, id, pageWidth, pageHeight,
setTimeout(function pageViewScrollIntoViewRelayout() {
// letting page to re-layout before scrolling
var scale = PDFView.currentScale;
- var x = Math.min(boundingRect[0].x, boundingRect[1].x);
- var y = Math.min(boundingRect[0].y, boundingRect[1].y);
- var width = Math.abs(boundingRect[0].x - boundingRect[1].x);
- var height = Math.abs(boundingRect[0].y - boundingRect[1].y);
+ var x = Math.min(boundingRect[0][0], boundingRect[1][0]);
+ var y = Math.min(boundingRect[0][1], boundingRect[1][1]);
+ var width = Math.abs(boundingRect[0][0] - boundingRect[1][0]);
+ var height = Math.abs(boundingRect[0][1] - boundingRect[1][1]);
// using temporary div to scroll it into view
var tempDiv = document.createElement('div');
tempDiv.style.position = 'absolute';
- tempDiv.style.left = Math.floor(x * scale) + 'px';
- tempDiv.style.top = Math.floor(y * scale) + 'px';
- tempDiv.style.width = Math.ceil(width * scale) + 'px';
- tempDiv.style.height = Math.ceil(height * scale) + 'px';
+ tempDiv.style.left = Math.floor(x) + 'px';
+ tempDiv.style.top = Math.floor(y) + 'px';
+ tempDiv.style.width = Math.ceil(width) + 'px';
+ tempDiv.style.height = Math.ceil(height) + 'px';
div.appendChild(tempDiv);
tempDiv.scrollIntoView(true);
div.removeChild(tempDiv);
@@ -934,21 +964,20 @@ var PageView = function pageView(container, content, id, pageWidth, pageHeight,
}
var textLayer = textLayerDiv ? new TextLayerBuilder(textLayerDiv) : null;
- var scale = this.scale;
- canvas.width = pageWidth * scale;
- canvas.height = pageHeight * scale;
+ var scale = this.scale, viewport = this.viewport;
+ canvas.width = viewport.width;
+ canvas.height = viewport.height;
var ctx = canvas.getContext('2d');
ctx.save();
ctx.fillStyle = 'rgb(255, 255, 255)';
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.restore();
- ctx.translate(-this.x * scale, -this.y * scale);
// Rendering area
var self = this;
- this.content.startRendering(ctx, function pageViewDrawCallback(error) {
+ function pageViewDrawCallback(error) {
if (self.loadingIconDiv) {
div.removeChild(self.loadingIconDiv);
delete self.loadingIconDiv;
@@ -957,16 +986,30 @@ var PageView = function pageView(container, content, id, pageWidth, pageHeight,
if (error)
PDFView.error('An error occurred while rendering the page.', error);
- self.stats = content.stats;
+ self.stats = pdfPage.stats;
self.updateStats();
if (self.onAfterDraw)
self.onAfterDraw();
cache.push(self);
callback();
- }, textLayer);
+ }
- setupAnnotations(this.content, this.scale);
+ var renderContext = {
+ canvasContext: ctx,
+ viewport: this.viewport,
+ textLayer: textLayer
+ };
+ this.pdfPage.render(renderContext).then(
+ function pdfPageRenderCallback() {
+ pageViewDrawCallback(null);
+ },
+ function pdfPageRenderError(error) {
+ pageViewDrawCallback(error);
+ }
+ );
+
+ setupAnnotations(this.pdfPage, this.viewport);
div.setAttribute('data-loaded', true);
};
@@ -978,7 +1021,7 @@ var PageView = function pageView(container, content, id, pageWidth, pageHeight,
};
};
-var ThumbnailView = function thumbnailView(container, page, id, pageRatio) {
+var ThumbnailView = function thumbnailView(container, pdfPage, id) {
var anchor = document.createElement('a');
anchor.href = PDFView.getAnchorUrl('#page=' + id);
anchor.onclick = function stopNivigation() {
@@ -986,18 +1029,19 @@ var ThumbnailView = function thumbnailView(container, page, id, pageRatio) {
return false;
};
- var view = page.view;
- this.width = view.width;
- this.height = view.height;
+ var viewport = pdfPage.getViewport(1);
+ var pageWidth = viewport.width;
+ var pageHeight = viewport.height;
+ var pageRatio = pageWidth / pageHeight;
this.id = id;
var maxThumbSize = 134;
- var canvasWidth = pageRatio >= 1 ? maxThumbSize :
+ var canvasWidth = this.width = pageRatio >= 1 ? maxThumbSize :
maxThumbSize * pageRatio;
- var canvasHeight = pageRatio <= 1 ? maxThumbSize :
+ var canvasHeight = this.height = pageRatio <= 1 ? maxThumbSize :
maxThumbSize / pageRatio;
- var scaleX = this.scaleX = (canvasWidth / this.width);
- var scaleY = this.scaleY = (canvasHeight / this.height);
+ var scaleX = this.scaleX = (canvasWidth / pageWidth);
+ var scaleY = this.scaleY = (canvasHeight / pageHeight);
var div = document.createElement('div');
div.id = 'thumbnailContainer' + id;
@@ -1022,15 +1066,8 @@ var ThumbnailView = function thumbnailView(container, page, id, pageRatio) {
var ctx = canvas.getContext('2d');
ctx.save();
ctx.fillStyle = 'rgb(255, 255, 255)';
- ctx.fillRect(0, 0, canvas.width, canvas.height);
+ ctx.fillRect(0, 0, canvasWidth, canvasHeight);
ctx.restore();
-
- var view = page.view;
- ctx.translate(-view.x * scaleX, -view.y * scaleY);
- div.style.width = (view.width * scaleX) + 'px';
- div.style.height = (view.height * scaleY) + 'px';
- div.style.lineHeight = (view.height * scaleY) + 'px';
-
return ctx;
}
@@ -1045,10 +1082,19 @@ var ThumbnailView = function thumbnailView(container, page, id, pageRatio) {
}
var ctx = getPageDrawContext();
- page.startRendering(ctx, function thumbnailViewDrawStartRendering() {
- callback();
- });
-
+ var drawViewport = pdfPage.getViewport(scaleX);
+ var renderContext = {
+ canvasContext: ctx,
+ viewport: drawViewport
+ };
+ pdfPage.render(renderContext).then(
+ function pdfPageRenderCallback() {
+ callback();
+ },
+ function pdfPageRenderError(error) {
+ callback();
+ }
+ );
this.hasImage = true;
};
@@ -1238,7 +1284,6 @@ window.addEventListener('load', function webViewerLoad(evt) {
var file = PDFJS.isFirefoxExtension ?
window.location.toString() : params.file || kDefaultURL;
- PDFView.open(file, 0);
if (PDFJS.isFirefoxExtension || !window.File || !window.FileReader ||
!window.FileList || !window.Blob) {
@@ -1270,6 +1315,7 @@ window.addEventListener('load', function webViewerLoad(evt) {
var sidebarScrollView = document.getElementById('sidebarScrollView');
sidebarScrollView.addEventListener('scroll', updateThumbViewArea, true);
+ PDFView.open(file, 0);
}, true);
/**
@@ -1333,14 +1379,14 @@ function updateViewarea() {
var currentPage = PDFView.pages[pageNumber - 1];
var topLeft = currentPage.getPagePoint(window.pageXOffset,
window.pageYOffset - firstPage.y - kViewerTopMargin);
- pdfOpenParams += ',' + Math.round(topLeft.x) + ',' + Math.round(topLeft.y);
+ pdfOpenParams += ',' + Math.round(topLeft[0]) + ',' + Math.round(topLeft[1]);
var store = PDFView.store;
store.set('exists', true);
store.set('page', pageNumber);
store.set('zoom', normalizedScaleValue);
- store.set('scrollLeft', Math.round(topLeft.x));
- store.set('scrollTop', Math.round(topLeft.y));
+ store.set('scrollLeft', Math.round(topLeft[0]));
+ store.set('scrollTop', Math.round(topLeft[1]));
var href = PDFView.getAnchorUrl(pdfOpenParams);
document.getElementById('viewBookmark').href = href;
}
@@ -1397,7 +1443,11 @@ window.addEventListener('change', function webViewerChange(evt) {
for (var i = 0; i < data.length; i++)
uint8Array[i] = data.charCodeAt(i);
- PDFView.load(uint8Array);
+
+ // TODO using blob instead?
+ PDFJS.getDocument(uint8Array).then(function(pdfDocument) {
+ PDFView.load(pdfDocument);
+ });
};
// Read as a binary string since "readAsArrayBuffer" is not yet