Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add local caching of TilingPatterns in PartialEvaluator.getOperatorList (issue 2765 and 8473) #12458

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
166 changes: 100 additions & 66 deletions src/core/evaluator.js
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ import {
LocalColorSpaceCache,
LocalGStateCache,
LocalImageCache,
LocalTilingPatternCache,
} from "./image_utils.js";
import { bidi } from "./bidi.js";
import { ColorSpace } from "./colorspace.js";
Expand Down Expand Up @@ -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();
Expand All @@ -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) {
Expand Down Expand Up @@ -1221,51 +1225,78 @@ class PartialEvaluator {
});
}

async handleColorN(
handleColorN(
operatorList,
fn,
args,
cs,
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}`);
}
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -1704,7 +1736,8 @@ class PartialEvaluator {
patterns,
resources,
task,
localColorSpaceCache
localColorSpaceCache,
localTilingPatternCache
)
);
return;
Expand All @@ -1724,7 +1757,8 @@ class PartialEvaluator {
patterns,
resources,
task,
localColorSpaceCache
localColorSpaceCache,
localTilingPatternCache
)
);
return;
Expand Down
24 changes: 24 additions & 0 deletions src/core/image_utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -231,5 +254,6 @@ export {
LocalColorSpaceCache,
LocalFunctionCache,
LocalGStateCache,
LocalTilingPatternCache,
GlobalImageCache,
};
4 changes: 2 additions & 2 deletions src/core/pattern.js
Original file line number Diff line number Diff line change
Expand Up @@ -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");
Expand All @@ -983,7 +983,7 @@ function getTilingPatternIR(operatorList, dict, args) {

return [
"TilingPattern",
args,
color,
operatorList,
matrix,
bbox,
Expand Down