Merge pull request #13808 from brendandahl/pattern-cache-v2
Improve caching of shading patterns. (bug 1721949)
This commit is contained in:
commit
4ad5c5d52a
@ -1315,20 +1315,17 @@ class PartialEvaluator {
|
|||||||
}
|
}
|
||||||
|
|
||||||
parseShading({
|
parseShading({
|
||||||
keyObj,
|
|
||||||
shading,
|
shading,
|
||||||
resources,
|
resources,
|
||||||
localColorSpaceCache,
|
localColorSpaceCache,
|
||||||
localShadingPatternCache,
|
localShadingPatternCache,
|
||||||
matrix = null,
|
|
||||||
}) {
|
}) {
|
||||||
// Shadings and patterns may be referenced by the same name but the resource
|
// Shadings and patterns may be referenced by the same name but the resource
|
||||||
// dictionary could be different so we can't use the name for the cache key.
|
// dictionary could be different so we can't use the name for the cache key.
|
||||||
let id = localShadingPatternCache.get(keyObj);
|
let id = localShadingPatternCache.get(shading);
|
||||||
if (!id) {
|
if (!id) {
|
||||||
var shadingFill = Pattern.parseShading(
|
var shadingFill = Pattern.parseShading(
|
||||||
shading,
|
shading,
|
||||||
matrix,
|
|
||||||
this.xref,
|
this.xref,
|
||||||
resources,
|
resources,
|
||||||
this.handler,
|
this.handler,
|
||||||
@ -1337,7 +1334,7 @@ class PartialEvaluator {
|
|||||||
);
|
);
|
||||||
const patternIR = shadingFill.getIR();
|
const patternIR = shadingFill.getIR();
|
||||||
id = `pattern_${this.idFactory.createObjId()}`;
|
id = `pattern_${this.idFactory.createObjId()}`;
|
||||||
localShadingPatternCache.set(keyObj, id);
|
localShadingPatternCache.set(shading, id);
|
||||||
this.handler.send("obj", [id, this.pageIndex, "Pattern", patternIR]);
|
this.handler.send("obj", [id, this.pageIndex, "Pattern", patternIR]);
|
||||||
}
|
}
|
||||||
return id;
|
return id;
|
||||||
@ -1402,14 +1399,12 @@ class PartialEvaluator {
|
|||||||
const shading = dict.get("Shading");
|
const shading = dict.get("Shading");
|
||||||
const matrix = dict.getArray("Matrix");
|
const matrix = dict.getArray("Matrix");
|
||||||
const objId = this.parseShading({
|
const objId = this.parseShading({
|
||||||
keyObj: pattern,
|
|
||||||
shading,
|
shading,
|
||||||
matrix,
|
|
||||||
resources,
|
resources,
|
||||||
localColorSpaceCache,
|
localColorSpaceCache,
|
||||||
localShadingPatternCache,
|
localShadingPatternCache,
|
||||||
});
|
});
|
||||||
operatorList.addOp(fn, ["Shading", objId]);
|
operatorList.addOp(fn, ["Shading", objId, matrix]);
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
throw new FormatError(`Unknown PatternType: ${typeNum}`);
|
throw new FormatError(`Unknown PatternType: ${typeNum}`);
|
||||||
@ -1942,7 +1937,6 @@ class PartialEvaluator {
|
|||||||
throw new FormatError("No shading object found");
|
throw new FormatError("No shading object found");
|
||||||
}
|
}
|
||||||
const patternId = self.parseShading({
|
const patternId = self.parseShading({
|
||||||
keyObj: shading,
|
|
||||||
shading,
|
shading,
|
||||||
resources,
|
resources,
|
||||||
localColorSpaceCache,
|
localColorSpaceCache,
|
||||||
|
@ -44,7 +44,6 @@ class Pattern {
|
|||||||
|
|
||||||
static parseShading(
|
static parseShading(
|
||||||
shading,
|
shading,
|
||||||
matrix,
|
|
||||||
xref,
|
xref,
|
||||||
res,
|
res,
|
||||||
handler,
|
handler,
|
||||||
@ -60,7 +59,6 @@ class Pattern {
|
|||||||
case ShadingType.RADIAL:
|
case ShadingType.RADIAL:
|
||||||
return new RadialAxialShading(
|
return new RadialAxialShading(
|
||||||
dict,
|
dict,
|
||||||
matrix,
|
|
||||||
xref,
|
xref,
|
||||||
res,
|
res,
|
||||||
pdfFunctionFactory,
|
pdfFunctionFactory,
|
||||||
@ -72,7 +70,6 @@ class Pattern {
|
|||||||
case ShadingType.TENSOR_PATCH_MESH:
|
case ShadingType.TENSOR_PATCH_MESH:
|
||||||
return new MeshShading(
|
return new MeshShading(
|
||||||
shading,
|
shading,
|
||||||
matrix,
|
|
||||||
xref,
|
xref,
|
||||||
res,
|
res,
|
||||||
pdfFunctionFactory,
|
pdfFunctionFactory,
|
||||||
@ -115,16 +112,8 @@ class BaseShading {
|
|||||||
// Radial and axial shading have very similar implementations
|
// Radial and axial shading have very similar implementations
|
||||||
// If needed, the implementations can be broken into two classes.
|
// If needed, the implementations can be broken into two classes.
|
||||||
class RadialAxialShading extends BaseShading {
|
class RadialAxialShading extends BaseShading {
|
||||||
constructor(
|
constructor(dict, xref, resources, pdfFunctionFactory, localColorSpaceCache) {
|
||||||
dict,
|
|
||||||
matrix,
|
|
||||||
xref,
|
|
||||||
resources,
|
|
||||||
pdfFunctionFactory,
|
|
||||||
localColorSpaceCache
|
|
||||||
) {
|
|
||||||
super();
|
super();
|
||||||
this.matrix = matrix;
|
|
||||||
this.coordsArr = dict.getArray("Coords");
|
this.coordsArr = dict.getArray("Coords");
|
||||||
this.shadingType = dict.get("ShadingType");
|
this.shadingType = dict.get("ShadingType");
|
||||||
const cs = ColorSpace.parse({
|
const cs = ColorSpace.parse({
|
||||||
@ -244,17 +233,7 @@ class RadialAxialShading extends BaseShading {
|
|||||||
unreachable(`getPattern type unknown: ${shadingType}`);
|
unreachable(`getPattern type unknown: ${shadingType}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
return [
|
return ["RadialAxial", type, this.bbox, this.colorStops, p0, p1, r0, r1];
|
||||||
"RadialAxial",
|
|
||||||
type,
|
|
||||||
this.bbox,
|
|
||||||
this.colorStops,
|
|
||||||
p0,
|
|
||||||
p1,
|
|
||||||
r0,
|
|
||||||
r1,
|
|
||||||
this.matrix,
|
|
||||||
];
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -418,7 +397,6 @@ class MeshShading extends BaseShading {
|
|||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
stream,
|
stream,
|
||||||
matrix,
|
|
||||||
xref,
|
xref,
|
||||||
resources,
|
resources,
|
||||||
pdfFunctionFactory,
|
pdfFunctionFactory,
|
||||||
@ -429,7 +407,6 @@ class MeshShading extends BaseShading {
|
|||||||
throw new FormatError("Mesh data is not a stream");
|
throw new FormatError("Mesh data is not a stream");
|
||||||
}
|
}
|
||||||
const dict = stream.dict;
|
const dict = stream.dict;
|
||||||
this.matrix = matrix;
|
|
||||||
this.shadingType = dict.get("ShadingType");
|
this.shadingType = dict.get("ShadingType");
|
||||||
const bbox = dict.getArray("BBox");
|
const bbox = dict.getArray("BBox");
|
||||||
if (Array.isArray(bbox) && bbox.length === 4) {
|
if (Array.isArray(bbox) && bbox.length === 4) {
|
||||||
@ -942,7 +919,6 @@ class MeshShading extends BaseShading {
|
|||||||
this.colors,
|
this.colors,
|
||||||
this.figures,
|
this.figures,
|
||||||
this.bounds,
|
this.bounds,
|
||||||
this.matrix,
|
|
||||||
this.bbox,
|
this.bbox,
|
||||||
this.background,
|
this.background,
|
||||||
];
|
];
|
||||||
|
@ -37,6 +37,10 @@ const MIN_FONT_SIZE = 16;
|
|||||||
const MAX_FONT_SIZE = 100;
|
const MAX_FONT_SIZE = 100;
|
||||||
const MAX_GROUP_SIZE = 4096;
|
const MAX_GROUP_SIZE = 4096;
|
||||||
|
|
||||||
|
// This value comes from sampling a few PDFs that re-use patterns, there doesn't
|
||||||
|
// seem to be any that benefit from caching more than 2 patterns.
|
||||||
|
const MAX_CACHED_CANVAS_PATTERNS = 2;
|
||||||
|
|
||||||
// Defines the time the `executeOperatorList`-method is going to be executing
|
// Defines the time the `executeOperatorList`-method is going to be executing
|
||||||
// before it stops and shedules a continue of execution.
|
// before it stops and shedules a continue of execution.
|
||||||
const EXECUTION_TIME = 15; // ms
|
const EXECUTION_TIME = 15; // ms
|
||||||
@ -231,6 +235,46 @@ class CachedCanvases {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Least recently used cache implemented with a JS Map. JS Map keys are ordered
|
||||||
|
* by last insertion.
|
||||||
|
*/
|
||||||
|
class LRUCache {
|
||||||
|
constructor(maxSize = 0) {
|
||||||
|
this._cache = new Map();
|
||||||
|
this._maxSize = maxSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
has(key) {
|
||||||
|
return this._cache.has(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
get(key) {
|
||||||
|
if (this._cache.has(key)) {
|
||||||
|
// Delete and set the value so it's moved to the end of the map iteration.
|
||||||
|
const value = this._cache.get(key);
|
||||||
|
this._cache.delete(key);
|
||||||
|
this._cache.set(key, value);
|
||||||
|
}
|
||||||
|
return this._cache.get(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
set(key, value) {
|
||||||
|
if (this._maxSize <= 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (this._cache.size + 1 > this._maxSize) {
|
||||||
|
// Delete the least recently used.
|
||||||
|
this._cache.delete(this._cache.keys().next().value);
|
||||||
|
}
|
||||||
|
this._cache.set(key, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
clear() {
|
||||||
|
this._cache.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function compileType3Glyph(imgData) {
|
function compileType3Glyph(imgData) {
|
||||||
const POINT_TO_PROCESS_LIMIT = 1000;
|
const POINT_TO_PROCESS_LIMIT = 1000;
|
||||||
const POINT_TYPES = new Uint8Array([
|
const POINT_TYPES = new Uint8Array([
|
||||||
@ -866,6 +910,7 @@ class CanvasGraphics {
|
|||||||
this.markedContentStack = [];
|
this.markedContentStack = [];
|
||||||
this.optionalContentConfig = optionalContentConfig;
|
this.optionalContentConfig = optionalContentConfig;
|
||||||
this.cachedCanvases = new CachedCanvases(this.canvasFactory);
|
this.cachedCanvases = new CachedCanvases(this.canvasFactory);
|
||||||
|
this.cachedCanvasPatterns = new LRUCache(MAX_CACHED_CANVAS_PATTERNS);
|
||||||
this.cachedPatterns = new Map();
|
this.cachedPatterns = new Map();
|
||||||
if (canvasCtx) {
|
if (canvasCtx) {
|
||||||
// NOTE: if mozCurrentTransform is polyfilled, then the current state of
|
// NOTE: if mozCurrentTransform is polyfilled, then the current state of
|
||||||
@ -1017,6 +1062,7 @@ class CanvasGraphics {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.cachedCanvases.clear();
|
this.cachedCanvases.clear();
|
||||||
|
this.cachedCanvasPatterns.clear();
|
||||||
this.cachedPatterns.clear();
|
this.cachedPatterns.clear();
|
||||||
|
|
||||||
if (this.imageLayer) {
|
if (this.imageLayer) {
|
||||||
@ -2125,7 +2171,7 @@ class CanvasGraphics {
|
|||||||
baseTransform
|
baseTransform
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
pattern = this._getPattern(IR[1]);
|
pattern = this._getPattern(IR[1], IR[2]);
|
||||||
}
|
}
|
||||||
return pattern;
|
return pattern;
|
||||||
}
|
}
|
||||||
@ -2152,12 +2198,20 @@ class CanvasGraphics {
|
|||||||
this.current.patternFill = false;
|
this.current.patternFill = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
_getPattern(objId) {
|
_getPattern(objId, matrix = null) {
|
||||||
|
let pattern;
|
||||||
if (this.cachedPatterns.has(objId)) {
|
if (this.cachedPatterns.has(objId)) {
|
||||||
return this.cachedPatterns.get(objId);
|
pattern = this.cachedPatterns.get(objId);
|
||||||
}
|
} else {
|
||||||
const pattern = getShadingPattern(this.objs.get(objId));
|
pattern = getShadingPattern(
|
||||||
|
this.objs.get(objId),
|
||||||
|
this.cachedCanvasPatterns
|
||||||
|
);
|
||||||
this.cachedPatterns.set(objId, pattern);
|
this.cachedPatterns.set(objId, pattern);
|
||||||
|
}
|
||||||
|
if (matrix) {
|
||||||
|
pattern.matrix = matrix;
|
||||||
|
}
|
||||||
return pattern;
|
return pattern;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -46,7 +46,7 @@ class BaseShadingPattern {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class RadialAxialShadingPattern extends BaseShadingPattern {
|
class RadialAxialShadingPattern extends BaseShadingPattern {
|
||||||
constructor(IR) {
|
constructor(IR, cachedCanvasPatterns) {
|
||||||
super();
|
super();
|
||||||
this._type = IR[1];
|
this._type = IR[1];
|
||||||
this._bbox = IR[2];
|
this._bbox = IR[2];
|
||||||
@ -55,8 +55,8 @@ class RadialAxialShadingPattern extends BaseShadingPattern {
|
|||||||
this._p1 = IR[5];
|
this._p1 = IR[5];
|
||||||
this._r0 = IR[6];
|
this._r0 = IR[6];
|
||||||
this._r1 = IR[7];
|
this._r1 = IR[7];
|
||||||
this._matrix = IR[8];
|
this.matrix = null;
|
||||||
this._patternCache = null;
|
this.cachedCanvasPatterns = cachedCanvasPatterns;
|
||||||
}
|
}
|
||||||
|
|
||||||
_createGradient(ctx) {
|
_createGradient(ctx) {
|
||||||
@ -87,10 +87,10 @@ class RadialAxialShadingPattern extends BaseShadingPattern {
|
|||||||
|
|
||||||
getPattern(ctx, owner, inverse, shadingFill = false) {
|
getPattern(ctx, owner, inverse, shadingFill = false) {
|
||||||
let pattern;
|
let pattern;
|
||||||
if (this._patternCache) {
|
|
||||||
pattern = this._patternCache;
|
|
||||||
} else {
|
|
||||||
if (!shadingFill) {
|
if (!shadingFill) {
|
||||||
|
if (this.cachedCanvasPatterns.has(this)) {
|
||||||
|
pattern = this.cachedCanvasPatterns.get(this);
|
||||||
|
} else {
|
||||||
const tmpCanvas = owner.cachedCanvases.getCanvas(
|
const tmpCanvas = owner.cachedCanvases.getCanvas(
|
||||||
"pattern",
|
"pattern",
|
||||||
owner.ctx.canvas.width,
|
owner.ctx.canvas.width,
|
||||||
@ -104,8 +104,8 @@ class RadialAxialShadingPattern extends BaseShadingPattern {
|
|||||||
tmpCtx.rect(0, 0, tmpCtx.canvas.width, tmpCtx.canvas.height);
|
tmpCtx.rect(0, 0, tmpCtx.canvas.width, tmpCtx.canvas.height);
|
||||||
|
|
||||||
tmpCtx.setTransform.apply(tmpCtx, owner.baseTransform);
|
tmpCtx.setTransform.apply(tmpCtx, owner.baseTransform);
|
||||||
if (this._matrix) {
|
if (this.matrix) {
|
||||||
tmpCtx.transform.apply(tmpCtx, this._matrix);
|
tmpCtx.transform.apply(tmpCtx, this.matrix);
|
||||||
}
|
}
|
||||||
applyBoundingBox(tmpCtx, this._bbox);
|
applyBoundingBox(tmpCtx, this._bbox);
|
||||||
|
|
||||||
@ -113,12 +113,13 @@ class RadialAxialShadingPattern extends BaseShadingPattern {
|
|||||||
tmpCtx.fill();
|
tmpCtx.fill();
|
||||||
|
|
||||||
pattern = ctx.createPattern(tmpCanvas.canvas, "repeat");
|
pattern = ctx.createPattern(tmpCanvas.canvas, "repeat");
|
||||||
|
this.cachedCanvasPatterns.set(this, pattern);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
|
// Don't bother caching gradients, they are quick to rebuild.
|
||||||
applyBoundingBox(ctx, this._bbox);
|
applyBoundingBox(ctx, this._bbox);
|
||||||
pattern = this._createGradient(ctx);
|
pattern = this._createGradient(ctx);
|
||||||
}
|
}
|
||||||
this._patternCache = pattern;
|
|
||||||
}
|
|
||||||
if (!shadingFill) {
|
if (!shadingFill) {
|
||||||
const domMatrix = new DOMMatrix(inverse);
|
const domMatrix = new DOMMatrix(inverse);
|
||||||
try {
|
try {
|
||||||
@ -305,9 +306,9 @@ class MeshShadingPattern extends BaseShadingPattern {
|
|||||||
this._colors = IR[3];
|
this._colors = IR[3];
|
||||||
this._figures = IR[4];
|
this._figures = IR[4];
|
||||||
this._bounds = IR[5];
|
this._bounds = IR[5];
|
||||||
this._matrix = IR[6];
|
|
||||||
this._bbox = IR[7];
|
this._bbox = IR[7];
|
||||||
this._background = IR[8];
|
this._background = IR[8];
|
||||||
|
this.matrix = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
_createMeshCanvas(combinedScale, backgroundColor, cachedCanvases) {
|
_createMeshCanvas(combinedScale, backgroundColor, cachedCanvases) {
|
||||||
@ -389,8 +390,8 @@ class MeshShadingPattern extends BaseShadingPattern {
|
|||||||
} else {
|
} else {
|
||||||
// Obtain scale from matrix and current transformation matrix.
|
// Obtain scale from matrix and current transformation matrix.
|
||||||
scale = Util.singularValueDecompose2dScale(owner.baseTransform);
|
scale = Util.singularValueDecompose2dScale(owner.baseTransform);
|
||||||
if (this._matrix) {
|
if (this.matrix) {
|
||||||
const matrixScale = Util.singularValueDecompose2dScale(this._matrix);
|
const matrixScale = Util.singularValueDecompose2dScale(this.matrix);
|
||||||
scale = [scale[0] * matrixScale[0], scale[1] * matrixScale[1]];
|
scale = [scale[0] * matrixScale[0], scale[1] * matrixScale[1]];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -405,8 +406,8 @@ class MeshShadingPattern extends BaseShadingPattern {
|
|||||||
|
|
||||||
if (!shadingFill) {
|
if (!shadingFill) {
|
||||||
ctx.setTransform.apply(ctx, owner.baseTransform);
|
ctx.setTransform.apply(ctx, owner.baseTransform);
|
||||||
if (this._matrix) {
|
if (this.matrix) {
|
||||||
ctx.transform.apply(ctx, this._matrix);
|
ctx.transform.apply(ctx, this.matrix);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -426,10 +427,10 @@ class DummyShadingPattern extends BaseShadingPattern {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function getShadingPattern(IR) {
|
function getShadingPattern(IR, cachedCanvasPatterns) {
|
||||||
switch (IR[0]) {
|
switch (IR[0]) {
|
||||||
case "RadialAxial":
|
case "RadialAxial":
|
||||||
return new RadialAxialShadingPattern(IR);
|
return new RadialAxialShadingPattern(IR, cachedCanvasPatterns);
|
||||||
case "Mesh":
|
case "Mesh":
|
||||||
return new MeshShadingPattern(IR);
|
return new MeshShadingPattern(IR);
|
||||||
case "Dummy":
|
case "Dummy":
|
||||||
|
Loading…
Reference in New Issue
Block a user