diff --git a/src/core/core.js b/src/core/core.js index 1f054ac3c..e3a821028 100644 --- a/src/core/core.js +++ b/src/core/core.js @@ -156,13 +156,6 @@ var Page = (function PageClosure() { getOperatorList: function Page_getOperatorList(handler, intent) { var self = this; - var capability = createPromiseCapability(); - - function reject(e) { - capability.reject(e); - } - - var pageListCapability = createPromiseCapability(); var pdfManager = this.pdfManager; var contentStreamPromise = pdfManager.ensure(this, 'getContentStream', @@ -184,9 +177,8 @@ var Page = (function PageClosure() { this.idCounters, this.fontCache); - var dataPromises = Promise.all([contentStreamPromise, resourcesPromise], - reject); - dataPromises.then(function(data) { + var dataPromises = Promise.all([contentStreamPromise, resourcesPromise]); + var pageListPromise = dataPromises.then(function(data) { var contentStream = data[0]; var opList = new OperatorList(intent, handler, self.pageIndex); @@ -195,31 +187,30 @@ var Page = (function PageClosure() { pageIndex: self.pageIndex, intent: intent }); - partialEvaluator.getOperatorList(contentStream, self.resources, opList); - pageListCapability.resolve(opList); + return partialEvaluator.getOperatorList(contentStream, self.resources, + opList).then(function () { + return opList; + }); }); var annotationsPromise = pdfManager.ensure(this, 'annotations'); - Promise.all([pageListCapability.promise, annotationsPromise]).then( + return Promise.all([pageListPromise, annotationsPromise]).then( function(datas) { var pageOpList = datas[0]; var annotations = datas[1]; if (annotations.length === 0) { pageOpList.flush(true); - capability.resolve(pageOpList); - return; + return pageOpList; } var annotationsReadyPromise = Annotation.appendToOperatorList( annotations, pageOpList, pdfManager, partialEvaluator, intent); - annotationsReadyPromise.then(function () { + return annotationsReadyPromise.then(function () { pageOpList.flush(true); - capability.resolve(pageOpList); - }, reject); - }, reject); - - return capability.promise; + return pageOpList; + }); + }); }, extractTextContent: function Page_extractTextContent() { diff --git a/src/core/evaluator.js b/src/core/evaluator.js index 29411bb84..5752f6445 100644 --- a/src/core/evaluator.js +++ b/src/core/evaluator.js @@ -22,7 +22,7 @@ stdFontMap, symbolsFonts, getTilingPatternIR, warn, Util, Promise, RefSetCache, isRef, TextRenderingMode, CMapFactory, OPS, UNSUPPORTED_FEATURES, UnsupportedManager, NormalizedUnicodes, - IDENTITY_MATRIX, reverseIfRtl */ + IDENTITY_MATRIX, reverseIfRtl, createPromiseCapability */ 'use strict'; @@ -38,6 +38,28 @@ var PartialEvaluator = (function PartialEvaluatorClosure() { this.fontCache = fontCache; } + // Trying to minimize Date.now() usage and check every 100 time + var TIME_SLOT_DURATION_MS = 20; + var CHECK_TIME_EVERY = 100; + function TimeSlotManager() { + this.reset(); + } + TimeSlotManager.prototype = { + check: function TimeSlotManager_check() { + if (++this.checked < CHECK_TIME_EVERY) { + return false; + } + this.checked = 0; + return this.endTime <= Date.now(); + }, + reset: function TimeSlotManager_reset() { + this.endTime = Date.now() + TIME_SLOT_DURATION_MS; + this.checked = 0; + } + }; + + var deferred = Promise.resolve(); + var TILING_PATTERN = 1, SHADING_PATTERN = 2; PartialEvaluator.prototype = { @@ -121,13 +143,15 @@ var PartialEvaluator = (function PartialEvaluatorClosure() { operatorList.addOp(OPS.paintFormXObjectBegin, [matrix, bbox]); - this.getOperatorList(xobj, (xobj.dict.get('Resources') || resources), - operatorList, initialState); - operatorList.addOp(OPS.paintFormXObjectEnd, []); + return this.getOperatorList(xobj, + (xobj.dict.get('Resources') || resources), operatorList, initialState). + then(function () { + operatorList.addOp(OPS.paintFormXObjectEnd, []); - if (group) { - operatorList.addOp(OPS.endGroup, [groupOptions]); - } + if (group) { + operatorList.addOp(OPS.endGroup, [groupOptions]); + } + }); }, buildPaintImageXObject: @@ -236,7 +260,7 @@ var PartialEvaluator = (function PartialEvaluatorClosure() { subtype: smask.get('S').name, backdrop: smask.get('BC') }; - this.buildFormXObject(resources, smaskContent, smaskOptions, + return this.buildFormXObject(resources, smaskContent, smaskOptions, operatorList, stateManager.state.clone()); }, @@ -245,15 +269,18 @@ var PartialEvaluator = (function PartialEvaluatorClosure() { pattern, patternDict, operatorList) { // Create an IR of the pattern code. - 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, getTilingPatternIR({ - fnArray: tilingOpList.fnArray, - argsArray: tilingOpList.argsArray - }, patternDict, args)); + var tilingOpList = new OperatorList(); + return this.getOperatorList(pattern, + (patternDict.get('Resources') || resources), tilingOpList). + then(function () { + // 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, getTilingPatternIR({ + fnArray: tilingOpList.fnArray, + argsArray: tilingOpList.argsArray + }, patternDict, args)); + }); }, handleSetFont: @@ -266,22 +293,23 @@ var PartialEvaluator = (function PartialEvaluatorClosure() { fontName = fontArgs[0].name; } var self = this; - var font = this.loadFont(fontName, fontRef, this.xref, resources, - operatorList); - state.font = font; - var loadedName = font.loadedName; - if (!font.sent) { - var fontData = font.translated.exportData(); + return this.loadFont(fontName, fontRef, this.xref, resources, + operatorList).then(function (font) { + state.font = font; + var loadedName = font.loadedName; + if (!font.sent) { + var fontData = font.translated.exportData(); - self.handler.send('commonobj', [ - loadedName, - 'Font', - fontData - ]); - font.sent = true; - } + self.handler.send('commonobj', [ + loadedName, + 'Font', + fontData + ]); + font.sent = true; + } - return loadedName; + return loadedName; + }); }, handleText: function PartialEvaluator_handleText(chars, state) { @@ -324,7 +352,6 @@ var PartialEvaluator = (function PartialEvaluatorClosure() { operatorList, xref, stateManager) { - var self = this; // 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) { @@ -343,11 +370,14 @@ var PartialEvaluator = (function PartialEvaluatorClosure() { gStateObj.push([key, value]); break; case 'Font': - var loadedName = self.handleSetFont(resources, null, value[0], - operatorList, - stateManager.state); - operatorList.addDependency(loadedName); - gStateObj.push([key, [loadedName, value[1]]]); + promise = promise.then(function () { + return self.handleSetFont(resources, null, value[0], + operatorList, stateManager.state). + then(function (loadedName) { + operatorList.addDependency(loadedName); + gStateObj.push([key, [loadedName, value[1]]]); + }); + }); break; case 'BM': gStateObj.push([key, value]); @@ -359,7 +389,10 @@ var PartialEvaluator = (function PartialEvaluatorClosure() { } var dict = xref.fetchIfRef(value); if (isDict(dict)) { - self.handleSMask(dict, resources, operatorList, stateManager); + promise = promise.then(function () { + return self.handleSMask(dict, resources, operatorList, + stateManager); + }); gStateObj.push([key, true]); } else { warn('Unsupported SMask type'); @@ -394,12 +427,15 @@ var PartialEvaluator = (function PartialEvaluatorClosure() { // This array holds the converted/processed state data. var gStateObj = []; var gStateMap = gState.map; + var self = this; + var promise = Promise.resolve(); for (var key in gStateMap) { var value = gStateMap[key]; setGStateForKey(gStateObj, key, value); } - - operatorList.addOp(OPS.setGState, [gStateObj]); + return promise.then(function () { + operatorList.addOp(OPS.setGState, [gStateObj]); + }); }, loadFont: function PartialEvaluator_loadFont(fontName, font, xref, @@ -407,10 +443,10 @@ var PartialEvaluator = (function PartialEvaluatorClosure() { parentOperatorList) { function errorFont() { - return { + return Promise.resolve({ translated: new ErrorFont('Font ' + fontName + ' is not available'), loadedName: 'g_font_error' - }; + }); } var fontRef; @@ -435,6 +471,8 @@ var PartialEvaluator = (function PartialEvaluatorClosure() { return errorFont(); } + var fontCapability = createPromiseCapability(); + var preEvaluatedFont = this.preEvaluateFont(font, xref); var descriptor = preEvaluatedFont.descriptor; var fontID = fontRef.num + '_' + fontRef.gen; @@ -471,7 +509,7 @@ var PartialEvaluator = (function PartialEvaluatorClosure() { // fontName in font.loadedName below. var fontRefIsDict = isDict(fontRef); if (!fontRefIsDict) { - this.fontCache.put(fontRef, font); + this.fontCache.put(fontRef, fontCapability.promise); } // Keep track of each font we translated so the caller can @@ -490,27 +528,38 @@ var PartialEvaluator = (function PartialEvaluatorClosure() { font.translated = translated; } + var loadCharProcsPromise = Promise.resolve(); if (font.translated.loadCharProcs) { var charProcs = font.get('CharProcs').getAll(); var fontResources = (font.get('Resources') || resources); 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]; - var operatorList = this.getOperatorList(glyphStream, fontResources); - charProcOperatorList[key] = operatorList.getIR(); - if (!parentOperatorList) { - continue; - } - // Add the dependencies to the parent operator list so they are - // resolved before sub operator list is executed synchronously. - parentOperatorList.addDependencies(charProcOperatorList.dependencies); + loadCharProcsPromise = loadCharProcsPromise.then(function (key) { + var glyphStream = charProcs[key]; + var operatorList = new OperatorList(); + return this.getOperatorList(glyphStream, fontResources, + operatorList). + then(function () { + charProcOperatorList[key] = operatorList.getIR(); + + // Add the dependencies to the parent operator list so they are + // resolved before sub operator list is executed synchronously. + if (parentOperatorList) { + parentOperatorList.addDependencies(operatorList.dependencies); + } + }); + }.bind(this, charProcKeys[i])); } - font.translated.charProcOperatorList = charProcOperatorList; + loadCharProcsPromise = loadCharProcsPromise.then(function () { + font.translated.charProcOperatorList = charProcOperatorList; + }); } - font.loaded = true; - return font; + return loadCharProcsPromise.then(function () { + font.loaded = true; + fontCapability.resolve(font); + return fontCapability.promise; + }, fontCapability.reject); }, buildPath: function PartialEvaluator_buildPath(operatorList, fn, args) { @@ -534,182 +583,196 @@ var PartialEvaluator = (function PartialEvaluatorClosure() { var xref = this.xref; var imageCache = {}; - operatorList = (operatorList || new OperatorList()); + assert(operatorList); resources = (resources || Dict.empty); var xobjs = (resources.get('XObject') || Dict.empty); var patterns = (resources.get('Pattern') || Dict.empty); var stateManager = new StateManager(initialState || new EvalState()); var preprocessor = new EvaluatorPreprocessor(stream, xref, stateManager); + var shading; + var timeSlotManager = new TimeSlotManager(); - var operation, i, ii; - while ((operation = preprocessor.read())) { - var args = operation.args; - var fn = operation.fn; - var shading; + return new Promise(function next(resolve, reject) { + timeSlotManager.reset(); + var stop, operation, i, ii; + while (!(stop = timeSlotManager.check()) && + (operation = preprocessor.read())) { + var args = operation.args; + var fn = operation.fn; - switch (fn | 0) { - case OPS.setStrokeColorN: - case OPS.setFillColorN: - if (args[args.length - 1].code) { + switch (fn | 0) { + case OPS.setStrokeColorN: + case OPS.setFillColorN: + if (args[args.length - 1].code) { + break; + } + // compile tiling patterns + var patternName = args[args.length - 1]; + // SCN/scn applies patterns along with normal colors + var pattern; + if (isName(patternName) && + (pattern = patterns.get(patternName.name))) { + var dict = (isStream(pattern) ? pattern.dict : pattern); + var typeNum = dict.get('PatternType'); + + if (typeNum == TILING_PATTERN) { + return self.handleTilingType(fn, args, resources, pattern, + dict, operatorList).then( + function() { + next(resolve, reject); + }, reject); + } else if (typeNum == SHADING_PATTERN) { + shading = dict.get('Shading'); + var matrix = dict.get('Matrix'); + pattern = Pattern.parseShading(shading, matrix, xref, + resources); + args = pattern.getIR(); + } else { + error('Unknown PatternType ' + typeNum); + } + } break; - } - // compile tiling patterns - var patternName = args[args.length - 1]; - // SCN/scn applies patterns along with normal colors - var pattern; - if (isName(patternName) && - (pattern = patterns.get(patternName.name))) { - var dict = (isStream(pattern) ? pattern.dict : pattern); - var typeNum = dict.get('PatternType'); - - if (typeNum == TILING_PATTERN) { - self.handleTilingType(fn, args, resources, pattern, dict, - operatorList); + case OPS.paintXObject: + if (args[0].code) { + break; + } + // eagerly compile XForm objects + var name = args[0].name; + if (imageCache.key === name) { + operatorList.addOp(imageCache.fn, imageCache.args); args = []; continue; - } else if (typeNum == SHADING_PATTERN) { - shading = dict.get('Shading'); - var matrix = dict.get('Matrix'); - pattern = Pattern.parseShading(shading, matrix, xref, - resources); - args = pattern.getIR(); - } else { - error('Unkown PatternType ' + typeNum); } - } - break; - case OPS.paintXObject: - if (args[0].code) { + + var xobj = xobjs.get(name); + if (xobj) { + assert(isStream(xobj), 'XObject should be a stream'); + + var type = xobj.dict.get('Subtype'); + assert(isName(type), + 'XObject should have a Name subtype'); + + if ('Form' == type.name) { + stateManager.save(); + return self.buildFormXObject(resources, xobj, null, + operatorList, + stateManager.state.clone()). + then(function () { + stateManager.restore(); + next(resolve, reject); + }, reject); + } else if ('Image' == type.name) { + self.buildPaintImageXObject(resources, xobj, false, + operatorList, name, imageCache); + args = []; + continue; + } else { + error('Unhandled XObject subtype ' + type.name); + } + } break; - } - // eagerly compile XForm objects - var name = args[0].name; - if (imageCache.key === name) { - operatorList.addOp(imageCache.fn, imageCache.args); + case OPS.setFont: + var fontSize = args[1]; + // eagerly collect all fonts + return self.handleSetFont(resources, args, null, + operatorList, stateManager.state). + then(function (loadedName) { + operatorList.addDependency(loadedName); + operatorList.addOp(OPS.setFont, [loadedName, fontSize]); + next(resolve, reject); + }, reject); + case OPS.endInlineImage: + var cacheKey = args[0].cacheKey; + if (cacheKey && imageCache.key === cacheKey) { + operatorList.addOp(imageCache.fn, imageCache.args); + args = []; + continue; + } + self.buildPaintImageXObject(resources, args[0], true, + operatorList, cacheKey, imageCache); args = []; continue; - } - - var xobj = xobjs.get(name); - if (xobj) { - assert(isStream(xobj), 'XObject should be a stream'); - - var type = xobj.dict.get('Subtype'); - assert(isName(type), - 'XObject should have a Name subtype'); - - if ('Form' == type.name) { - stateManager.save(); - self.buildFormXObject(resources, xobj, null, operatorList, - stateManager.state.clone()); - args = []; - stateManager.restore(); - continue; - } else if ('Image' == type.name) { - self.buildPaintImageXObject(resources, xobj, false, - operatorList, name, imageCache); - args = []; - continue; - } else { - error('Unhandled XObject subtype ' + type.name); - } - } - break; - case OPS.setFont: - // eagerly collect all fonts - var loadedName = self.handleSetFont(resources, args, null, - operatorList, - stateManager.state); - operatorList.addDependency(loadedName); - args[0] = loadedName; - break; - case OPS.endInlineImage: - var cacheKey = args[0].cacheKey; - if (cacheKey && imageCache.key === cacheKey) { - operatorList.addOp(imageCache.fn, imageCache.args); - args = []; - continue; - } - self.buildPaintImageXObject(resources, args[0], true, - operatorList, cacheKey, imageCache); - args = []; - continue; - case OPS.showText: - args[0] = this.handleText(args[0], stateManager.state); - break; - case OPS.showSpacedText: - var arr = args[0]; - var arrLength = arr.length; - for (i = 0; i < arrLength; ++i) { - if (isString(arr[i])) { - arr[i] = this.handleText(arr[i], stateManager.state); - } - } - break; - case OPS.nextLineShowText: - args[0] = this.handleText(args[0], stateManager.state); - break; - case OPS.nextLineSetSpacingShowText: - args[2] = this.handleText(args[2], stateManager.state); - break; - case OPS.setTextRenderingMode: - stateManager.state.textRenderingMode = args[0]; - break; - // Parse the ColorSpace data to a raw format. - case OPS.setFillColorSpace: - case OPS.setStrokeColorSpace: - args = [ColorSpace.parseToIR(args[0], xref, resources)]; - break; - case OPS.shadingFill: - var shadingRes = resources.get('Shading'); - if (!shadingRes) { - error('No shading resource found'); - } - - shading = shadingRes.get(args[0].name); - if (!shading) { - error('No shading object found'); - } - - var shadingFill = Pattern.parseShading(shading, null, xref, - resources); - var patternIR = shadingFill.getIR(); - args = [patternIR]; - fn = OPS.shadingFill; - break; - case OPS.setGState: - var dictName = args[0]; - var extGState = resources.get('ExtGState'); - - if (!isDict(extGState) || !extGState.has(dictName.name)) { + case OPS.showText: + args[0] = self.handleText(args[0], stateManager.state); break; - } + case OPS.showSpacedText: + var arr = args[0]; + var arrLength = arr.length; + for (i = 0; i < arrLength; ++i) { + if (isString(arr[i])) { + arr[i] = self.handleText(arr[i], stateManager.state); + } + } + break; + case OPS.nextLineShowText: + args[0] = self.handleText(args[0], stateManager.state); + break; + case OPS.nextLineSetSpacingShowText: + args[2] = self.handleText(args[2], stateManager.state); + break; + case OPS.setTextRenderingMode: + stateManager.state.textRenderingMode = args[0]; + break; + // Parse the ColorSpace data to a raw format. + case OPS.setFillColorSpace: + case OPS.setStrokeColorSpace: + args = [ColorSpace.parseToIR(args[0], xref, resources)]; + break; + case OPS.shadingFill: + var shadingRes = resources.get('Shading'); + if (!shadingRes) { + error('No shading resource found'); + } - var gState = extGState.get(dictName.name); - self.setGState(resources, gState, operatorList, xref, - stateManager); - args = []; - continue; - case OPS.moveTo: - case OPS.lineTo: - case OPS.curveTo: - case OPS.curveTo2: - case OPS.curveTo3: - case OPS.closePath: - self.buildPath(operatorList, fn, args); - continue; + shading = shadingRes.get(args[0].name); + if (!shading) { + error('No shading object found'); + } + + var shadingFill = Pattern.parseShading(shading, null, xref, + resources); + var patternIR = shadingFill.getIR(); + args = [patternIR]; + fn = OPS.shadingFill; + break; + case OPS.setGState: + var dictName = args[0]; + var extGState = resources.get('ExtGState'); + + if (!isDict(extGState) || !extGState.has(dictName.name)) { + break; + } + + var gState = extGState.get(dictName.name); + return self.setGState(resources, gState, operatorList, xref, + stateManager).then(function() { + next(resolve, reject); + }, reject); + case OPS.moveTo: + case OPS.lineTo: + case OPS.curveTo: + case OPS.curveTo2: + case OPS.curveTo3: + case OPS.closePath: + self.buildPath(operatorList, fn, args); + continue; + } + operatorList.addOp(fn, args); } - operatorList.addOp(fn, args); - } - - // Some PDFs don't close all restores inside object/form. - // Closing those for them. - for (i = 0, ii = preprocessor.savedStatesDepth; i < ii; i++) { - operatorList.addOp(OPS.restore, []); - } - - return operatorList; + if (stop) { + deferred.then(function () { + next(resolve, reject); + }); + return; + } + // Some PDFs don't close all restores inside object/form. + // Closing those for them. + for (i = 0, ii = preprocessor.savedStatesDepth; i < ii; i++) { + operatorList.addOp(OPS.restore, []); + } + resolve(); + }); }, getTextContent: function PartialEvaluator_getTextContent(stream, resources, @@ -767,10 +830,13 @@ var PartialEvaluator = (function PartialEvaluatorClosure() { } function handleSetFont(fontName, fontRef) { - var font = textState.font = self.loadFont(fontName, fontRef, xref, - resources, null).translated; - textState.fontMatrix = font.fontMatrix ? font.fontMatrix : - FONT_IDENTITY_MATRIX; + return self.loadFont(fontName, fontRef, xref, resources, null). + then(function (font) { + var fontTranslated = textState.font = font.translated; + textState.fontMatrix = fontTranslated.fontMatrix ? + fontTranslated.fontMatrix : + FONT_IDENTITY_MATRIX; + }); } function buildTextGeometry(chars, textChunk) { @@ -865,171 +931,192 @@ var PartialEvaluator = (function PartialEvaluatorClosure() { return textChunk; } - while ((operation = preprocessor.read())) { - textState = stateManager.state; - var fn = operation.fn; - var args = operation.args; - switch (fn | 0) { - case OPS.setFont: - handleSetFont(args[0].name); - textState.fontSize = args[1]; - break; - case OPS.setTextRise: - textState.textRise = args[0]; - break; - case OPS.setHScale: - textState.textHScale = args[0] / 100; - break; - case OPS.setLeading: - textState.leading = args[0]; - break; - case OPS.moveText: - textState.translateTextLineMatrix(args[0], args[1]); - textState.textMatrix = textState.textLineMatrix.slice(); - break; - case OPS.setLeadingMoveText: - textState.leading = -args[1]; - textState.translateTextLineMatrix(args[0], args[1]); - textState.textMatrix = textState.textLineMatrix.slice(); - break; - case OPS.nextLine: - textState.carriageReturn(); - break; - case OPS.setTextMatrix: - textState.setTextMatrix(args[0], args[1], args[2], args[3], - args[4], args[5]); - textState.setTextLineMatrix(args[0], args[1], args[2], args[3], - args[4], args[5]); - break; - case OPS.setCharSpacing: - textState.charSpacing = args[0]; - break; - case OPS.setWordSpacing: - textState.wordSpacing = args[0]; - break; - case OPS.beginText: - textState.textMatrix = IDENTITY_MATRIX.slice(); - textState.textLineMatrix = IDENTITY_MATRIX.slice(); - break; - case OPS.showSpacedText: - var items = args[0]; - var textChunk = newTextChunk(); - var offset; - for (var j = 0, jj = items.length; j < jj; j++) { - if (typeof items[j] === 'string') { - buildTextGeometry(items[j], textChunk); - } else { - var val = items[j] / 1000; - if (!textState.font.vertical) { - offset = -val * textState.fontSize * textState.textHScale * - textState.textMatrix[0]; - textState.translateTextMatrix(offset, 0); - textChunk.width += offset; + var timeSlotManager = new TimeSlotManager(); + + return new Promise(function next(resolve, reject) { + timeSlotManager.reset(); + var stop; + while (!(stop = timeSlotManager.check()) && + (operation = preprocessor.read())) { + textState = stateManager.state; + var fn = operation.fn; + var args = operation.args; + switch (fn | 0) { + case OPS.setFont: + textState.fontSize = args[1]; + return handleSetFont(args[0].name).then(function() { + next(resolve, reject); + }, reject); + case OPS.setTextRise: + textState.textRise = args[0]; + break; + case OPS.setHScale: + textState.textHScale = args[0] / 100; + break; + case OPS.setLeading: + textState.leading = args[0]; + break; + case OPS.moveText: + textState.translateTextLineMatrix(args[0], args[1]); + textState.textMatrix = textState.textLineMatrix.slice(); + break; + case OPS.setLeadingMoveText: + textState.leading = -args[1]; + textState.translateTextLineMatrix(args[0], args[1]); + textState.textMatrix = textState.textLineMatrix.slice(); + break; + case OPS.nextLine: + textState.carriageReturn(); + break; + case OPS.setTextMatrix: + textState.setTextMatrix(args[0], args[1], args[2], args[3], + args[4], args[5]); + textState.setTextLineMatrix(args[0], args[1], args[2], args[3], + args[4], args[5]); + break; + case OPS.setCharSpacing: + textState.charSpacing = args[0]; + break; + case OPS.setWordSpacing: + textState.wordSpacing = args[0]; + break; + case OPS.beginText: + textState.textMatrix = IDENTITY_MATRIX.slice(); + textState.textLineMatrix = IDENTITY_MATRIX.slice(); + break; + case OPS.showSpacedText: + var items = args[0]; + var textChunk = newTextChunk(); + var offset; + for (var j = 0, jj = items.length; j < jj; j++) { + if (typeof items[j] === 'string') { + buildTextGeometry(items[j], textChunk); } else { - offset = -val * textState.fontSize * textState.textMatrix[3]; - textState.translateTextMatrix(0, offset); - textChunk.height += offset; - } - if (items[j] < 0 && textState.font.spaceWidth > 0) { - var fakeSpaces = -items[j] / textState.font.spaceWidth; - if (fakeSpaces > MULTI_SPACE_FACTOR) { - fakeSpaces = Math.round(fakeSpaces); - while (fakeSpaces--) { + var val = items[j] / 1000; + if (!textState.font.vertical) { + offset = -val * textState.fontSize * textState.textHScale * + textState.textMatrix[0]; + textState.translateTextMatrix(offset, 0); + textChunk.width += offset; + } else { + offset = -val * textState.fontSize * + textState.textMatrix[3]; + textState.translateTextMatrix(0, offset); + textChunk.height += offset; + } + if (items[j] < 0 && textState.font.spaceWidth > 0) { + var fakeSpaces = -items[j] / textState.font.spaceWidth; + if (fakeSpaces > MULTI_SPACE_FACTOR) { + fakeSpaces = Math.round(fakeSpaces); + while (fakeSpaces--) { + textChunk.str += ' '; + } + } else if (fakeSpaces > SPACE_FACTOR) { textChunk.str += ' '; } - } else if (fakeSpaces > SPACE_FACTOR) { - textChunk.str += ' '; } } } - } - bidiTexts.push(runBidi(textChunk)); - break; - case OPS.showText: - bidiTexts.push(runBidi(buildTextGeometry(args[0]))); - break; - case OPS.nextLineShowText: - textState.carriageReturn(); - bidiTexts.push(runBidi(buildTextGeometry(args[0]))); - break; - case OPS.nextLineSetSpacingShowText: - textState.wordSpacing = args[0]; - textState.charSpacing = args[1]; - textState.carriageReturn(); - bidiTexts.push(runBidi(buildTextGeometry(args[2]))); - break; - case OPS.paintXObject: - if (args[0].code) { + bidiTexts.push(runBidi(textChunk)); break; - } + case OPS.showText: + bidiTexts.push(runBidi(buildTextGeometry(args[0]))); + break; + case OPS.nextLineShowText: + textState.carriageReturn(); + bidiTexts.push(runBidi(buildTextGeometry(args[0]))); + break; + case OPS.nextLineSetSpacingShowText: + textState.wordSpacing = args[0]; + textState.charSpacing = args[1]; + textState.carriageReturn(); + bidiTexts.push(runBidi(buildTextGeometry(args[2]))); + break; + case OPS.paintXObject: + if (args[0].code) { + break; + } - if (!xobjs) { - xobjs = (resources.get('XObject') || Dict.empty); - } + if (!xobjs) { + xobjs = (resources.get('XObject') || Dict.empty); + } - var name = args[0].name; - if (xobjsCache.key === name) { - if (xobjsCache.texts) { - Util.concatenateToArray(bidiTexts, xobjsCache.texts.items); - Util.extendObj(textContent.styles, xobjsCache.texts.styles); + var name = args[0].name; + if (xobjsCache.key === name) { + if (xobjsCache.texts) { + Util.concatenateToArray(bidiTexts, xobjsCache.texts.items); + Util.extendObj(textContent.styles, xobjsCache.texts.styles); + } + break; + } + + var xobj = xobjs.get(name); + if (!xobj) { + break; + } + assert(isStream(xobj), 'XObject should be a stream'); + + var type = xobj.dict.get('Subtype'); + assert(isName(type), + 'XObject should have a Name subtype'); + + if ('Form' !== type.name) { + xobjsCache.key = name; + xobjsCache.texts = null; + break; + } + + stateManager.save(); + var matrix = xobj.dict.get('Matrix'); + if (isArray(matrix) && matrix.length === 6) { + stateManager.transform(matrix); + } + + return self.getTextContent(xobj, + xobj.dict.get('Resources') || resources, stateManager). + then(function (formTextContent) { + Util.concatenateToArray(bidiTexts, formTextContent.items); + Util.extendObj(textContent.styles, formTextContent.styles); + stateManager.restore(); + + xobjsCache.key = name; + xobjsCache.texts = formTextContent; + + next(resolve, reject); + }, reject); + case OPS.setGState: + var dictName = args[0]; + var extGState = resources.get('ExtGState'); + + if (!isDict(extGState) || !extGState.has(dictName.name)) { + break; + } + + var gsStateMap = extGState.get(dictName.name); + var gsStateFont = null; + for (var key in gsStateMap) { + if (key === 'Font') { + assert(!gsStateFont); + gsStateFont = gsStateMap[key]; + } + } + if (gsStateFont) { + textState.fontSize = gsStateFont[1]; + return handleSetFont(gsStateFont[0]).then(function() { + next(resolve, reject); + }, reject); } break; - } - - var xobj = xobjs.get(name); - if (!xobj) { - break; - } - assert(isStream(xobj), 'XObject should be a stream'); - - var type = xobj.dict.get('Subtype'); - assert(isName(type), - 'XObject should have a Name subtype'); - - if ('Form' !== type.name) { - xobjsCache.key = name; - xobjsCache.texts = null; - break; - } - - stateManager.save(); - var matrix = xobj.dict.get('Matrix'); - if (isArray(matrix) && matrix.length === 6) { - stateManager.transform(matrix); - } - - var formTextContent = this.getTextContent( - xobj, - xobj.dict.get('Resources') || resources, - stateManager - ); - Util.concatenateToArray(bidiTexts, formTextContent.items); - Util.extendObj(textContent.styles, formTextContent.styles); - stateManager.restore(); - - xobjsCache.key = name; - xobjsCache.texts = formTextContent; - break; - case OPS.setGState: - var dictName = args[0]; - var extGState = resources.get('ExtGState'); - - if (!isDict(extGState) || !extGState.has(dictName.name)) { - break; - } - - var gsState = extGState.get(dictName.name); - - for (var i = 0; i < gsState.length; i++) { - if (gsState[i] === 'Font') { - handleSetFont(args[0].name); - } - } - break; - } // switch - } // while - - return textContent; + } // switch + } // while + if (stop) { + deferred.then(function () { + next(resolve, reject); + }); + return; + } + resolve(textContent); + }); }, extractDataStructures: function diff --git a/src/core/obj.js b/src/core/obj.js index 6c1131924..67a191d5b 100644 --- a/src/core/obj.js +++ b/src/core/obj.js @@ -507,11 +507,17 @@ var Catalog = (function CatalogClosure() { }, cleanup: function Catalog_cleanup() { - this.fontCache.forEach(function (font) { - delete font.sent; - delete font.translated; + var promises = []; + this.fontCache.forEach(function (promise) { + promises.push(promise); }); - this.fontCache.clear(); + return Promise.all(promises).then(function (fonts) { + for (var i = 0, ii = fonts.length; i < ii; i++) { + delete fonts[i].sent; + delete fonts[i].translated; + } + this.fontCache.clear(); + }.bind(this)); }, getPage: function Catalog_getPage(pageIndex) { diff --git a/src/core/worker.js b/src/core/worker.js index 15207a76d..6d9fba27d 100644 --- a/src/core/worker.js +++ b/src/core/worker.js @@ -387,8 +387,7 @@ var WorkerMessageHandler = PDFJS.WorkerMessageHandler = { }); handler.on('Cleanup', function wphCleanup(data) { - pdfManager.cleanup(); - return true; + return pdfManager.cleanup(); }); handler.on('Terminate', function wphTerminate(data) { diff --git a/src/shared/annotation.js b/src/shared/annotation.js index af3fa9fe7..7e1c256ad 100644 --- a/src/shared/annotation.js +++ b/src/shared/annotation.js @@ -219,11 +219,9 @@ var Annotation = (function AnnotationClosure() { }, getOperatorList: function Annotation_getOperatorList(evaluator) { - var capability = createPromiseCapability(); if (!this.appearance) { - capability.resolve(new OperatorList()); - return capability.promise; + return Promise.resolve(new OperatorList()); } var data = this.data; @@ -242,18 +240,18 @@ var Annotation = (function AnnotationClosure() { var bbox = appearanceDict.get('BBox') || [0, 0, 1, 1]; var matrix = appearanceDict.get('Matrix') || [1, 0, 0, 1, 0 ,0]; var transform = getTransformMatrix(data.rect, bbox, matrix); + var self = this; - resourcesPromise.then(function(resources) { - var opList = new OperatorList(); - opList.addOp(OPS.beginAnnotation, [data.rect, transform, matrix]); - evaluator.getOperatorList(this.appearance, resources, opList); - opList.addOp(OPS.endAnnotation, []); - capability.resolve(opList); - - this.appearance.reset(); - }.bind(this), capability.reject); - - return capability.promise; + return resourcesPromise.then(function(resources) { + var opList = new OperatorList(); + opList.addOp(OPS.beginAnnotation, [data.rect, transform, matrix]); + return evaluator.getOperatorList(self.appearance, resources, opList). + then(function () { + opList.addOp(OPS.endAnnotation, []); + self.appearance.reset(); + return opList; + }); + }); } }; @@ -512,8 +510,10 @@ var TextWidgetAnnotation = (function TextWidgetAnnotationClosure() { } var stream = new Stream(stringToBytes(data.defaultAppearance)); - evaluator.getOperatorList(stream, this.fieldResources, opList); - return Promise.resolve(opList); + return evaluator.getOperatorList(stream, this.fieldResources, opList). + then(function () { + return opList; + }); } }); diff --git a/test/unit/evaluator_spec.js b/test/unit/evaluator_spec.js index 1786ceb94..26c04c78b 100644 --- a/test/unit/evaluator_spec.js +++ b/test/unit/evaluator_spec.js @@ -1,6 +1,7 @@ /* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */ -/* globals expect, it, describe, PartialEvaluator, StringStream, OPS */ +/* globals expect, it, describe, PartialEvaluator, StringStream, OPS, + OperatorList, waitsFor, runs */ 'use strict'; @@ -30,17 +31,34 @@ describe('evaluator', function() { function PdfManagerMock() { } + function runOperatorListCheck(evaluator, stream, resources, check) { + var done = false; + runs(function () { + var result = new OperatorList(); + evaluator.getOperatorList(stream, resources, result).then(function () { + check(result); + done = true; + }); + }); + waitsFor(function () { + return done; + }); + } + describe('splitCombinedOperations', function() { it('should reject unknown operations', function() { var evaluator = new PartialEvaluator(new PdfManagerMock(), new XrefMock(), new HandlerMock(), 'prefix'); var stream = new StringStream('fTT'); - 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(OPS.fill); - expect(result.argsArray[0].length).toEqual(0); + + runOperatorListCheck(evaluator, stream, new ResourcesMock(), + function(result) { + expect(!!result.fnArray && !!result.argsArray).toEqual(true); + expect(result.fnArray.length).toEqual(1); + expect(result.fnArray[0]).toEqual(OPS.fill); + expect(result.argsArray[0].length).toEqual(0); + }); }); it('should handle one operations', function() { @@ -48,10 +66,12 @@ describe('evaluator', function() { new XrefMock(), new HandlerMock(), 'prefix'); var stream = new StringStream('Q'); - 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(OPS.restore); + runOperatorListCheck(evaluator, stream, new ResourcesMock(), + function(result) { + expect(!!result.fnArray && !!result.argsArray).toEqual(true); + expect(result.fnArray.length).toEqual(1); + expect(result.fnArray[0]).toEqual(OPS.restore); + }); }); it('should handle two glued operations', function() { @@ -61,11 +81,12 @@ describe('evaluator', function() { var resources = new ResourcesMock(); resources.Res1 = {}; var stream = new StringStream('/Res1 DoQ'); - var result = evaluator.getOperatorList(stream, resources); - expect(!!result.fnArray && !!result.argsArray).toEqual(true); - expect(result.fnArray.length).toEqual(2); - expect(result.fnArray[0]).toEqual(OPS.paintXObject); - expect(result.fnArray[1]).toEqual(OPS.restore); + runOperatorListCheck(evaluator, stream, resources, function (result) { + expect(!!result.fnArray && !!result.argsArray).toEqual(true); + expect(result.fnArray.length).toEqual(2); + expect(result.fnArray[0]).toEqual(OPS.paintXObject); + expect(result.fnArray[1]).toEqual(OPS.restore); + }); }); it('should handle tree glued operations', function() { @@ -73,12 +94,14 @@ describe('evaluator', function() { new XrefMock(), new HandlerMock(), 'prefix'); var stream = new StringStream('fff'); - 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(OPS.fill); - expect(result.fnArray[1]).toEqual(OPS.fill); - expect(result.fnArray[2]).toEqual(OPS.fill); + runOperatorListCheck(evaluator, stream, new ResourcesMock(), + function (result) { + expect(!!result.fnArray && !!result.argsArray).toEqual(true); + expect(result.fnArray.length).toEqual(3); + expect(result.fnArray[0]).toEqual(OPS.fill); + expect(result.fnArray[1]).toEqual(OPS.fill); + expect(result.fnArray[2]).toEqual(OPS.fill); + }); }); it('should handle three glued operations #2', function() { @@ -88,12 +111,13 @@ describe('evaluator', function() { var resources = new ResourcesMock(); resources.Res1 = {}; var stream = new StringStream('B*Bf*'); - var result = evaluator.getOperatorList(stream, resources); - expect(!!result.fnArray && !!result.argsArray).toEqual(true); - expect(result.fnArray.length).toEqual(3); - expect(result.fnArray[0]).toEqual(OPS.eoFillStroke); - expect(result.fnArray[1]).toEqual(OPS.fillStroke); - expect(result.fnArray[2]).toEqual(OPS.eoFill); + runOperatorListCheck(evaluator, stream, resources, function (result) { + expect(!!result.fnArray && !!result.argsArray).toEqual(true); + expect(result.fnArray.length).toEqual(3); + expect(result.fnArray[0]).toEqual(OPS.eoFillStroke); + expect(result.fnArray[1]).toEqual(OPS.fillStroke); + expect(result.fnArray[2]).toEqual(OPS.eoFill); + }); }); it('should handle glued operations and operands', function() { @@ -101,14 +125,16 @@ describe('evaluator', function() { new XrefMock(), new HandlerMock(), 'prefix'); var stream = new StringStream('f5 Ts'); - 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(OPS.fill); - expect(result.fnArray[1]).toEqual(OPS.setTextRise); - expect(result.argsArray.length).toEqual(2); - expect(result.argsArray[1].length).toEqual(1); - expect(result.argsArray[1][0]).toEqual(5); + runOperatorListCheck(evaluator, stream, new ResourcesMock(), + function (result) { + expect(!!result.fnArray && !!result.argsArray).toEqual(true); + expect(result.fnArray.length).toEqual(2); + expect(result.fnArray[0]).toEqual(OPS.fill); + expect(result.fnArray[1]).toEqual(OPS.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() { @@ -116,18 +142,20 @@ describe('evaluator', function() { new XrefMock(), new HandlerMock(), 'prefix'); var stream = new StringStream('trueifalserinulln'); - 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(OPS.setFlatness); - expect(result.fnArray[1]).toEqual(OPS.setRenderingIntent); - expect(result.fnArray[2]).toEqual(OPS.endPath); - 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); + runOperatorListCheck(evaluator, stream, new ResourcesMock(), + function (result) { + expect(!!result.fnArray && !!result.argsArray).toEqual(true); + expect(result.fnArray.length).toEqual(3); + expect(result.fnArray[0]).toEqual(OPS.setFlatness); + expect(result.fnArray[1]).toEqual(OPS.setRenderingIntent); + expect(result.fnArray[2]).toEqual(OPS.endPath); + 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); + }); }); }); @@ -138,57 +166,67 @@ describe('evaluator', function() { 'prefix'); var stream = new StringStream('5 1 d0'); console.log('here!'); - 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(OPS.setCharWidth); + runOperatorListCheck(evaluator, stream, new ResourcesMock(), + function (result) { + expect(result.argsArray[0][0]).toEqual(5); + expect(result.argsArray[0][1]).toEqual(1); + expect(result.fnArray[0]).toEqual(OPS.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 result = evaluator.getOperatorList(stream, new ResourcesMock()); - expect(result.argsArray[0][0]).toEqual(1); - expect(result.argsArray[0][1]).toEqual(4); - expect(result.fnArray[0]).toEqual(OPS.setCharWidth); + runOperatorListCheck(evaluator, stream, new ResourcesMock(), + function (result) { + expect(result.argsArray[0][0]).toEqual(1); + expect(result.argsArray[0][1]).toEqual(4); + expect(result.fnArray[0]).toEqual(OPS.setCharWidth); + }); }); it('should execute if nested commands', function() { var evaluator = new PartialEvaluator(new PdfManagerMock(), new XrefMock(), new HandlerMock(), 'prefix'); var stream = new StringStream('/F2 /GS2 gs 5.711 Tf'); - var result = evaluator.getOperatorList(stream, new ResourcesMock()); - expect(result.fnArray.length).toEqual(3); - expect(result.fnArray[0]).toEqual(OPS.setGState); - expect(result.fnArray[1]).toEqual(OPS.dependency); - expect(result.fnArray[2]).toEqual(OPS.setFont); - expect(result.argsArray.length).toEqual(3); - expect(result.argsArray[0].length).toEqual(1); - expect(result.argsArray[1].length).toEqual(1); - expect(result.argsArray[2].length).toEqual(2); + runOperatorListCheck(evaluator, stream, new ResourcesMock(), + function (result) { + expect(result.fnArray.length).toEqual(3); + expect(result.fnArray[0]).toEqual(OPS.setGState); + expect(result.fnArray[1]).toEqual(OPS.dependency); + expect(result.fnArray[2]).toEqual(OPS.setFont); + expect(result.argsArray.length).toEqual(3); + expect(result.argsArray[0].length).toEqual(1); + expect(result.argsArray[1].length).toEqual(1); + expect(result.argsArray[2].length).toEqual(2); + }); }); 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 result = evaluator.getOperatorList(stream, new ResourcesMock()); - expect(result.argsArray).toEqual([]); - expect(result.fnArray).toEqual([]); + runOperatorListCheck(evaluator, stream, new ResourcesMock(), + function (result) { + expect(result.argsArray).toEqual([]); + expect(result.fnArray).toEqual([]); + }); }); it('should close opened saves', function() { var evaluator = new PartialEvaluator(new PdfManagerMock(), new XrefMock(), new HandlerMock(), 'prefix'); var stream = new StringStream('qq'); - var result = evaluator.getOperatorList(stream, new ResourcesMock()); - expect(!!result.fnArray && !!result.argsArray).toEqual(true); - expect(result.fnArray.length).toEqual(4); - expect(result.fnArray[0]).toEqual(OPS.save); - expect(result.fnArray[1]).toEqual(OPS.save); - expect(result.fnArray[2]).toEqual(OPS.restore); - expect(result.fnArray[3]).toEqual(OPS.restore); + runOperatorListCheck(evaluator, stream, new ResourcesMock(), + function (result) { + expect(!!result.fnArray && !!result.argsArray).toEqual(true); + expect(result.fnArray.length).toEqual(4); + expect(result.fnArray[0]).toEqual(OPS.save); + expect(result.fnArray[1]).toEqual(OPS.save); + expect(result.fnArray[2]).toEqual(OPS.restore); + expect(result.fnArray[3]).toEqual(OPS.restore); + }); }); }); });