diff --git a/src/annotation.js b/src/annotation.js
index b24ec5489..6f5ae28c7 100644
--- a/src/annotation.js
+++ b/src/annotation.js
@@ -16,7 +16,7 @@
*/
/* globals Util, isDict, isName, stringToPDFString, TODO, Dict, Stream,
stringToBytes, PDFJS, isWorker, assert, NotImplementedException,
- Promise, isArray, ObjectLoader, isValidUrl */
+ Promise, isArray, ObjectLoader, isValidUrl, OperatorList */
'use strict';
@@ -162,13 +162,7 @@ var Annotation = (function AnnotationClosure() {
var promise = new Promise();
if (!this.appearance) {
- promise.resolve({
- queue: {
- fnArray: [],
- argsArray: []
- },
- dependency: {}
- });
+ promise.resolve(new OperatorList());
return promise;
}
@@ -192,19 +186,11 @@ var Annotation = (function AnnotationClosure() {
var border = data.border;
resourcesPromise.then(function(resources) {
- var listPromise = evaluator.getOperatorList(this.appearance, resources);
- listPromise.then(function(appearanceStreamData) {
- var fnArray = appearanceStreamData.queue.fnArray;
- var argsArray = appearanceStreamData.queue.argsArray;
-
- fnArray.unshift('beginAnnotation');
- argsArray.unshift([data.rect, transform, matrix]);
-
- fnArray.push('endAnnotation');
- argsArray.push([]);
-
- promise.resolve(appearanceStreamData);
- });
+ var opList = new OperatorList();
+ opList.addOp('beginAnnotation', [data.rect, transform, matrix]);
+ evaluator.getOperatorList(this.appearance, resources, opList);
+ opList.addOp('endAnnotation', []);
+ promise.resolve(opList);
}.bind(this));
return promise;
@@ -284,7 +270,7 @@ var Annotation = (function AnnotationClosure() {
};
Annotation.appendToOperatorList = function Annotation_appendToOperatorList(
- annotations, pageQueue, pdfManager, dependencies, partialEvaluator) {
+ annotations, opList, pdfManager, partialEvaluator) {
function reject(e) {
annotationsReadyPromise.reject(e);
@@ -296,22 +282,13 @@ var Annotation = (function AnnotationClosure() {
for (var i = 0, n = annotations.length; i < n; ++i) {
annotationPromises.push(annotations[i].getOperatorList(partialEvaluator));
}
-
Promise.all(annotationPromises).then(function(datas) {
- var fnArray = pageQueue.fnArray;
- var argsArray = pageQueue.argsArray;
- fnArray.push('beginAnnotations');
- argsArray.push([]);
+ opList.addOp('beginAnnotations', []);
for (var i = 0, n = datas.length; i < n; ++i) {
- var annotationData = datas[i];
- var annotationQueue = annotationData.queue;
- Util.concatenateToArray(fnArray, annotationQueue.fnArray);
- Util.concatenateToArray(argsArray, annotationQueue.argsArray);
- Util.extendObj(dependencies, annotationData.dependencies);
+ var annotOpList = datas[i];
+ opList.addOpList(annotOpList);
}
- fnArray.push('endAnnotations');
- argsArray.push([]);
-
+ opList.addOp('endAnnotations', []);
annotationsReadyPromise.resolve();
}, reject);
@@ -458,8 +435,8 @@ var TextWidgetAnnotation = (function TextWidgetAnnotationClosure() {
getOperatorList: function TextWidgetAnnotation_getOperatorList(evaluator) {
-
var promise = new Promise();
+ var opList = new OperatorList();
var data = this.data;
// Even if there is an appearance stream, ignore it. This is the
@@ -467,65 +444,44 @@ var TextWidgetAnnotation = (function TextWidgetAnnotationClosure() {
var defaultAppearance = data.defaultAppearance;
if (!defaultAppearance) {
- promise.resolve({
- queue: {
- fnArray: [],
- argsArray: []
- },
- dependency: {}
- });
+ promise.resolve(opList);
return promise;
}
// Include any font resources found in the default appearance
var stream = new Stream(stringToBytes(defaultAppearance));
- var listPromise = evaluator.getOperatorList(stream, this.fieldResources);
- listPromise.then(function(appearanceStreamData) {
- var appearanceFnArray = appearanceStreamData.queue.fnArray;
- var appearanceArgsArray = appearanceStreamData.queue.argsArray;
- var fnArray = [];
- var argsArray = [];
+ evaluator.getOperatorList(stream, this.fieldResources, opList);
+ var appearanceFnArray = opList.fnArray;
+ var appearanceArgsArray = opList.argsArray;
+ var fnArray = [];
+ var argsArray = [];
- // TODO(mack): Add support for stroke color
- data.rgb = [0, 0, 0];
- for (var i = 0, n = fnArray.length; i < n; ++i) {
- var fnName = appearanceFnArray[i];
- var args = appearanceArgsArray[i];
- if (fnName === 'dependency') {
- var dependency = args[i];
- if (dependency.indexOf('g_font_') === 0) {
- data.fontRefName = dependency;
- }
- fnArray.push(fnName);
- argsArray.push(args);
- } else if (fnName === 'setFont') {
- data.fontRefName = args[0];
- var size = args[1];
- if (size < 0) {
- data.fontDirection = -1;
- data.fontSize = -size;
- } else {
- data.fontDirection = 1;
- data.fontSize = size;
- }
- } else if (fnName === 'setFillRGBColor') {
- data.rgb = args;
- } else if (fnName === 'setFillGray') {
- var rgbValue = args[0] * 255;
- data.rgb = [rgbValue, rgbValue, rgbValue];
+ // TODO(mack): Add support for stroke color
+ data.rgb = [0, 0, 0];
+ // TODO THIS DOESN'T MAKE ANY SENSE SINCE THE fnArray IS EMPTY!
+ for (var i = 0, n = fnArray.length; i < n; ++i) {
+ var fnName = appearanceFnArray[i];
+ var args = appearanceArgsArray[i];
+
+ if (fnName === 'setFont') {
+ data.fontRefName = args[0];
+ var size = args[1];
+ if (size < 0) {
+ data.fontDirection = -1;
+ data.fontSize = -size;
+ } else {
+ data.fontDirection = 1;
+ data.fontSize = size;
}
+ } else if (fnName === 'setFillRGBColor') {
+ data.rgb = args;
+ } else if (fnName === 'setFillGray') {
+ var rgbValue = args[0] * 255;
+ data.rgb = [rgbValue, rgbValue, rgbValue];
}
- promise.resolve({
- queue: {
- fnArray: fnArray,
- argsArray: argsArray
- },
- dependency: {}
- });
-
- });
-
+ }
+ promise.resolve(opList);
return promise;
}
});
@@ -557,13 +513,7 @@ var TextAnnotation = (function TextAnnotationClosure() {
getOperatorList: function TextAnnotation_getOperatorList(evaluator) {
var promise = new Promise();
- promise.resolve({
- queue: {
- fnArray: [],
- argsArray: []
- },
- dependency: {}
- });
+ promise.resolve(new OperatorList());
return promise;
},
diff --git a/src/api.js b/src/api.js
index c1ab09d95..0f571a0f1 100644
--- a/src/api.js
+++ b/src/api.js
@@ -17,7 +17,7 @@
/* globals CanvasGraphics, combineUrl, createScratchCanvas, error, ErrorFont,
Font, FontLoader, globalScope, info, isArrayBuffer, loadJpegStream,
MessageHandler, PDFJS, PDFObjects, Promise, StatTimer, warn,
- WorkerMessageHandler, PasswordResponses */
+ WorkerMessageHandler, PasswordResponses, Util */
'use strict';
@@ -222,6 +222,7 @@ var PDFPageProxy = (function PDFPageProxyClosure() {
this.objs = new PDFObjects();
this.renderInProgress = false;
this.cleanupAfterRender = false;
+ this.renderTasks = [];
}
PDFPageProxy.prototype = {
/**
@@ -289,20 +290,24 @@ var PDFPageProxy = (function PDFPageProxyClosure() {
* rendering call the function that is the
* first argument to the callback.
* }.
- * @return {Promise} A promise that is resolved when the page finishes
- * rendering.
+ * @return {RenderTask} An extended promise that is resolved when the page
+ * finishes rendering (see RenderTask).
*/
render: function PDFPageProxy_render(params) {
this.renderInProgress = true;
-
- 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.destroyed = false;
+ this.operatorList = {
+ fnArray: [],
+ argsArray: [],
+ lastChunk: false
+ };
this.stats.time('Page Request');
this.transport.messageHandler.send('RenderPageRequest', {
@@ -310,128 +315,48 @@ var PDFPageProxy = (function PDFPageProxyClosure() {
});
}
+ var internalRenderTask = new InternalRenderTask(complete, params,
+ this.objs, this.commonObjs,
+ this.operatorList, this.pageNumber);
+ this.renderTasks.push(internalRenderTask);
+ var renderTask = new RenderTask(internalRenderTask);
+
var self = this;
- function complete(error) {
- self.renderInProgress = false;
- if (self.destroyed || self.cleanupAfterRender) {
- delete self.displayReadyPromise;
- delete self.operatorList;
- self.objs.clear();
- }
-
- if (error)
- promise.reject(error);
- else
- promise.resolve();
- }
- var continueCallback = params.continueCallback;
-
- // Once the operatorList and fonts are loaded, do the actual rendering.
this.displayReadyPromise.then(
- function pageDisplayReadyPromise() {
+ function pageDisplayReadyPromise(transparency) {
if (self.destroyed) {
complete();
return;
}
-
- var gfx = new CanvasGraphics(params.canvasContext, this.commonObjs,
- this.objs, params.textLayer, params.imageLayer);
- try {
- this.display(gfx, params.viewport, complete, continueCallback);
- } catch (e) {
- complete(e);
- }
- }.bind(this),
+ stats.time('Rendering');
+ internalRenderTask.initalizeGraphics(transparency);
+ internalRenderTask.operatorListChanged();
+ },
function pageDisplayReadPromiseError(reason) {
complete(reason);
}
);
- return promise;
- },
- /**
- * For internal use only.
- */
- startRenderingFromOperatorList:
- function PDFPageProxy_startRenderingFromOperatorList(operatorList,
- fonts) {
- var self = this;
- this.operatorList = operatorList;
+ function complete(error) {
+ var i = self.renderTasks.indexOf(internalRenderTask);
+ if (i >= 0) {
+ self.renderTasks.splice(i, 1);
+ }
- this.ensureFonts(fonts,
- function pageStartRenderingFromOperatorListEnsureFonts() {
- self.displayReadyPromise.resolve();
+ if (self.renderTasks.length === 0 &&
+ (self.destroyed || self.cleanupAfterRender)) {
+ self._destroy();
}
- );
- },
- /**
- * For internal use only.
- */
- ensureFonts: function PDFPageProxy_ensureFonts(fonts, callback) {
- this.stats.time('Font Loading');
- // Convert the font names to the corresponding font obj.
- var fontObjs = [];
- for (var i = 0, ii = fonts.length; i < ii; i++) {
- var obj = this.commonObjs.getData(fonts[i]);
- if (obj.error) {
- warn('Error during font loading: ' + obj.error);
- continue;
+ if (error) {
+ renderTask.reject(error);
+ } else {
+ renderTask.resolve();
}
- if (!obj.coded) {
- this.transport.embeddedFontsUsed = true;
- }
- fontObjs.push(obj);
+ stats.timeEnd('Rendering');
+ stats.timeEnd('Overall');
}
- // Load all the fonts
- FontLoader.bind(
- fontObjs,
- function pageEnsureFontsFontObjs(fontObjs) {
- this.stats.timeEnd('Font Loading');
-
- callback.call(this);
- }.bind(this)
- );
- },
- /**
- * For internal use only.
- */
- display: function PDFPageProxy_display(gfx, viewport, callback,
- continueCallback) {
- var stats = this.stats;
- stats.time('Rendering');
-
- var operatorList = this.operatorList;
- gfx.beginDrawing(viewport, operatorList.transparency);
-
- var startIdx = 0;
- var length = operatorList.fnArray.length;
- var stepper = null;
- if (PDFJS.pdfBug && 'StepperManager' in globalScope &&
- globalScope['StepperManager'].enabled) {
- stepper = globalScope['StepperManager'].create(this.pageNumber - 1);
- stepper.init(operatorList);
- stepper.nextBreakPoint = stepper.getNextBreakPoint();
- }
-
- var continueWrapper;
- if (continueCallback)
- continueWrapper = function() { continueCallback(next); };
- else
- continueWrapper = next;
-
- var self = this;
- function next() {
- startIdx = gfx.executeOperatorList(operatorList, startIdx,
- continueWrapper, stepper);
- if (startIdx == length) {
- gfx.endDrawing();
- stats.timeEnd('Rendering');
- stats.timeEnd('Overall');
- if (callback) callback();
- }
- }
- continueWrapper();
+ return renderTask;
},
/**
* @return {Promise} That is resolved with the a {string} that is the text
@@ -466,10 +391,38 @@ var PDFPageProxy = (function PDFPageProxyClosure() {
destroy: function PDFPageProxy_destroy() {
this.destroyed = true;
- if (!this.renderInProgress) {
- delete this.operatorList;
- delete this.displayReadyPromise;
- this.objs.clear();
+ if (this.renderTasks.length === 0) {
+ this._destroy();
+ }
+ },
+ /**
+ * For internal use only. Does the actual cleanup.
+ */
+ _destroy: function PDFPageProxy__destroy() {
+ delete this.operatorList;
+ delete this.displayReadyPromise;
+ this.objs.clear();
+ },
+ /**
+ * For internal use only.
+ */
+ _startRenderPage: function PDFPageProxy_startRenderPage(transparency) {
+ this.displayReadyPromise.resolve(transparency);
+ },
+ /**
+ * For internal use only.
+ */
+ _renderPageChunk: function PDFPageProxy_renderPageChunk(operatorListChunk) {
+ // Add the new chunk to the current operator list.
+ Util.concatenateToArray(this.operatorList.fnArray,
+ operatorListChunk.fnArray);
+ Util.concatenateToArray(this.operatorList.argsArray,
+ operatorListChunk.argsArray);
+ this.operatorList.lastChunk = operatorListChunk.lastChunk;
+
+ // Notify all the rendering tasks there are more operators to be consumed.
+ for (var i = 0; i < this.renderTasks.length; i++) {
+ this.renderTasks[i].operatorListChanged();
}
}
};
@@ -644,12 +597,17 @@ var WorkerTransport = (function WorkerTransportClosure() {
promise.resolve(annotations);
}, this);
- messageHandler.on('RenderPage', function transportRender(data) {
+ messageHandler.on('StartRenderPage', function transportRender(data) {
var page = this.pageCache[data.pageIndex];
- var depFonts = data.depFonts;
page.stats.timeEnd('Page Request');
- page.startRenderingFromOperatorList(data.operatorList, depFonts);
+ page._startRenderPage(data.transparency);
+ }, this);
+
+ messageHandler.on('RenderPageChunk', function transportRender(data) {
+ var page = this.pageCache[data.pageIndex];
+
+ page._renderPageChunk(data.operatorList);
}, this);
messageHandler.on('commonobj', function transportObj(data) {
@@ -662,14 +620,22 @@ var WorkerTransport = (function WorkerTransportClosure() {
case 'Font':
var exportedData = data[2];
- // 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;
- if ('error' in exportedData)
+ if ('error' in exportedData) {
font = new ErrorFont(exportedData.error);
- else
+ warn('Error during font loading: ' + font.error);
+ this.commonObjs.resolve(id, font);
+ break;
+ } else {
font = new Font(exportedData);
- this.commonObjs.resolve(id, font);
+ }
+
+ FontLoader.bind(
+ [font],
+ function fontReady(fontObjs) {
+ this.commonObjs.resolve(id, font);
+ }.bind(this)
+ );
break;
default:
error('Got unknown common object type ' + type);
@@ -814,3 +780,129 @@ var WorkerTransport = (function WorkerTransportClosure() {
return WorkerTransport;
})();
+
+/**
+ * RenderTask is basically a promise but adds a cancel function to terminate it.
+ */
+var RenderTask = (function RenderTaskClosure() {
+ function RenderTask(internalRenderTask) {
+ this.internalRenderTask = internalRenderTask;
+ Promise.call(this);
+ }
+
+ RenderTask.prototype = Object.create(Promise.prototype);
+
+ /**
+ * Cancel the rendering task. If the task is curently rendering it will not be
+ * cancelled until graphics pauses with a timeout. The promise that this
+ * object extends will resolved when cancelled.
+ */
+ RenderTask.prototype.cancel = function RenderTask_cancel() {
+ this.internalRenderTask.cancel();
+ };
+
+ return RenderTask;
+})();
+
+var InternalRenderTask = (function InternalRenderTaskClosure() {
+
+ function InternalRenderTask(callback, params, objs, commonObjs, operatorList,
+ pageNumber) {
+ this.callback = callback;
+ this.params = params;
+ this.objs = objs;
+ this.commonObjs = commonObjs;
+ this.operatorListIdx = null;
+ this.operatorList = operatorList;
+ this.pageNumber = pageNumber;
+ this.running = false;
+ this.graphicsReadyCallback = null;
+ this.graphicsReady = false;
+ this.cancelled = false;
+ }
+
+ InternalRenderTask.prototype = {
+
+ initalizeGraphics:
+ function InternalRenderTask_initalizeGraphics(transparency) {
+
+ if (this.cancelled) {
+ return;
+ }
+ if (PDFJS.pdfBug && 'StepperManager' in globalScope &&
+ globalScope.StepperManager.enabled) {
+ this.stepper = globalScope.StepperManager.create(this.pageNumber - 1);
+ this.stepper.init(this.operatorList);
+ this.stepper.nextBreakPoint = this.stepper.getNextBreakPoint();
+ }
+
+ var params = this.params;
+ this.gfx = new CanvasGraphics(params.canvasContext, this.commonObjs,
+ this.objs, params.textLayer,
+ params.imageLayer);
+
+ this.gfx.beginDrawing(params.viewport, transparency);
+ this.operatorListIdx = 0;
+ this.graphicsReady = true;
+ if (this.graphicsReadyCallback) {
+ this.graphicsReadyCallback();
+ }
+ },
+
+ cancel: function InternalRenderTask_cancel() {
+ this.running = false;
+ this.cancelled = true;
+ this.callback();
+ },
+
+ operatorListChanged: function InternalRenderTask_operatorListChanged() {
+ if (!this.graphicsReady) {
+ if (!this.graphicsReadyCallback) {
+ this.graphicsReadyCallback = this._continue.bind(this);
+ }
+ return;
+ }
+
+ if (this.stepper) {
+ this.stepper.updateOperatorList(this.operatorList);
+ }
+
+ if (this.running) {
+ return;
+ }
+ this._continue();
+ },
+
+ _continue: function InternalRenderTask__continue() {
+ this.running = true;
+ if (this.cancelled) {
+ return;
+ }
+ if (this.params.continueCallback) {
+ this.params.continueCallback(this._next.bind(this));
+ } else {
+ this._next();
+ }
+ },
+
+ _next: function InternalRenderTask__next() {
+ if (this.cancelled) {
+ return;
+ }
+ this.operatorListIdx = this.gfx.executeOperatorList(this.operatorList,
+ this.operatorListIdx,
+ this._continue.bind(this),
+ this.stepper);
+ if (this.operatorListIdx === this.operatorList.fnArray.length) {
+ this.running = false;
+ if (this.operatorList.lastChunk) {
+ this.gfx.endDrawing();
+ this.callback();
+ }
+ }
+ }
+
+ };
+
+ return InternalRenderTask;
+})();
diff --git a/src/canvas.js b/src/canvas.js
index 0b1bed490..d51ed0308 100644
--- a/src/canvas.js
+++ b/src/canvas.js
@@ -670,7 +670,7 @@ var CanvasGraphics = (function CanvasGraphicsClosure() {
this.setFlatness(value);
break;
case 'Font':
- this.setFont(state[1], state[2]);
+ this.setFont(value[0], value[1]);
break;
case 'CA':
this.current.strokeAlpha = state[1];
diff --git a/src/core.js b/src/core.js
index 4b005361c..3e1ea9339 100644
--- a/src/core.js
+++ b/src/core.js
@@ -18,7 +18,8 @@
isArrayBuffer, isDict, isName, isStream, isString, Lexer,
Linearization, NullStream, PartialEvaluator, shadow, Stream,
StreamsSequenceStream, stringToPDFString, TODO, Util, warn, XRef,
- MissingDataException, Promise, Annotation, ObjectLoader */
+ MissingDataException, Promise, Annotation, ObjectLoader, OperatorList
+ */
'use strict';
@@ -183,33 +184,35 @@ var Page = (function PageClosure() {
dataPromises.then(function(data) {
var contentStream = data[0];
- partialEvaluator.getOperatorList(contentStream, self.resources).then(
- function(data) {
- pageListPromise.resolve(data);
- },
- reject
- );
+
+ var opList = new OperatorList(handler, self.pageIndex);
+
+ handler.send('StartRenderPage', {
+ transparency: partialEvaluator.hasBlendModes(self.resources),
+ pageIndex: self.pageIndex
+ });
+ partialEvaluator.getOperatorList(contentStream, self.resources, opList);
+ pageListPromise.resolve(opList);
});
var annotationsPromise = pdfManager.ensure(this, 'annotations');
Promise.all([pageListPromise, annotationsPromise]).then(function(datas) {
- var pageData = datas[0];
- var pageQueue = pageData.queue;
+ var pageOpList = datas[0];
var annotations = datas[1];
if (annotations.length === 0) {
- PartialEvaluator.optimizeQueue(pageQueue);
- promise.resolve(pageData);
+ PartialEvaluator.optimizeQueue(pageOpList);
+ pageOpList.flush(true);
+ promise.resolve(pageOpList);
return;
}
- var dependencies = pageData.dependencies;
var annotationsReadyPromise = Annotation.appendToOperatorList(
- annotations, pageQueue, pdfManager, dependencies, partialEvaluator);
+ annotations, pageOpList, pdfManager, partialEvaluator);
annotationsReadyPromise.then(function () {
- PartialEvaluator.optimizeQueue(pageQueue);
-
- promise.resolve(pageData);
+ PartialEvaluator.optimizeQueue(pageOpList);
+ pageOpList.flush(true);
+ promise.resolve(pageOpList);
}, reject);
}, reject);
@@ -244,12 +247,9 @@ var Page = (function PageClosure() {
self.pageIndex, 'p' + self.pageIndex + '_',
self.idCounters);
- partialEvaluator.getTextContent(
- contentStream, self.resources).then(function(bidiTexts) {
- textContentPromise.resolve({
- bidiTexts: bidiTexts
- });
- });
+ var bidiTexts = partialEvaluator.getTextContent(contentStream,
+ self.resources);
+ textContentPromise.resolve(bidiTexts);
});
return textContentPromise;
diff --git a/src/evaluator.js b/src/evaluator.js
index b3de3b102..7d141eac5 100644
--- a/src/evaluator.js
+++ b/src/evaluator.js
@@ -153,24 +153,51 @@ var PartialEvaluator = (function PartialEvaluatorClosure() {
var TILING_PATTERN = 1, SHADING_PATTERN = 2;
- function createOperatorList(fnArray, argsArray, dependencies) {
- return {
- queue: {
- fnArray: fnArray || [],
- argsArray: argsArray || []
- },
- dependencies: dependencies || {}
- };
- }
-
PartialEvaluator.prototype = {
+ hasBlendModes: function PartialEvaluator_hasBlendModes(resources) {
+ if (!isDict(resources)) {
+ return false;
+ }
+
+ var nodes = [resources];
+ while (nodes.length) {
+ var node = nodes.shift();
+ // First check the current resources for blend modes.
+ var graphicStates = node.get('ExtGState');
+ if (isDict(graphicStates)) {
+ graphicStates = graphicStates.getAll();
+ for (var key in graphicStates) {
+ var graphicState = graphicStates[key];
+ var bm = graphicState['BM'];
+ if (isName(bm) && bm.name !== 'Normal') {
+ return true;
+ }
+ }
+ }
+ // Descend into the XObjects to look for more resources and blend modes.
+ var xObjects = node.get('XObject');
+ if (!isDict(xObjects)) {
+ continue;
+ }
+ xObjects = xObjects.getAll();
+ for (var key in xObjects) {
+ var xObject = xObjects[key];
+ if (!isStream(xObject)) {
+ continue;
+ }
+ var xResources = xObject.dict.get('Resources');
+ if (isDict(xResources)) {
+ nodes.push(xResources);
+ }
+ }
+ }
+ return false;
+ },
buildFormXObject: function PartialEvaluator_buildFormXObject(resources,
- xobj, smask) {
+ xobj, smask,
+ operatorList) {
var self = this;
- var promise = new Promise();
- var fnArray = [];
- var argsArray = [];
var matrix = xobj.dict.get('Matrix');
var bbox = xobj.dict.get('BBox');
@@ -191,44 +218,22 @@ var PartialEvaluator = (function PartialEvaluatorClosure() {
// There is also a group colorspace, but since we put everything in
// RGB I'm not sure we need it.
}
- fnArray.push('beginGroup');
- argsArray.push([groupOptions]);
+ operatorList.addOp('beginGroup', [groupOptions]);
}
- fnArray.push('paintFormXObjectBegin');
- argsArray.push([matrix, bbox]);
+ operatorList.addOp('paintFormXObjectBegin', [matrix, bbox]);
- // Pass in the current `queue` object. That means the `fnArray`
- // and the `argsArray` in this scope is reused and new commands
- // are added to them.
- var opListPromise = this.getOperatorList(xobj,
- xobj.dict.get('Resources') || resources);
- opListPromise.then(function(data) {
- var queue = data.queue;
- var dependencies = data.dependencies;
- Util.prependToArray(queue.fnArray, fnArray);
- Util.prependToArray(queue.argsArray, argsArray);
- self.insertDependencies(queue, dependencies);
+ this.getOperatorList(xobj, xobj.dict.get('Resources') || resources,
+ operatorList);
+ operatorList.addOp('paintFormXObjectEnd', []);
- queue.fnArray.push('paintFormXObjectEnd');
- queue.argsArray.push([]);
-
- if (group) {
- queue.fnArray.push('endGroup');
- queue.argsArray.push([groupOptions]);
- }
-
- promise.resolve({
- queue: queue,
- dependencies: dependencies
- });
- });
-
- return promise;
+ if (group) {
+ operatorList.addOp('endGroup', [groupOptions]);
+ }
},
buildPaintImageXObject: function PartialEvaluator_buildPaintImageXObject(
- resources, image, inline) {
+ resources, image, inline, operatorList) {
var self = this;
var dict = image.dict;
var w = dict.get('Width', 'W');
@@ -236,14 +241,9 @@ var PartialEvaluator = (function PartialEvaluatorClosure() {
if (PDFJS.maxImageSize !== -1 && w * h > PDFJS.maxImageSize) {
warn('Image exceeded maximum allowed size and was removed.');
- return null;
+ return;
}
- var dependencies = {};
- var retData = {
- dependencies: dependencies
- };
-
var imageMask = dict.get('ImageMask', 'IM') || false;
if (imageMask) {
// This depends on a tmpCanvas beeing filled with the
@@ -259,10 +259,11 @@ var PartialEvaluator = (function PartialEvaluatorClosure() {
var decode = dict.get('Decode', 'D');
var inverseDecode = !!decode && decode[0] > 0;
- retData.fn = 'paintImageMaskXObject';
- retData.args = [PDFImage.createMask(imgArray, width, height,
- inverseDecode)];
- return retData;
+ operatorList.addOp('paintImageMaskXObject',
+ [PDFImage.createMask(imgArray, width, height,
+ inverseDecode)]
+ );
+ return;
}
var softMask = dict.get('SMask', 'SM') || false;
@@ -276,64 +277,53 @@ var PartialEvaluator = (function PartialEvaluatorClosure() {
var imageObj = new PDFImage(this.xref, resources, image,
inline, null, null);
var imgData = imageObj.getImageData();
- retData.fn = 'paintInlineImageXObject';
- retData.args = [imgData];
- return retData;
+ operatorList.addOp('paintInlineImageXObject', [imgData]);
+ return;
}
// If there is no imageMask, create the PDFImage and a lot
// of image processing can be done here.
var uniquePrefix = this.uniquePrefix || '';
var objId = 'img_' + uniquePrefix + (++this.idCounters.obj);
- dependencies[objId] = true;
- retData.args = [objId, w, h];
+ operatorList.addDependency(objId);
+ var args = [objId, w, h];
if (!softMask && !mask && image instanceof JpegStream &&
image.isNativelySupported(this.xref, resources)) {
// These JPEGs don't need any more processing so we can just send it.
- retData.fn = 'paintJpegXObject';
+ operatorList.addOp('paintJpegXObject', args);
this.handler.send(
'obj', [objId, this.pageIndex, 'JpegStream', image.getIR()]);
- return retData;
+ return;
}
- retData.fn = 'paintImageXObject';
PDFImage.buildImage(function(imageObj) {
var imgData = imageObj.getImageData();
self.handler.send('obj', [objId, self.pageIndex, 'Image', imgData]);
}, self.handler, self.xref, resources, image, inline);
- return retData;
+ operatorList.addOp('paintImageXObject', args);
},
handleTilingType: function PartialEvaluator_handleTilingType(
- fn, args, resources, pattern, patternDict) {
- var self = this;
+ fn, args, resources, pattern, patternDict,
+ operatorList) {
// Create an IR of the pattern code.
- var promise = new Promise();
- var opListPromise = this.getOperatorList(pattern,
- patternDict.get('Resources') || resources);
- opListPromise.then(function(data) {
- var opListData = createOperatorList([], [], data.dependencies);
- var queue = opListData.queue;
-
- // Add the dependencies that are required to execute the
- // operatorList.
- self.insertDependencies(queue, data.dependencies);
- queue.fnArray.push(fn);
- queue.argsArray.push(
- TilingPattern.getIR(data.queue, patternDict, args));
- promise.resolve(opListData);
- });
-
- return promise;
+ var tilingOpList = this.getOperatorList(pattern,
+ patternDict.get('Resources') || resources);
+ // Add the dependencies to the parent operator list so they are resolved
+ // before sub operator list is executed synchronously.
+ operatorList.addDependencies(tilingOpList.dependencies);
+ operatorList.addOp(fn, TilingPattern.getIR({
+ fnArray: tilingOpList.fnArray,
+ argsArray: tilingOpList.argsArray
+ }, patternDict, args));
},
handleSetFont: function PartialEvaluator_handleSetFont(
- resources, fontArgs, font) {
+ resources, fontArgs, fontRef, operatorList) {
- var promise = new Promise();
// TODO(mack): Not needed?
var fontName;
if (fontArgs) {
@@ -341,69 +331,27 @@ var PartialEvaluator = (function PartialEvaluatorClosure() {
fontName = fontArgs[0].name;
}
var self = this;
- var fontPromise = this.loadFont(fontName, font, this.xref, resources);
- fontPromise.then(function(data) {
- var font = data.font;
- var loadedName = font.loadedName;
- if (!font.sent) {
- var fontData = font.translated.exportData();
+ var font = this.loadFont(fontName, fontRef, this.xref, resources,
+ operatorList);
+ var loadedName = font.loadedName;
+ if (!font.sent) {
+ var fontData = font.translated.exportData();
- self.handler.send('commonobj', [
- loadedName,
- 'Font',
- fontData
- ]);
- font.sent = true;
- }
-
- // Ensure the font is ready before the font is set
- // and later on used for drawing.
- // OPTIMIZE: This should get insert to the operatorList only once per
- // page.
- var fnArray = [];
- var argsArray = [];
- var queue = {
- fnArray: fnArray,
- argsArray: argsArray
- };
- var dependencies = data.dependencies;
- dependencies[loadedName] = true;
- self.insertDependencies(queue, dependencies);
- if (fontArgs) {
- fontArgs[0] = loadedName;
- fnArray.push('setFont');
- argsArray.push(fontArgs);
- }
- promise.resolve({
- loadedName: loadedName,
- queue: queue,
- dependencies: dependencies
- });
- });
- return promise;
- },
-
- insertDependencies: function PartialEvaluator_insertDependencies(
- queue, dependencies) {
-
- var fnArray = queue.fnArray;
- var argsArray = queue.argsArray;
- var depList = Object.keys(dependencies);
- if (depList.length) {
- fnArray.push('dependency');
- argsArray.push(depList);
+ self.handler.send('commonobj', [
+ loadedName,
+ 'Font',
+ fontData
+ ]);
+ font.sent = true;
}
+
+ return loadedName;
},
- setGState: function PartialEvaluator_setGState(resources, gState) {
+ setGState: function PartialEvaluator_setGState(resources, gState,
+ operatorList) {
var self = this;
- var opListData = createOperatorList();
- var queue = opListData.queue;
- var fnArray = queue.fnArray;
- var argsArray = queue.argsArray;
- var dependencies = opListData.dependencies;
-
// TODO(mack): This should be rewritten so that this function returns
// what should be added to the queue during each iteration
function setGStateForKey(gStateObj, key, value) {
@@ -422,21 +370,12 @@ var PartialEvaluator = (function PartialEvaluatorClosure() {
gStateObj.push([key, value]);
break;
case 'Font':
- var promise = new Promise();
- self.handleSetFont(resources, null, value[0]).then(function(data) {
- var gState = ['Font', data.loadedName, value[1]];
- promise.resolve({
- gState: gState,
- queue: data.queue,
- dependencies: data.dependencies
- });
- });
- gStateObj.push(['promise', promise]);
+ var loadedName = self.handleSetFont(resources, null, value[0],
+ operatorList);
+ operatorList.addDependency(loadedName);
+ gStateObj.push([key, [loadedName, value[1]]]);
break;
case 'BM':
- if (!isName(value) || value.name !== 'Normal') {
- queue.transparency = true;
- }
gStateObj.push([key, value]);
break;
case 'SMask':
@@ -477,47 +416,18 @@ var PartialEvaluator = (function PartialEvaluatorClosure() {
setGStateForKey(gStateObj, key, value);
}
- var promises = [];
- var indices = [];
- for (var i = 0, n = gStateObj.length; i < n; ++i) {
- var value = gStateObj[i];
- if (value[0] === 'promise') {
- promises.push(value[1]);
- indices.push(i);
- }
- }
-
- var promise = new Promise();
- Promise.all(promises).then(function(datas) {
- for (var i = 0, n = datas.length; i < n; ++i) {
- var data = datas[i];
- var index = indices[i];
- gStateObj[index] = data.gState;
- var subQueue = data.queue;
- Util.concatenateToArray(fnArray, subQueue.fnArray);
- Util.concatenateToArray(argsArray, subQueue.argsArray);
- queue.transparency = subQueue.transparency || queue.transparency;
- Util.extendObj(dependencies, data.dependencies);
- }
- fnArray.push('setGState');
- argsArray.push([gStateObj]);
- promise.resolve(opListData);
- });
-
- return promise;
+ operatorList.addOp('setGState', [gStateObj]);
},
loadFont: function PartialEvaluator_loadFont(fontName, font, xref,
- resources) {
- function errorFont(promise) {
- promise.resolve({
- font: {
- translated: new ErrorFont('Font ' + fontName + ' is not available'),
- loadedName: 'g_font_error'
- },
- dependencies: {}
- });
- return promise;
+ resources,
+ parentOperatorList) {
+
+ function errorFont() {
+ return {
+ translated: new ErrorFont('Font ' + fontName + ' is not available'),
+ loadedName: 'g_font_error'
+ };
}
var fontRef;
@@ -530,20 +440,19 @@ var PartialEvaluator = (function PartialEvaluatorClosure() {
fontRef = fontRes.getRaw(fontName);
} else {
warn('fontRes not available');
- return errorFont(new Promise());
+ return errorFont();
}
}
if (this.fontCache.has(fontRef)) {
return this.fontCache.get(fontRef);
}
- var promise = new Promise();
- this.fontCache.put(fontRef, promise);
font = xref.fetchIfRef(fontRef);
if (!isDict(font)) {
- return errorFont(promise);
+ return errorFont();
}
+ this.fontCache.put(fontRef, font);
// keep track of each font we translated so the caller can
// load them asynchronously before calling display on a page
@@ -562,55 +471,37 @@ var PartialEvaluator = (function PartialEvaluatorClosure() {
if (font.translated.loadCharProcs) {
var charProcs = font.get('CharProcs').getAll();
var fontResources = font.get('Resources') || resources;
- var opListPromises = [];
var charProcKeys = Object.keys(charProcs);
+ var charProcOperatorList = {};
for (var i = 0, n = charProcKeys.length; i < n; ++i) {
var key = charProcKeys[i];
var glyphStream = charProcs[key];
- opListPromises.push(
- this.getOperatorList(glyphStream, fontResources));
- }
- Promise.all(opListPromises).then(function(datas) {
- var charProcOperatorList = {};
- var dependencies = {};
- for (var i = 0, n = charProcKeys.length; i < n; ++i) {
- var key = charProcKeys[i];
- var data = datas[i];
- charProcOperatorList[key] = data.queue;
- Util.extendObj(dependencies, data.dependencies);
+ var operatorList = this.getOperatorList(glyphStream, fontResources);
+ charProcOperatorList[key] = operatorList.getIR();
+ if (!parentOperatorList) {
+ continue;
}
- font.translated.charProcOperatorList = charProcOperatorList;
- font.loaded = true;
- promise.resolve({
- font: font,
- dependencies: dependencies
- });
- }.bind(this));
+ // Add the dependencies to the parent operator list so they are
+ // resolved before sub operator list is executed synchronously.
+ parentOperatorList.addDependencies(charProcOperatorList.dependencies);
+ }
+ font.translated.charProcOperatorList = charProcOperatorList;
+ font.loaded = true;
} else {
font.loaded = true;
- promise.resolve({
- font: font,
- dependencies: {}
- });
}
- return promise;
+ return font;
},
getOperatorList: function PartialEvaluator_getOperatorList(stream,
- resources) {
+ resources,
+ operatorList) {
var self = this;
var xref = this.xref;
var handler = this.handler;
- var fnArray = [];
- var argsArray = [];
- var queue = {
- transparency: false,
- fnArray: fnArray,
- argsArray: argsArray
- };
- var dependencies = {};
+ operatorList = operatorList || new OperatorList();
resources = resources || new Dict();
var xobjs = resources.get('XObject') || new Dict();
@@ -621,6 +512,7 @@ var PartialEvaluator = (function PartialEvaluatorClosure() {
var promise = new Promise();
var args = [];
+ nextOp:
while (true) {
var obj = parser.getObj();
@@ -677,10 +569,10 @@ var PartialEvaluator = (function PartialEvaluatorClosure() {
var typeNum = dict.get('PatternType');
if (typeNum == TILING_PATTERN) {
- var patternPromise = self.handleTilingType(
- fn, args, resources, pattern, dict);
- fn = 'promise';
- args = [patternPromise];
+ self.handleTilingType(fn, args, resources, pattern, dict,
+ operatorList);
+ args = [];
+ continue;
} else if (typeNum == SHADING_PATTERN) {
var shading = dict.get('Shading');
var matrix = dict.get('Matrix');
@@ -706,37 +598,28 @@ var PartialEvaluator = (function PartialEvaluatorClosure() {
);
if ('Form' == type.name) {
- fn = 'promise';
- args = [self.buildFormXObject(resources, xobj)];
+ self.buildFormXObject(resources, xobj, null, operatorList);
+ args = [];
+ continue;
} else if ('Image' == type.name) {
- var data = self.buildPaintImageXObject(
- resources, xobj, false);
- if (!data) {
- args = [];
- continue;
- }
- Util.extendObj(dependencies, data.dependencies);
- self.insertDependencies(queue, data.dependencies);
- fn = data.fn;
- args = data.args;
+ self.buildPaintImageXObject(resources, xobj, false,
+ operatorList);
+ args = [];
+ continue;
} else {
error('Unhandled XObject subtype ' + type.name);
}
}
} else if (cmd == 'Tf') { // eagerly collect all fonts
- fn = 'promise';
- args = [self.handleSetFont(resources, args)];
+ var loadedName = self.handleSetFont(resources, args, null,
+ operatorList);
+ operatorList.addDependency(loadedName);
+ fn = 'setFont';
+ args[0] = loadedName;
} else if (cmd == 'EI') {
- var data = self.buildPaintImageXObject(
- resources, args[0], true);
- if (!data) {
- args = [];
- continue;
- }
- Util.extendObj(dependencies, data.dependencies);
- self.insertDependencies(queue, data.dependencies);
- fn = data.fn;
- args = data.args;
+ self.buildPaintImageXObject(resources, args[0], true, operatorList);
+ args = [];
+ continue;
}
switch (fn) {
@@ -768,12 +651,12 @@ var PartialEvaluator = (function PartialEvaluatorClosure() {
break;
var gState = extGState.get(dictName.name);
- fn = 'promise';
- args = [self.setGState(resources, gState)];
+ self.setGState(resources, gState, operatorList);
+ args = [];
+ continue nextOp;
} // switch
- fnArray.push(fn);
- argsArray.push(args);
+ operatorList.addOp(fn, args);
args = [];
parser.saveState();
} else if (obj !== null && obj !== undefined) {
@@ -782,163 +665,86 @@ var PartialEvaluator = (function PartialEvaluatorClosure() {
}
}
- var subQueuePromises = [];
- for (var i = 0; i < fnArray.length; ++i) {
- if (fnArray[i] === 'promise') {
- subQueuePromises.push(argsArray[i][0]);
- }
- }
- Promise.all(subQueuePromises).then(function(datas) {
- // TODO(mack): Optimize by using repositioning elements
- // in original queue rather than creating new queue
-
- for (var i = 0, n = datas.length; i < n; ++i) {
- var data = datas[i];
- var subQueue = data.queue;
- queue.transparency = subQueue.transparency || queue.transparency;
- Util.extendObj(dependencies, data.dependencies);
- }
-
- var newFnArray = [];
- var newArgsArray = [];
- var currOffset = 0;
- var subQueueIdx = 0;
- for (var i = 0, n = fnArray.length; i < n; ++i) {
- var offset = i + currOffset;
- if (fnArray[i] === 'promise') {
- var data = datas[subQueueIdx++];
- var subQueue = data.queue;
- var subQueueFnArray = subQueue.fnArray;
- var subQueueArgsArray = subQueue.argsArray;
- for (var j = 0, nn = subQueueFnArray.length; j < nn; ++j) {
- newFnArray[offset + j] = subQueueFnArray[j];
- newArgsArray[offset + j] = subQueueArgsArray[j];
- }
- currOffset += subQueueFnArray.length - 1;
- } else {
- newFnArray[offset] = fnArray[i];
- newArgsArray[offset] = argsArray[i];
- }
- }
-
- promise.resolve({
- queue: {
- fnArray: newFnArray,
- argsArray: newArgsArray,
- transparency: queue.transparency
- },
- dependencies: dependencies
- });
- });
-
- return promise;
+ return operatorList;
},
getTextContent: function PartialEvaluator_getTextContent(
- stream, resources) {
+ stream, resources, state) {
+ var bidiTexts;
var SPACE_FACTOR = 0.35;
var MULTI_SPACE_FACTOR = 1.5;
+
+ if (!state) {
+ bidiTexts = [];
+ state = {
+ bidiTexts: bidiTexts
+ };
+ } else {
+ bidiTexts = state.bidiTexts;
+ }
+
var self = this;
+ var xref = this.xref;
- var statePromise = new Promise();
-
- function handleSetFont(fontName, fontRef, resources) {
- var promise = new Promise();
- self.loadFont(fontName, fontRef, self.xref, resources).then(
- function(data) {
- promise.resolve(data.font.translated);
- }
- );
- return promise;
+ function handleSetFont(fontName, fontRef) {
+ return self.loadFont(fontName, fontRef, xref, resources, null);
}
- function getBidiText(str, startLevel, vertical) {
- if (str) {
- return PDFJS.bidi(str, -1, vertical);
- }
- }
-
- resources = this.xref.fetchIfRef(resources) || new Dict();
+ resources = xref.fetchIfRef(resources) || new Dict();
// The xobj is parsed iff it's needed, e.g. if there is a `DO` cmd.
var xobjs = null;
var parser = new Parser(new Lexer(stream), false);
+ var res = resources;
+ var args = [], obj;
- var chunkPromises = [];
- var fontPromise;
- var args = [];
-
- while (true) {
- var obj = parser.getObj();
- if (isEOF(obj)) {
- break;
- }
-
+ var chunk = '';
+ var font = null;
+ while (!isEOF(obj = parser.getObj())) {
if (isCmd(obj)) {
var cmd = obj.cmd;
switch (cmd) {
// TODO: Add support for SAVE/RESTORE and XFORM here.
case 'Tf':
- fontPromise = handleSetFont(args[0].name, null, resources);
- //.translated;
+ font = handleSetFont(args[0].name).translated;
break;
case 'TJ':
- var chunkPromise = new Promise();
- chunkPromises.push(chunkPromise);
- fontPromise.then(function(items, chunkPromise, font) {
- var chunk = '';
- for (var j = 0, jj = items.length; j < jj; j++) {
- if (typeof items[j] === 'string') {
- chunk += fontCharsToUnicode(items[j], font);
- } else if (items[j] < 0 && font.spaceWidth > 0) {
- var fakeSpaces = -items[j] / font.spaceWidth;
- if (fakeSpaces > MULTI_SPACE_FACTOR) {
- fakeSpaces = Math.round(fakeSpaces);
- while (fakeSpaces--) {
- chunk += ' ';
- }
- } else if (fakeSpaces > SPACE_FACTOR) {
+ var items = args[0];
+ for (var j = 0, jj = items.length; j < jj; j++) {
+ if (typeof items[j] === 'string') {
+ chunk += fontCharsToUnicode(items[j], font);
+ } else if (items[j] < 0 && font.spaceWidth > 0) {
+ var fakeSpaces = -items[j] / font.spaceWidth;
+ if (fakeSpaces > MULTI_SPACE_FACTOR) {
+ fakeSpaces = Math.round(fakeSpaces);
+ while (fakeSpaces--) {
chunk += ' ';
}
+ } else if (fakeSpaces > SPACE_FACTOR) {
+ chunk += ' ';
}
}
- chunkPromise.resolve(
- getBidiText(chunk, -1, font.vertical));
- }.bind(null, args[0], chunkPromise));
+ }
break;
case 'Tj':
- var chunkPromise = new Promise();
- chunkPromises.push(chunkPromise);
- fontPromise.then(function(charCodes, chunkPromise, font) {
- var chunk = fontCharsToUnicode(charCodes, font);
- chunkPromise.resolve(
- getBidiText(chunk, -1, font.vertical));
- }.bind(null, args[0], chunkPromise));
+ chunk += fontCharsToUnicode(args[0], font);
break;
case '\'':
- // For search, adding a extra white space for line breaks
- // would be better here, but that causes too much spaces in
- // the text-selection divs.
- var chunkPromise = new Promise();
- chunkPromises.push(chunkPromise);
- fontPromise.then(function(charCodes, chunkPromise, font) {
- var chunk = fontCharsToUnicode(charCodes, font);
- chunkPromise.resolve(
- getBidiText(chunk, -1, font.vertical));
- }.bind(null, args[0], chunkPromise));
+ // For search, adding a extra white space for line breaks would be
+ // better here, but that causes too much spaces in the
+ // text-selection divs.
+ chunk += fontCharsToUnicode(args[0], font);
break;
case '"':
// Note comment in "'"
- var chunkPromise = new Promise();
- chunkPromises.push(chunkPromise);
- fontPromise.then(function(charCodes, chunkPromise, font) {
- var chunk = fontCharsToUnicode(charCodes, font);
- chunkPromise.resolve(
- getBidiText(chunk, -1, font.vertical));
- }.bind(null, args[2], chunkPromise));
+ chunk += fontCharsToUnicode(args[2], font);
break;
case 'Do':
+ // Set the chunk such that the following if won't add something
+ // to the state.
+ chunk = '';
+
if (args[0].code) {
break;
}
@@ -951,8 +757,7 @@ var PartialEvaluator = (function PartialEvaluatorClosure() {
var xobj = xobjs.get(name);
if (!xobj)
break;
- assertWellFormed(isStream(xobj),
- 'XObject should be a stream');
+ assertWellFormed(isStream(xobj), 'XObject should be a stream');
var type = xobj.dict.get('Subtype');
assertWellFormed(
@@ -963,11 +768,11 @@ var PartialEvaluator = (function PartialEvaluatorClosure() {
if ('Form' !== type.name)
break;
- var chunkPromise = self.getTextContent(
+ state = this.getTextContent(
xobj,
- xobj.dict.get('Resources') || resources
+ xobj.dict.get('Resources') || resources,
+ state
);
- chunkPromises.push(chunkPromise);
break;
case 'gs':
var dictName = args[0];
@@ -980,37 +785,27 @@ var PartialEvaluator = (function PartialEvaluatorClosure() {
for (var i = 0; i < gsState.length; i++) {
if (gsState[i] === 'Font') {
- fontPromise = handleSetFont(
- args[0].name, null, resources);
+ font = handleSetFont(args[0].name).translated;
}
}
break;
} // switch
+ if (chunk !== '') {
+ var bidiText = PDFJS.bidi(chunk, -1, font.vertical);
+ bidiTexts.push(bidiText);
+
+ chunk = '';
+ }
+
args = [];
- parser.saveState();
} else if (obj !== null && obj !== undefined) {
assertWellFormed(args.length <= 33, 'Too many arguments');
args.push(obj);
}
} // while
- Promise.all(chunkPromises).then(function(datas) {
- var bidiTexts = [];
- for (var i = 0, n = datas.length; i < n; ++i) {
- var bidiText = datas[i];
- if (!bidiText) {
- continue;
- } else if (isArray(bidiText)) {
- Util.concatenateToArray(bidiTexts, bidiText);
- } else {
- bidiTexts.push(bidiText);
- }
- }
- statePromise.resolve(bidiTexts);
- });
-
- return statePromise;
+ return state;
},
extractDataStructures: function
@@ -1639,6 +1434,74 @@ var PartialEvaluator = (function PartialEvaluatorClosure() {
return PartialEvaluator;
})();
+
+var OperatorList = (function OperatorListClosure() {
+ var CHUNK_SIZE = 100;
+
+ function OperatorList(messageHandler, pageIndex) {
+ this.messageHandler = messageHandler;
+ this.fnArray = [];
+ this.argsArray = [];
+ this.dependencies = {},
+ this.pageIndex = pageIndex;
+ }
+
+ OperatorList.prototype = {
+
+ addOp: function(fn, args) {
+ this.fnArray.push(fn);
+ this.argsArray.push(args);
+ if (this.messageHandler && this.fnArray.length >= CHUNK_SIZE) {
+ this.flush();
+ }
+ },
+
+ addDependency: function(dependency) {
+ if (dependency in this.dependencies) {
+ return;
+ }
+ this.dependencies[dependency] = true;
+ this.addOp('dependency', [dependency]);
+ },
+
+ addDependencies: function(dependencies) {
+ for (var key in dependencies) {
+ this.addDependency(key);
+ }
+ },
+
+ addOpList: function(opList) {
+ Util.concatenateToArray(this.fnArray, opList.fnArray);
+ Util.concatenateToArray(this.argsArray, opList.argsArray);
+ Util.extendObj(this.dependencies, opList.dependencies);
+ },
+
+ getIR: function() {
+ return {
+ fnArray: this.fnArray,
+ argsArray: this.argsArray
+ };
+ },
+
+ flush: function(lastChunk) {
+ PartialEvaluator.optimizeQueue(this);
+ this.messageHandler.send('RenderPageChunk', {
+ operatorList: {
+ fnArray: this.fnArray,
+ argsArray: this.argsArray,
+ lastChunk: lastChunk
+ },
+ pageIndex: this.pageIndex
+ });
+ this.dependencies = [];
+ this.fnArray = [];
+ this.argsArray = [];
+ }
+ };
+
+ return OperatorList;
+})();
+
var EvalState = (function EvalStateClosure() {
function EvalState() {
// Are soft masks and alpha values shapes or opacities?
diff --git a/src/util.js b/src/util.js
index 8cee4a1eb..a1d7e2b98 100644
--- a/src/util.js
+++ b/src/util.js
@@ -903,14 +903,14 @@ var StatTimer = (function StatTimerClosure() {
if (!this.enabled)
return;
if (name in this.started)
- throw 'Timer is already running for ' + name;
+ warn('Timer is already running for ' + name);
this.started[name] = Date.now();
},
timeEnd: function StatTimer_timeEnd(name) {
if (!this.enabled)
return;
if (!(name in this.started))
- throw 'Timer has not been started for ' + name;
+ warn('Timer has not been started for ' + name);
this.times.push({
'name': name,
'start': this.started[name],
diff --git a/src/worker.js b/src/worker.js
index 9b12a2ff1..72839d740 100644
--- a/src/worker.js
+++ b/src/worker.js
@@ -398,31 +398,11 @@ var WorkerMessageHandler = {
var pageNum = data.pageIndex + 1;
var start = Date.now();
// Pre compile the pdf page and fetch the fonts/images.
- page.getOperatorList(handler).then(function(opListData) {
-
- var operatorList = opListData.queue;
- var dependency = Object.keys(opListData.dependencies);
-
- // The following code does quite the same as
- // Page.prototype.startRendering, but stops at one point and sends the
- // result back to the main thread.
+ page.getOperatorList(handler).then(function(operatorList) {
log('page=%d - getOperatorList: time=%dms, len=%d', pageNum,
Date.now() - start, operatorList.fnArray.length);
- // Filter the dependecies for fonts.
- var fonts = {};
- for (var i = 0, ii = dependency.length; i < ii; i++) {
- var dep = dependency[i];
- if (dep.indexOf('g_font_') === 0) {
- fonts[dep] = true;
- }
- }
- handler.send('RenderPage', {
- pageIndex: data.pageIndex,
- operatorList: operatorList,
- depFonts: Object.keys(fonts)
- });
}, function(e) {
var minimumStackMessage =
diff --git a/test/unit/evaluator_spec.js b/test/unit/evaluator_spec.js
index 5d558fca4..fd023fc4d 100644
--- a/test/unit/evaluator_spec.js
+++ b/test/unit/evaluator_spec.js
@@ -36,14 +36,11 @@ describe('evaluator', function() {
new XrefMock(), new HandlerMock(),
'prefix');
var stream = new StringStream('qTT');
- var promise = evaluator.getOperatorList(stream, new ResourcesMock());
- promise.then(function(data) {
- var result = data.queue;
- expect(!!result.fnArray && !!result.argsArray).toEqual(true);
- expect(result.fnArray.length).toEqual(1);
- expect(result.fnArray[0]).toEqual('save');
- expect(result.argsArray[0].length).toEqual(0);
- });
+ var result = evaluator.getOperatorList(stream, new ResourcesMock());
+ expect(!!result.fnArray && !!result.argsArray).toEqual(true);
+ expect(result.fnArray.length).toEqual(1);
+ expect(result.fnArray[0]).toEqual('save');
+ expect(result.argsArray[0].length).toEqual(0);
});
it('should handle one operations', function() {
@@ -51,13 +48,10 @@ describe('evaluator', function() {
new XrefMock(), new HandlerMock(),
'prefix');
var stream = new StringStream('Q');
- var promise = evaluator.getOperatorList(stream, new ResourcesMock());
- promise.then(function(data) {
- var result = data.queue;
- expect(!!result.fnArray && !!result.argsArray).toEqual(true);
- expect(result.fnArray.length).toEqual(1);
- expect(result.fnArray[0]).toEqual('restore');
- });
+ var result = evaluator.getOperatorList(stream, new ResourcesMock());
+ expect(!!result.fnArray && !!result.argsArray).toEqual(true);
+ expect(result.fnArray.length).toEqual(1);
+ expect(result.fnArray[0]).toEqual('restore');
});
it('should handle two glued operations', function() {
@@ -67,14 +61,11 @@ describe('evaluator', function() {
var resources = new ResourcesMock();
resources.Res1 = {};
var stream = new StringStream('/Res1 DoQ');
- var promise = evaluator.getOperatorList(stream, resources);
- promise.then(function(data) {
- var result = data.queue;
- expect(!!result.fnArray && !!result.argsArray).toEqual(true);
- expect(result.fnArray.length).toEqual(2);
- expect(result.fnArray[0]).toEqual('paintXObject');
- expect(result.fnArray[1]).toEqual('restore');
- });
+ var result = evaluator.getOperatorList(stream, resources);
+ expect(!!result.fnArray && !!result.argsArray).toEqual(true);
+ expect(result.fnArray.length).toEqual(2);
+ expect(result.fnArray[0]).toEqual('paintXObject');
+ expect(result.fnArray[1]).toEqual('restore');
});
it('should handle tree glued operations', function() {
@@ -82,15 +73,12 @@ describe('evaluator', function() {
new XrefMock(), new HandlerMock(),
'prefix');
var stream = new StringStream('qqq');
- var promise = evaluator.getOperatorList(stream, new ResourcesMock());
- promise.then(function(data) {
- var result = data.queue;
- expect(!!result.fnArray && !!result.argsArray).toEqual(true);
- expect(result.fnArray.length).toEqual(3);
- expect(result.fnArray[0]).toEqual('save');
- expect(result.fnArray[1]).toEqual('save');
- expect(result.fnArray[2]).toEqual('save');
- });
+ var result = evaluator.getOperatorList(stream, new ResourcesMock());
+ expect(!!result.fnArray && !!result.argsArray).toEqual(true);
+ expect(result.fnArray.length).toEqual(3);
+ expect(result.fnArray[0]).toEqual('save');
+ expect(result.fnArray[1]).toEqual('save');
+ expect(result.fnArray[2]).toEqual('save');
});
it('should handle three glued operations #2', function() {
@@ -100,15 +88,12 @@ describe('evaluator', function() {
var resources = new ResourcesMock();
resources.Res1 = {};
var stream = new StringStream('B*Bf*');
- var promise = evaluator.getOperatorList(stream, resources);
- promise.then(function(data) {
- var result = data.queue;
- expect(!!result.fnArray && !!result.argsArray).toEqual(true);
- expect(result.fnArray.length).toEqual(3);
- expect(result.fnArray[0]).toEqual('eoFillStroke');
- expect(result.fnArray[1]).toEqual('fillStroke');
- expect(result.fnArray[2]).toEqual('eoFill');
- });
+ var result = evaluator.getOperatorList(stream, resources);
+ expect(!!result.fnArray && !!result.argsArray).toEqual(true);
+ expect(result.fnArray.length).toEqual(3);
+ expect(result.fnArray[0]).toEqual('eoFillStroke');
+ expect(result.fnArray[1]).toEqual('fillStroke');
+ expect(result.fnArray[2]).toEqual('eoFill');
});
it('should handle glued operations and operands', function() {
@@ -116,17 +101,14 @@ describe('evaluator', function() {
new XrefMock(), new HandlerMock(),
'prefix');
var stream = new StringStream('q5 Ts');
- var promise = evaluator.getOperatorList(stream, new ResourcesMock());
- promise.then(function(data) {
- var result = data.queue;
- expect(!!result.fnArray && !!result.argsArray).toEqual(true);
- expect(result.fnArray.length).toEqual(2);
- expect(result.fnArray[0]).toEqual('save');
- expect(result.fnArray[1]).toEqual('setTextRise');
- expect(result.argsArray.length).toEqual(2);
- expect(result.argsArray[1].length).toEqual(1);
- expect(result.argsArray[1][0]).toEqual(5);
- });
+ var result = evaluator.getOperatorList(stream, new ResourcesMock());
+ expect(!!result.fnArray && !!result.argsArray).toEqual(true);
+ expect(result.fnArray.length).toEqual(2);
+ expect(result.fnArray[0]).toEqual('save');
+ expect(result.fnArray[1]).toEqual('setTextRise');
+ expect(result.argsArray.length).toEqual(2);
+ expect(result.argsArray[1].length).toEqual(1);
+ expect(result.argsArray[1][0]).toEqual(5);
});
it('should handle glued operations and literals', function() {
@@ -134,21 +116,18 @@ describe('evaluator', function() {
new XrefMock(), new HandlerMock(),
'prefix');
var stream = new StringStream('trueifalserinullq');
- var promise = evaluator.getOperatorList(stream, new ResourcesMock());
- promise.then(function(data) {
- var result = data.queue;
- expect(!!result.fnArray && !!result.argsArray).toEqual(true);
- expect(result.fnArray.length).toEqual(3);
- expect(result.fnArray[0]).toEqual('setFlatness');
- expect(result.fnArray[1]).toEqual('setRenderingIntent');
- expect(result.fnArray[2]).toEqual('save');
- expect(result.argsArray.length).toEqual(3);
- expect(result.argsArray[0].length).toEqual(1);
- expect(result.argsArray[0][0]).toEqual(true);
- expect(result.argsArray[1].length).toEqual(1);
- expect(result.argsArray[1][0]).toEqual(false);
- expect(result.argsArray[2].length).toEqual(0);
- });
+ var result = evaluator.getOperatorList(stream, new ResourcesMock());
+ expect(!!result.fnArray && !!result.argsArray).toEqual(true);
+ expect(result.fnArray.length).toEqual(3);
+ expect(result.fnArray[0]).toEqual('setFlatness');
+ expect(result.fnArray[1]).toEqual('setRenderingIntent');
+ expect(result.fnArray[2]).toEqual('save');
+ expect(result.argsArray.length).toEqual(3);
+ expect(result.argsArray[0].length).toEqual(1);
+ expect(result.argsArray[0][0]).toEqual(true);
+ expect(result.argsArray[1].length).toEqual(1);
+ expect(result.argsArray[1][0]).toEqual(false);
+ expect(result.argsArray[2].length).toEqual(0);
});
});
@@ -159,39 +138,30 @@ describe('evaluator', function() {
'prefix');
var stream = new StringStream('5 1 d0');
console.log('here!');
- var promise = evaluator.getOperatorList(stream, new ResourcesMock());
- promise.then(function(data) {
- var result = data.queue;
- expect(result.argsArray[0][0]).toEqual(5);
- expect(result.argsArray[0][1]).toEqual(1);
- expect(result.fnArray[0]).toEqual('setCharWidth');
- });
+ var result = evaluator.getOperatorList(stream, new ResourcesMock());
+ expect(result.argsArray[0][0]).toEqual(5);
+ expect(result.argsArray[0][1]).toEqual(1);
+ expect(result.fnArray[0]).toEqual('setCharWidth');
});
it('should execute if too many arguments', function() {
var evaluator = new PartialEvaluator(new PdfManagerMock(),
new XrefMock(), new HandlerMock(),
'prefix');
var stream = new StringStream('5 1 4 d0');
- var promise = evaluator.getOperatorList(stream, new ResourcesMock());
- promise.then(function(data) {
- var result = data.queue;
- expect(result.argsArray[0][0]).toEqual(5);
- expect(result.argsArray[0][1]).toEqual(1);
- expect(result.argsArray[0][2]).toEqual(4);
- expect(result.fnArray[0]).toEqual('setCharWidth');
- });
+ var result = evaluator.getOperatorList(stream, new ResourcesMock());
+ expect(result.argsArray[0][0]).toEqual(5);
+ expect(result.argsArray[0][1]).toEqual(1);
+ expect(result.argsArray[0][2]).toEqual(4);
+ expect(result.fnArray[0]).toEqual('setCharWidth');
});
it('should skip if too few arguments', function() {
var evaluator = new PartialEvaluator(new PdfManagerMock(),
new XrefMock(), new HandlerMock(),
'prefix');
var stream = new StringStream('5 d0');
- var promise = evaluator.getOperatorList(stream, new ResourcesMock());
- promise.then(function(data) {
- var result = data.queue;
- expect(result.argsArray).toEqual([]);
- expect(result.fnArray).toEqual([]);
- });
+ var result = evaluator.getOperatorList(stream, new ResourcesMock());
+ expect(result.argsArray).toEqual([]);
+ expect(result.fnArray).toEqual([]);
});
});
});
diff --git a/test/unit/unit_test.html b/test/unit/unit_test.html
index 26ffbeba5..1d22f517b 100644
--- a/test/unit/unit_test.html
+++ b/test/unit/unit_test.html
@@ -15,8 +15,8 @@
-
+
diff --git a/web/debugger.js b/web/debugger.js
index 2305bb773..ceefd19e1 100644
--- a/web/debugger.js
+++ b/web/debugger.js
@@ -220,26 +220,26 @@ var StepperManager = (function StepperManagerClosure() {
// The stepper for each page's IRQueue.
var Stepper = (function StepperClosure() {
+ // Shorter way to create element and optionally set textContent.
+ function c(tag, textContent) {
+ var d = document.createElement(tag);
+ if (textContent)
+ d.textContent = textContent;
+ return d;
+ }
+
function Stepper(panel, pageIndex, initialBreakPoints) {
this.panel = panel;
- this.len = 0;
this.breakPoint = 0;
this.nextBreakPoint = null;
this.pageIndex = pageIndex;
this.breakPoints = initialBreakPoints;
this.currentIdx = -1;
+ this.operatorListIdx = 0;
}
Stepper.prototype = {
- init: function init(IRQueue) {
- // Shorter way to create element and optionally set textContent.
- function c(tag, textContent) {
- var d = document.createElement(tag);
- if (textContent)
- d.textContent = textContent;
- return d;
- }
+ init: function init() {
var panel = this.panel;
- this.len = IRQueue.fnArray.length;
var content = c('div', 'c=continue, s=step');
var table = c('table');
content.appendChild(table);
@@ -250,15 +250,18 @@ var Stepper = (function StepperClosure() {
headerRow.appendChild(c('th', 'Idx'));
headerRow.appendChild(c('th', 'fn'));
headerRow.appendChild(c('th', 'args'));
-
+ panel.appendChild(content);
+ this.table = table;
+ },
+ updateOperatorList: function updateOperatorList(operatorList) {
var self = this;
- for (var i = 0; i < IRQueue.fnArray.length; i++) {
+ for (var i = this.operatorListIdx; i < operatorList.fnArray.length; i++) {
var line = c('tr');
line.className = 'line';
line.dataset.idx = i;
- table.appendChild(line);
+ this.table.appendChild(line);
var checked = this.breakPoints.indexOf(i) != -1;
- var args = IRQueue.argsArray[i] ? IRQueue.argsArray[i] : [];
+ var args = operatorList.argsArray[i] ? operatorList.argsArray[i] : [];
var breakCell = c('td');
var cbox = c('input');
@@ -278,11 +281,9 @@ var Stepper = (function StepperClosure() {
breakCell.appendChild(cbox);
line.appendChild(breakCell);
line.appendChild(c('td', i.toString()));
- line.appendChild(c('td', IRQueue.fnArray[i]));
+ line.appendChild(c('td', operatorList.fnArray[i]));
line.appendChild(c('td', args.join(', ')));
}
- panel.appendChild(content);
- var self = this;
},
getNextBreakPoint: function getNextBreakPoint() {
this.breakPoints.sort(function(a, b) { return a - b; });
diff --git a/web/viewer.js b/web/viewer.js
index fd10930eb..fb6b3aecd 100644
--- a/web/viewer.js
+++ b/web/viewer.js
@@ -1553,8 +1553,12 @@ var PageView = function pageView(container, id, scale,
};
this.update = function pageViewUpdate(scale, rotation) {
- this.renderingState = RenderingStates.INITIAL;
+ if (this.renderTask) {
+ this.renderTask.cancel();
+ this.renderTask = null;
+ }
this.resume = null;
+ this.renderingState = RenderingStates.INITIAL;
if (typeof rotation !== 'undefined') {
this.rotation = rotation;
@@ -1818,13 +1822,9 @@ var PageView = function pageView(container, id, scale,
// Rendering area
var self = this;
- var renderingWasReset = false;
function pageViewDrawCallback(error) {
- if (renderingWasReset) {
- return;
- }
-
self.renderingState = RenderingStates.FINISHED;
+ self.renderTask = null;
if (self.loadingIconDiv) {
div.removeChild(self.loadingIconDiv);
@@ -1874,12 +1874,6 @@ var PageView = function pageView(container, id, scale,
viewport: this.viewport,
textLayer: textLayer,
continueCallback: function pdfViewcContinueCallback(cont) {
- if (self.renderingState === RenderingStates.INITIAL) {
- // The page update() was called, we just need to abort any rendering.
- renderingWasReset = true;
- return;
- }
-
if (PDFView.highestPriorityPage !== 'page' + self.id) {
self.renderingState = RenderingStates.PAUSED;
self.resume = function resumeCallback() {
@@ -1891,7 +1885,9 @@ var PageView = function pageView(container, id, scale,
cont();
}
};
- this.pdfPage.render(renderContext).then(
+ this.renderTask = this.pdfPage.render(renderContext);
+
+ this.renderTask.then(
function pdfPageRenderCallback() {
pageViewDrawCallback(null);
},