mirror of
https://github.com/mozilla/pdf.js.git
synced 2025-04-22 16:18:08 +02:00
Improve performance of reused patterns.
Bug 1721218 has a shading pattern that was used thousands of times. To improve performance of this PDF: - add a cache for patterns in the evaluator and only send the IR form once to the main thread (this also makes caching in canvas easier) - cache the created canvas radial/axial patterns - for shading fill radial/axial use the pattern directly instead of creating temporary canvas
This commit is contained in:
parent
2cf90cd9ad
commit
da1af02ac8
7 changed files with 121 additions and 59 deletions
|
@ -1314,6 +1314,35 @@ class PartialEvaluator {
|
|||
});
|
||||
}
|
||||
|
||||
parseShading({
|
||||
keyObj,
|
||||
shading,
|
||||
resources,
|
||||
localColorSpaceCache,
|
||||
localShadingPatternCache,
|
||||
matrix = null,
|
||||
}) {
|
||||
// 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.
|
||||
let id = localShadingPatternCache.get(keyObj);
|
||||
if (!id) {
|
||||
var shadingFill = Pattern.parseShading(
|
||||
shading,
|
||||
matrix,
|
||||
this.xref,
|
||||
resources,
|
||||
this.handler,
|
||||
this._pdfFunctionFactory,
|
||||
localColorSpaceCache
|
||||
);
|
||||
const patternIR = shadingFill.getIR();
|
||||
id = `pattern_${this.idFactory.createObjId()}`;
|
||||
localShadingPatternCache.set(keyObj, id);
|
||||
this.handler.send("obj", [id, this.pageIndex, "Pattern", patternIR]);
|
||||
}
|
||||
return id;
|
||||
}
|
||||
|
||||
handleColorN(
|
||||
operatorList,
|
||||
fn,
|
||||
|
@ -1323,7 +1352,8 @@ class PartialEvaluator {
|
|||
resources,
|
||||
task,
|
||||
localColorSpaceCache,
|
||||
localTilingPatternCache
|
||||
localTilingPatternCache,
|
||||
localShadingPatternCache
|
||||
) {
|
||||
// compile tiling patterns
|
||||
const patternName = args.pop();
|
||||
|
@ -1350,7 +1380,7 @@ class PartialEvaluator {
|
|||
// if and only if there are PDF documents where doing so would
|
||||
// significantly improve performance.
|
||||
|
||||
let pattern = patterns.get(name);
|
||||
const pattern = patterns.get(name);
|
||||
if (pattern) {
|
||||
const dict = isStream(pattern) ? pattern.dict : pattern;
|
||||
const typeNum = dict.get("PatternType");
|
||||
|
@ -1371,16 +1401,15 @@ class PartialEvaluator {
|
|||
} else if (typeNum === PatternType.SHADING) {
|
||||
const shading = dict.get("Shading");
|
||||
const matrix = dict.getArray("Matrix");
|
||||
pattern = Pattern.parseShading(
|
||||
const objId = this.parseShading({
|
||||
keyObj: pattern,
|
||||
shading,
|
||||
matrix,
|
||||
this.xref,
|
||||
resources,
|
||||
this.handler,
|
||||
this._pdfFunctionFactory,
|
||||
localColorSpaceCache
|
||||
);
|
||||
operatorList.addOp(fn, pattern.getIR());
|
||||
localColorSpaceCache,
|
||||
localShadingPatternCache,
|
||||
});
|
||||
operatorList.addOp(fn, ["Shading", objId]);
|
||||
return undefined;
|
||||
}
|
||||
throw new FormatError(`Unknown PatternType: ${typeNum}`);
|
||||
|
@ -1513,6 +1542,7 @@ class PartialEvaluator {
|
|||
const localColorSpaceCache = new LocalColorSpaceCache();
|
||||
const localGStateCache = new LocalGStateCache();
|
||||
const localTilingPatternCache = new LocalTilingPatternCache();
|
||||
const localShadingPatternCache = new Map();
|
||||
|
||||
const xobjs = resources.get("XObject") || Dict.empty;
|
||||
const patterns = resources.get("Pattern") || Dict.empty;
|
||||
|
@ -1869,7 +1899,8 @@ class PartialEvaluator {
|
|||
resources,
|
||||
task,
|
||||
localColorSpaceCache,
|
||||
localTilingPatternCache
|
||||
localTilingPatternCache,
|
||||
localShadingPatternCache
|
||||
)
|
||||
);
|
||||
return;
|
||||
|
@ -1890,7 +1921,8 @@ class PartialEvaluator {
|
|||
resources,
|
||||
task,
|
||||
localColorSpaceCache,
|
||||
localTilingPatternCache
|
||||
localTilingPatternCache,
|
||||
localShadingPatternCache
|
||||
)
|
||||
);
|
||||
return;
|
||||
|
@ -1909,18 +1941,14 @@ class PartialEvaluator {
|
|||
if (!shading) {
|
||||
throw new FormatError("No shading object found");
|
||||
}
|
||||
|
||||
var shadingFill = Pattern.parseShading(
|
||||
const patternId = self.parseShading({
|
||||
keyObj: shading,
|
||||
shading,
|
||||
null,
|
||||
xref,
|
||||
resources,
|
||||
self.handler,
|
||||
self._pdfFunctionFactory,
|
||||
localColorSpaceCache
|
||||
);
|
||||
var patternIR = shadingFill.getIR();
|
||||
args = [patternIR];
|
||||
localColorSpaceCache,
|
||||
localShadingPatternCache,
|
||||
});
|
||||
args = [patternId];
|
||||
fn = OPS.shadingFill;
|
||||
break;
|
||||
case OPS.setGState:
|
||||
|
|
|
@ -2707,6 +2707,9 @@ class WorkerTransport {
|
|||
pageProxy.cleanupAfterRender = true;
|
||||
}
|
||||
break;
|
||||
case "Pattern":
|
||||
pageProxy.objs.resolve(id, imageData);
|
||||
break;
|
||||
default:
|
||||
throw new Error(`Got unknown object type ${type}`);
|
||||
}
|
||||
|
|
|
@ -872,6 +872,7 @@ const CanvasGraphics = (function CanvasGraphicsClosure() {
|
|||
this.markedContentStack = [];
|
||||
this.optionalContentConfig = optionalContentConfig;
|
||||
this.cachedCanvases = new CachedCanvases(this.canvasFactory);
|
||||
this.cachedPatterns = new Map();
|
||||
if (canvasCtx) {
|
||||
// NOTE: if mozCurrentTransform is polyfilled, then the current state of
|
||||
// the transformation must already be set in canvasCtx._transformMatrix.
|
||||
|
@ -1025,6 +1026,7 @@ const CanvasGraphics = (function CanvasGraphicsClosure() {
|
|||
}
|
||||
|
||||
this.cachedCanvases.clear();
|
||||
this.cachedPatterns.clear();
|
||||
|
||||
if (this.imageLayer) {
|
||||
this.imageLayer.endLayout();
|
||||
|
@ -2132,7 +2134,7 @@ const CanvasGraphics = (function CanvasGraphicsClosure() {
|
|||
baseTransform
|
||||
);
|
||||
} else {
|
||||
pattern = getShadingPattern(IR);
|
||||
pattern = this._getPattern(IR[1]);
|
||||
}
|
||||
return pattern;
|
||||
}
|
||||
|
@ -2159,14 +2161,23 @@ const CanvasGraphics = (function CanvasGraphicsClosure() {
|
|||
this.current.patternFill = false;
|
||||
}
|
||||
|
||||
shadingFill(patternIR) {
|
||||
_getPattern(objId) {
|
||||
if (this.cachedPatterns.has(objId)) {
|
||||
return this.cachedPatterns.get(objId);
|
||||
}
|
||||
const pattern = getShadingPattern(this.objs.get(objId));
|
||||
this.cachedPatterns.set(objId, pattern);
|
||||
return pattern;
|
||||
}
|
||||
|
||||
shadingFill(objId) {
|
||||
if (!this.contentVisible) {
|
||||
return;
|
||||
}
|
||||
const ctx = this.ctx;
|
||||
|
||||
this.save();
|
||||
const pattern = getShadingPattern(patternIR);
|
||||
const pattern = this._getPattern(objId);
|
||||
ctx.fillStyle = pattern.getPattern(
|
||||
ctx,
|
||||
this,
|
||||
|
|
|
@ -56,41 +56,20 @@ class RadialAxialShadingPattern extends BaseShadingPattern {
|
|||
this._r0 = IR[6];
|
||||
this._r1 = IR[7];
|
||||
this._matrix = IR[8];
|
||||
this._patternCache = null;
|
||||
}
|
||||
|
||||
getPattern(ctx, owner, inverse, shadingFill = false) {
|
||||
const tmpCanvas = owner.cachedCanvases.getCanvas(
|
||||
"pattern",
|
||||
owner.ctx.canvas.width,
|
||||
owner.ctx.canvas.height,
|
||||
true
|
||||
);
|
||||
|
||||
const tmpCtx = tmpCanvas.context;
|
||||
tmpCtx.clearRect(0, 0, tmpCtx.canvas.width, tmpCtx.canvas.height);
|
||||
tmpCtx.beginPath();
|
||||
tmpCtx.rect(0, 0, tmpCtx.canvas.width, tmpCtx.canvas.height);
|
||||
|
||||
if (!shadingFill) {
|
||||
tmpCtx.setTransform.apply(tmpCtx, owner.baseTransform);
|
||||
if (this._matrix) {
|
||||
tmpCtx.transform.apply(tmpCtx, this._matrix);
|
||||
}
|
||||
} else {
|
||||
tmpCtx.setTransform.apply(tmpCtx, ctx.mozCurrentTransform);
|
||||
}
|
||||
applyBoundingBox(tmpCtx, this._bbox);
|
||||
|
||||
_createGradient(ctx) {
|
||||
let grad;
|
||||
if (this._type === "axial") {
|
||||
grad = tmpCtx.createLinearGradient(
|
||||
grad = ctx.createLinearGradient(
|
||||
this._p0[0],
|
||||
this._p0[1],
|
||||
this._p1[0],
|
||||
this._p1[1]
|
||||
);
|
||||
} else if (this._type === "radial") {
|
||||
grad = tmpCtx.createRadialGradient(
|
||||
grad = ctx.createRadialGradient(
|
||||
this._p0[0],
|
||||
this._p0[1],
|
||||
this._r0,
|
||||
|
@ -103,18 +82,52 @@ class RadialAxialShadingPattern extends BaseShadingPattern {
|
|||
for (const colorStop of this._colorStops) {
|
||||
grad.addColorStop(colorStop[0], colorStop[1]);
|
||||
}
|
||||
tmpCtx.fillStyle = grad;
|
||||
tmpCtx.fill();
|
||||
return grad;
|
||||
}
|
||||
|
||||
const domMatrix = new DOMMatrix(inverse);
|
||||
getPattern(ctx, owner, inverse, shadingFill = false) {
|
||||
let pattern;
|
||||
if (this._patternCache) {
|
||||
pattern = this._patternCache;
|
||||
} else {
|
||||
if (!shadingFill) {
|
||||
const tmpCanvas = owner.cachedCanvases.getCanvas(
|
||||
"pattern",
|
||||
owner.ctx.canvas.width,
|
||||
owner.ctx.canvas.height,
|
||||
true
|
||||
);
|
||||
|
||||
const pattern = ctx.createPattern(tmpCanvas.canvas, "repeat");
|
||||
try {
|
||||
pattern.setTransform(domMatrix);
|
||||
} catch (ex) {
|
||||
// Avoid rendering breaking completely in Firefox 78 ESR,
|
||||
// and in Node.js (see issue 13724).
|
||||
warn(`RadialAxialShadingPattern.getPattern: "${ex?.message}".`);
|
||||
const tmpCtx = tmpCanvas.context;
|
||||
tmpCtx.clearRect(0, 0, tmpCtx.canvas.width, tmpCtx.canvas.height);
|
||||
tmpCtx.beginPath();
|
||||
tmpCtx.rect(0, 0, tmpCtx.canvas.width, tmpCtx.canvas.height);
|
||||
|
||||
tmpCtx.setTransform.apply(tmpCtx, owner.baseTransform);
|
||||
if (this._matrix) {
|
||||
tmpCtx.transform.apply(tmpCtx, this._matrix);
|
||||
}
|
||||
applyBoundingBox(tmpCtx, this._bbox);
|
||||
|
||||
tmpCtx.fillStyle = this._createGradient(tmpCtx);
|
||||
tmpCtx.fill();
|
||||
|
||||
pattern = ctx.createPattern(tmpCanvas.canvas, "repeat");
|
||||
} else {
|
||||
applyBoundingBox(ctx, this._bbox);
|
||||
pattern = this._createGradient(ctx);
|
||||
}
|
||||
this._patternCache = pattern;
|
||||
}
|
||||
if (!shadingFill) {
|
||||
const domMatrix = new DOMMatrix(inverse);
|
||||
try {
|
||||
pattern.setTransform(domMatrix);
|
||||
} catch (ex) {
|
||||
// Avoid rendering breaking completely in Firefox 78 ESR,
|
||||
// and in Node.js (see issue 13724).
|
||||
warn(`RadialAxialShadingPattern.getPattern: "${ex?.message}".`);
|
||||
}
|
||||
}
|
||||
return pattern;
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue