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); },