diff --git a/src/core/evaluator.js b/src/core/evaluator.js index 1441f542d594b..f5a7f36879356 100644 --- a/src/core/evaluator.js +++ b/src/core/evaluator.js @@ -81,6 +81,7 @@ import { LocalColorSpaceCache, LocalGStateCache, LocalImageCache, + LocalTilingPatternCache, } from "./image_utils.js"; import { bidi } from "./bidi.js"; import { ColorSpace } from "./colorspace.js"; @@ -716,12 +717,14 @@ class PartialEvaluator { handleTilingType( fn, - args, + color, resources, pattern, patternDict, operatorList, - task + task, + cacheKey, + localTilingPatternCache ) { // Create an IR of the pattern code. const tilingOpList = new OperatorList(); @@ -739,38 +742,39 @@ class PartialEvaluator { operatorList: tilingOpList, }) .then(function () { - return getTilingPatternIR( - { - fnArray: tilingOpList.fnArray, - argsArray: tilingOpList.argsArray, - }, + const operatorListIR = tilingOpList.getIR(); + const tilingPatternIR = getTilingPatternIR( + operatorListIR, patternDict, - args + color ); + // Add the dependencies to the parent operator list so they are + // resolved before the sub operator list is executed synchronously. + operatorList.addDependencies(tilingOpList.dependencies); + operatorList.addOp(fn, tilingPatternIR); + + if (cacheKey) { + localTilingPatternCache.set(cacheKey, patternDict.objId, { + operatorListIR, + dict: patternDict, + }); + } }) - .then( - function (tilingPatternIR) { - // Add the dependencies to the parent operator list so they are - // resolved before the sub operator list is executed synchronously. - operatorList.addDependencies(tilingOpList.dependencies); - operatorList.addOp(fn, tilingPatternIR); - }, - reason => { - if (reason instanceof AbortException) { - return; - } - if (this.options.ignoreErrors) { - // Error(s) in the TilingPattern -- sending unsupported feature - // notification and allow rendering to continue. - this.handler.send("UnsupportedFeature", { - featureId: UNSUPPORTED_FEATURES.errorTilingPattern, - }); - warn(`handleTilingType - ignoring pattern: "${reason}".`); - return; - } - throw reason; + .catch(reason => { + if (reason instanceof AbortException) { + return; } - ); + if (this.options.ignoreErrors) { + // Error(s) in the TilingPattern -- sending unsupported feature + // notification and allow rendering to continue. + this.handler.send("UnsupportedFeature", { + featureId: UNSUPPORTED_FEATURES.errorTilingPattern, + }); + warn(`handleTilingType - ignoring pattern: "${reason}".`); + return; + } + throw reason; + }); } handleSetFont(resources, fontArgs, fontRef, operatorList, task, state) { @@ -1221,7 +1225,7 @@ class PartialEvaluator { }); } - async handleColorN( + handleColorN( operatorList, fn, args, @@ -1229,43 +1233,70 @@ class PartialEvaluator { patterns, resources, task, - localColorSpaceCache + localColorSpaceCache, + localTilingPatternCache ) { // compile tiling patterns - var patternName = args[args.length - 1]; + const 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 === PatternType.TILING) { - var color = cs.base ? cs.base.getRgb(args, 0) : null; - return this.handleTilingType( - fn, - color, - resources, - pattern, - dict, - operatorList, - task - ); - } else if (typeNum === PatternType.SHADING) { - var shading = dict.get("Shading"); - var matrix = dict.getArray("Matrix"); - pattern = Pattern.parseShading( - shading, - matrix, - this.xref, - resources, - this.handler, - this._pdfFunctionFactory, - localColorSpaceCache - ); - operatorList.addOp(fn, pattern.getIR()); - return undefined; + if (patternName instanceof Name) { + const localTilingPattern = localTilingPatternCache.getByName(patternName); + if (localTilingPattern) { + try { + const color = cs.base ? cs.base.getRgb(args, 0) : null; + const tilingPatternIR = getTilingPatternIR( + localTilingPattern.operatorListIR, + localTilingPattern.dict, + color + ); + operatorList.addOp(fn, tilingPatternIR); + return undefined; + } catch (ex) { + if (ex instanceof MissingDataException) { + throw ex; + } + // Handle any errors during normal TilingPattern parsing. + } + } + // TODO: Attempt to lookup cached TilingPatterns by reference as well, + // if and only if there are PDF documents where doing so would + // significantly improve performance. + + let pattern = patterns.get(patternName.name); + if (pattern) { + var dict = isStream(pattern) ? pattern.dict : pattern; + var typeNum = dict.get("PatternType"); + + if (typeNum === PatternType.TILING) { + const color = cs.base ? cs.base.getRgb(args, 0) : null; + return this.handleTilingType( + fn, + color, + resources, + pattern, + dict, + operatorList, + task, + patternName, + localTilingPatternCache + ); + } else if (typeNum === PatternType.SHADING) { + var shading = dict.get("Shading"); + var matrix = dict.getArray("Matrix"); + pattern = Pattern.parseShading( + shading, + matrix, + this.xref, + resources, + this.handler, + this._pdfFunctionFactory, + localColorSpaceCache + ); + operatorList.addOp(fn, pattern.getIR()); + return undefined; + } + throw new FormatError(`Unknown PatternType: ${typeNum}`); } - throw new FormatError(`Unknown PatternType: ${typeNum}`); } throw new FormatError(`Unknown PatternName: ${patternName}`); } @@ -1349,6 +1380,7 @@ class PartialEvaluator { const localImageCache = new LocalImageCache(); const localColorSpaceCache = new LocalColorSpaceCache(); const localGStateCache = new LocalGStateCache(); + const localTilingPatternCache = new LocalTilingPatternCache(); var xobjs = resources.get("XObject") || Dict.empty; var patterns = resources.get("Pattern") || Dict.empty; @@ -1704,7 +1736,8 @@ class PartialEvaluator { patterns, resources, task, - localColorSpaceCache + localColorSpaceCache, + localTilingPatternCache ) ); return; @@ -1724,7 +1757,8 @@ class PartialEvaluator { patterns, resources, task, - localColorSpaceCache + localColorSpaceCache, + localTilingPatternCache ) ); return; diff --git a/src/core/image_utils.js b/src/core/image_utils.js index d1faf37dd76ee..6cb485f454bad 100644 --- a/src/core/image_utils.js +++ b/src/core/image_utils.js @@ -133,6 +133,29 @@ class LocalGStateCache extends BaseLocalCache { } } +class LocalTilingPatternCache extends BaseLocalCache { + set(name, ref = null, data) { + if (!name) { + throw new Error( + 'LocalTilingPatternCache.set - expected "name" argument.' + ); + } + if (ref) { + if (this._imageCache.has(ref)) { + return; + } + this._nameRefMap.set(name, ref); + this._imageCache.put(ref, data); + return; + } + // name + if (this._imageMap.has(name)) { + return; + } + this._imageMap.set(name, data); + } +} + class GlobalImageCache { static get NUM_PAGES_THRESHOLD() { return shadow(this, "NUM_PAGES_THRESHOLD", 2); @@ -231,5 +254,6 @@ export { LocalColorSpaceCache, LocalFunctionCache, LocalGStateCache, + LocalTilingPatternCache, GlobalImageCache, }; diff --git a/src/core/pattern.js b/src/core/pattern.js index 34f0ab13857fc..3c81d78245890 100644 --- a/src/core/pattern.js +++ b/src/core/pattern.js @@ -967,7 +967,7 @@ Shadings.Dummy = (function DummyClosure() { return Dummy; })(); -function getTilingPatternIR(operatorList, dict, args) { +function getTilingPatternIR(operatorList, dict, color) { const matrix = dict.getArray("Matrix"); const bbox = Util.normalizeRect(dict.getArray("BBox")); const xstep = dict.get("XStep"); @@ -983,7 +983,7 @@ function getTilingPatternIR(operatorList, dict, args) { return [ "TilingPattern", - args, + color, operatorList, matrix, bbox,