mirror of
https://github.com/mozilla/pdf.js.git
synced 2026-06-22 16:05:56 +02:00
Merge pull request #21061 from calixteman/pattern_perf
Avoid as much as possible to have intermediate canvases
This commit is contained in:
commit
59c21e3110
@ -29,6 +29,7 @@ import {
|
|||||||
Util,
|
Util,
|
||||||
warn,
|
warn,
|
||||||
} from "../shared/util.js";
|
} from "../shared/util.js";
|
||||||
|
import { CheckedOperatorList, OperatorList } from "./operator_list.js";
|
||||||
import { CMapFactory, IdentityCMap } from "./cmap.js";
|
import { CMapFactory, IdentityCMap } from "./cmap.js";
|
||||||
import { Cmd, Dict, EOF, isName, Name, Ref, RefSet } from "./primitives.js";
|
import { Cmd, Dict, EOF, isName, Name, Ref, RefSet } from "./primitives.js";
|
||||||
import {
|
import {
|
||||||
@ -85,7 +86,6 @@ import { getGlyphsUnicode } from "./glyphlist.js";
|
|||||||
import { getMetrics } from "./metrics.js";
|
import { getMetrics } from "./metrics.js";
|
||||||
import { getUnicodeForGlyph } from "./unicode.js";
|
import { getUnicodeForGlyph } from "./unicode.js";
|
||||||
import { MurmurHash3_64 } from "../shared/murmurhash3.js";
|
import { MurmurHash3_64 } from "../shared/murmurhash3.js";
|
||||||
import { OperatorList } from "./operator_list.js";
|
|
||||||
import { PDFImage } from "./image.js";
|
import { PDFImage } from "./image.js";
|
||||||
import { Stream } from "./stream.js";
|
import { Stream } from "./stream.js";
|
||||||
|
|
||||||
@ -501,7 +501,17 @@ class PartialEvaluator {
|
|||||||
if (optionalContent !== undefined) {
|
if (optionalContent !== undefined) {
|
||||||
operatorList.addOp(OPS.beginMarkedContentProps, ["OC", optionalContent]);
|
operatorList.addOp(OPS.beginMarkedContentProps, ["OC", optionalContent]);
|
||||||
}
|
}
|
||||||
|
|
||||||
const group = dict.get("Group");
|
const group = dict.get("Group");
|
||||||
|
let newOpList;
|
||||||
|
|
||||||
|
// If it's a group, a new canvas will be created that is the size of the
|
||||||
|
// bounding box and translated to the correct position so we don't need to
|
||||||
|
// apply the bounding box to it.
|
||||||
|
const f32matrix = matrix && new Float32Array(matrix);
|
||||||
|
const args = [f32matrix, (!group && f32bbox) || null];
|
||||||
|
const localResources = dict.get("Resources");
|
||||||
|
|
||||||
if (group) {
|
if (group) {
|
||||||
groupOptions = {
|
groupOptions = {
|
||||||
matrix,
|
matrix,
|
||||||
@ -509,6 +519,7 @@ class PartialEvaluator {
|
|||||||
smask,
|
smask,
|
||||||
isolated: false,
|
isolated: false,
|
||||||
knockout: false,
|
knockout: false,
|
||||||
|
needsIsolation: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
const groupSubtype = group.get("S");
|
const groupSubtype = group.get("S");
|
||||||
@ -532,30 +543,30 @@ class PartialEvaluator {
|
|||||||
smask.backdrop = colorSpace.getRgbHex(smask.backdrop, 0);
|
smask.backdrop = colorSpace.getRgbHex(smask.backdrop, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
operatorList.addOp(OPS.beginGroup, [groupOptions]);
|
newOpList = new CheckedOperatorList();
|
||||||
|
} else {
|
||||||
|
newOpList = operatorList;
|
||||||
|
operatorList.addOp(OPS.paintFormXObjectBegin, args);
|
||||||
}
|
}
|
||||||
|
|
||||||
// If it's a group, a new canvas will be created that is the size of the
|
|
||||||
// bounding box and translated to the correct position so we don't need to
|
|
||||||
// apply the bounding box to it.
|
|
||||||
const f32matrix = matrix && new Float32Array(matrix);
|
|
||||||
const args = [f32matrix, (!group && f32bbox) || null];
|
|
||||||
operatorList.addOp(OPS.paintFormXObjectBegin, args);
|
|
||||||
|
|
||||||
const localResources = dict.get("Resources");
|
|
||||||
|
|
||||||
await this.getOperatorList({
|
await this.getOperatorList({
|
||||||
stream: xobj,
|
stream: xobj,
|
||||||
task,
|
task,
|
||||||
resources: localResources instanceof Dict ? localResources : resources,
|
resources: localResources instanceof Dict ? localResources : resources,
|
||||||
operatorList,
|
operatorList: newOpList,
|
||||||
initialState,
|
initialState,
|
||||||
prevRefs: seenRefs,
|
prevRefs: seenRefs,
|
||||||
});
|
});
|
||||||
operatorList.addOp(OPS.paintFormXObjectEnd, []);
|
|
||||||
|
|
||||||
if (group) {
|
if (group) {
|
||||||
|
groupOptions.needsIsolation = newOpList.needsIsolation || !!smask;
|
||||||
|
operatorList.addOp(OPS.beginGroup, [groupOptions]);
|
||||||
|
operatorList.addOp(OPS.paintFormXObjectBegin, args);
|
||||||
|
operatorList.addOpList(newOpList);
|
||||||
|
operatorList.addOp(OPS.paintFormXObjectEnd, []);
|
||||||
operatorList.addOp(OPS.endGroup, [groupOptions]);
|
operatorList.addOp(OPS.endGroup, [groupOptions]);
|
||||||
|
} else {
|
||||||
|
operatorList.addOp(OPS.paintFormXObjectEnd, []);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (optionalContent !== undefined) {
|
if (optionalContent !== undefined) {
|
||||||
@ -972,7 +983,7 @@ class PartialEvaluator {
|
|||||||
localTilingPatternCache
|
localTilingPatternCache
|
||||||
) {
|
) {
|
||||||
// Create an IR of the pattern code.
|
// Create an IR of the pattern code.
|
||||||
const tilingOpList = new OperatorList();
|
const tilingOpList = new CheckedOperatorList();
|
||||||
// Merge the available resources, to prevent issues when the patternDict
|
// Merge the available resources, to prevent issues when the patternDict
|
||||||
// is missing some /Resources entries (fixes issue6541.pdf).
|
// is missing some /Resources entries (fixes issue6541.pdf).
|
||||||
const patternResources = Dict.merge({
|
const patternResources = Dict.merge({
|
||||||
@ -988,10 +999,12 @@ class PartialEvaluator {
|
|||||||
})
|
})
|
||||||
.then(function () {
|
.then(function () {
|
||||||
const operatorListIR = tilingOpList.getIR();
|
const operatorListIR = tilingOpList.getIR();
|
||||||
|
const { needsIsolation } = tilingOpList;
|
||||||
const tilingPatternIR = getTilingPatternIR(
|
const tilingPatternIR = getTilingPatternIR(
|
||||||
operatorListIR,
|
operatorListIR,
|
||||||
patternDict,
|
patternDict,
|
||||||
color
|
color,
|
||||||
|
needsIsolation
|
||||||
);
|
);
|
||||||
// Add the dependencies to the parent operator list so they are
|
// Add the dependencies to the parent operator list so they are
|
||||||
// resolved before the sub operator list is executed synchronously.
|
// resolved before the sub operator list is executed synchronously.
|
||||||
@ -1001,6 +1014,7 @@ class PartialEvaluator {
|
|||||||
if (patternDict.objId) {
|
if (patternDict.objId) {
|
||||||
localTilingPatternCache.set(/* name = */ null, patternDict.objId, {
|
localTilingPatternCache.set(/* name = */ null, patternDict.objId, {
|
||||||
operatorListIR,
|
operatorListIR,
|
||||||
|
needsIsolation,
|
||||||
dict: patternDict,
|
dict: patternDict,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -1578,7 +1592,8 @@ class PartialEvaluator {
|
|||||||
const tilingPatternIR = getTilingPatternIR(
|
const tilingPatternIR = getTilingPatternIR(
|
||||||
localTilingPattern.operatorListIR,
|
localTilingPattern.operatorListIR,
|
||||||
localTilingPattern.dict,
|
localTilingPattern.dict,
|
||||||
color
|
color,
|
||||||
|
localTilingPattern.needsIsolation
|
||||||
);
|
);
|
||||||
operatorList.addOp(fn, tilingPatternIR);
|
operatorList.addOp(fn, tilingPatternIR);
|
||||||
return undefined;
|
return undefined;
|
||||||
|
|||||||
@ -822,4 +822,35 @@ class OperatorList {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export { OperatorList };
|
/**
|
||||||
|
* A subclass of OperatorList that checks whether added group or pattern
|
||||||
|
* operations require being drawn in isolation (i.e. on a separate canvas).
|
||||||
|
* A group/pattern needs isolation when it uses non-default compositing
|
||||||
|
* (blend mode) or a soft mask. The result is exposed via `needsIsolation`.
|
||||||
|
*/
|
||||||
|
class CheckedOperatorList extends OperatorList {
|
||||||
|
needsIsolation = false;
|
||||||
|
|
||||||
|
addOp(fn, args) {
|
||||||
|
if (!this.needsIsolation) {
|
||||||
|
if (fn === OPS.beginGroup) {
|
||||||
|
// Propagate isolation only if the nested group itself needs it.
|
||||||
|
this.needsIsolation = args[0].needsIsolation;
|
||||||
|
} else if (fn === OPS.setGState) {
|
||||||
|
for (const [key, val] of args[0]) {
|
||||||
|
if (key === "BM" && val !== "source-over") {
|
||||||
|
this.needsIsolation = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (key === "SMask" && val !== false) {
|
||||||
|
this.needsIsolation = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
super.addOp(fn, args);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export { CheckedOperatorList, OperatorList };
|
||||||
|
|||||||
@ -1139,7 +1139,7 @@ class DummyShading extends BaseShading {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function getTilingPatternIR(operatorList, dict, color) {
|
function getTilingPatternIR(operatorList, dict, color, needsIsolation = true) {
|
||||||
const matrix = lookupMatrix(dict.getArray("Matrix"), IDENTITY_MATRIX);
|
const matrix = lookupMatrix(dict.getArray("Matrix"), IDENTITY_MATRIX);
|
||||||
const bbox = lookupNormalRect(dict.getArray("BBox"), null);
|
const bbox = lookupNormalRect(dict.getArray("BBox"), null);
|
||||||
// Ensure that the pattern has a non-zero width and height, to prevent errors
|
// Ensure that the pattern has a non-zero width and height, to prevent errors
|
||||||
@ -1174,6 +1174,7 @@ function getTilingPatternIR(operatorList, dict, color) {
|
|||||||
ystep,
|
ystep,
|
||||||
paintType,
|
paintType,
|
||||||
tilingType,
|
tilingType,
|
||||||
|
needsIsolation,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -2608,7 +2608,8 @@ class CanvasGraphics {
|
|||||||
this.save(opIdx);
|
this.save(opIdx);
|
||||||
// If there's an active soft mask we don't want it enabled for the group, so
|
// If there's an active soft mask we don't want it enabled for the group, so
|
||||||
// clear it out. The mask and suspended canvas will be restored in endGroup.
|
// clear it out. The mask and suspended canvas will be restored in endGroup.
|
||||||
if (this.inSMaskMode) {
|
const { inSMaskMode } = this;
|
||||||
|
if (inSMaskMode) {
|
||||||
this.endSMaskMode();
|
this.endSMaskMode();
|
||||||
this.current.activeSMask = null;
|
this.current.activeSMask = null;
|
||||||
}
|
}
|
||||||
@ -2637,6 +2638,28 @@ class CanvasGraphics {
|
|||||||
warn("Knockout groups not supported.");
|
warn("Knockout groups not supported.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
!group.needsIsolation &&
|
||||||
|
currentCtx.globalAlpha === 1 &&
|
||||||
|
currentCtx.globalCompositeOperation === "source-over" &&
|
||||||
|
!inSMaskMode
|
||||||
|
) {
|
||||||
|
if (group.bbox) {
|
||||||
|
let clip = new Path2D();
|
||||||
|
const [x0, y0, x1, y1] = group.bbox;
|
||||||
|
clip.rect(x0, y0, x1 - x0, y1 - y0);
|
||||||
|
if (group.matrix) {
|
||||||
|
const path = new Path2D();
|
||||||
|
path.addPath(clip, new DOMMatrix(group.matrix));
|
||||||
|
clip = path;
|
||||||
|
}
|
||||||
|
currentCtx.clip(clip);
|
||||||
|
}
|
||||||
|
this.groupStack.push(null); // null = no intermediate canvas
|
||||||
|
this.groupLevel++;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const currentTransform = getCurrentTransform(currentCtx);
|
const currentTransform = getCurrentTransform(currentCtx);
|
||||||
if (group.matrix) {
|
if (group.matrix) {
|
||||||
currentCtx.transform(...group.matrix);
|
currentCtx.transform(...group.matrix);
|
||||||
@ -2756,6 +2779,12 @@ class CanvasGraphics {
|
|||||||
this.groupLevel--;
|
this.groupLevel--;
|
||||||
const groupCtx = this.ctx;
|
const groupCtx = this.ctx;
|
||||||
const ctx = this.groupStack.pop();
|
const ctx = this.groupStack.pop();
|
||||||
|
if (ctx === null) {
|
||||||
|
// Simple group: content was drawn directly on the parent canvas.
|
||||||
|
this.restore(opIdx);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
this.ctx = ctx;
|
this.ctx = ctx;
|
||||||
// Turn off image smoothing to avoid sub pixel interpolation which can
|
// Turn off image smoothing to avoid sub pixel interpolation which can
|
||||||
// look kind of blurry for some pdfs.
|
// look kind of blurry for some pdfs.
|
||||||
|
|||||||
@ -614,6 +614,7 @@ class TilingPattern {
|
|||||||
this.ystep = IR[6];
|
this.ystep = IR[6];
|
||||||
this.paintType = IR[7];
|
this.paintType = IR[7];
|
||||||
this.tilingType = IR[8];
|
this.tilingType = IR[8];
|
||||||
|
this.needsIsolation = IR[9] ?? true;
|
||||||
this.ctx = ctx;
|
this.ctx = ctx;
|
||||||
this.canvasGraphicsFactory = canvasGraphicsFactory;
|
this.canvasGraphicsFactory = canvasGraphicsFactory;
|
||||||
this.baseTransform = baseTransform;
|
this.baseTransform = baseTransform;
|
||||||
@ -702,23 +703,6 @@ class TilingPattern {
|
|||||||
// Draws a single tile directly onto owner, clipped to path.
|
// Draws a single tile directly onto owner, clipped to path.
|
||||||
drawPattern(owner, path, useEOFill = false, [n, m], opIdx) {
|
drawPattern(owner, path, useEOFill = false, [n, m], opIdx) {
|
||||||
const [x0, y0, x1, y1] = this.bbox;
|
const [x0, y0, x1, y1] = this.bbox;
|
||||||
const bboxWidth = x1 - x0;
|
|
||||||
const bboxHeight = y1 - y0;
|
|
||||||
|
|
||||||
const [combinedScaleX, combinedScaleY] = this._getCombinedScales();
|
|
||||||
const dimx = this.getSizeAndScale(
|
|
||||||
bboxWidth,
|
|
||||||
this.ctx.canvas.width,
|
|
||||||
combinedScaleX
|
|
||||||
);
|
|
||||||
const dimy = this.getSizeAndScale(
|
|
||||||
bboxHeight,
|
|
||||||
this.ctx.canvas.height,
|
|
||||||
combinedScaleY
|
|
||||||
);
|
|
||||||
|
|
||||||
// Isolate blend modes from the main canvas.
|
|
||||||
const tmpCanvas = this._renderTileCanvas(owner, opIdx, dimx, dimy);
|
|
||||||
|
|
||||||
owner.save();
|
owner.save();
|
||||||
if (useEOFill) {
|
if (useEOFill) {
|
||||||
@ -730,9 +714,39 @@ class TilingPattern {
|
|||||||
// by setTransform.
|
// by setTransform.
|
||||||
owner.ctx.setTransform(...this.patternBaseMatrix);
|
owner.ctx.setTransform(...this.patternBaseMatrix);
|
||||||
owner.ctx.translate(n * this.xstep, m * this.ystep);
|
owner.ctx.translate(n * this.xstep, m * this.ystep);
|
||||||
owner.ctx.drawImage(tmpCanvas.canvas, x0, y0, bboxWidth, bboxHeight);
|
if (
|
||||||
|
this.needsIsolation ||
|
||||||
|
owner.ctx.globalAlpha !== 1 ||
|
||||||
|
owner.ctx.globalCompositeOperation !== "source-over" ||
|
||||||
|
owner.inSMaskMode
|
||||||
|
) {
|
||||||
|
const bboxWidth = x1 - x0;
|
||||||
|
const bboxHeight = y1 - y0;
|
||||||
|
const [combinedScaleX, combinedScaleY] = this._getCombinedScales();
|
||||||
|
const dimx = this.getSizeAndScale(
|
||||||
|
bboxWidth,
|
||||||
|
this.ctx.canvas.width,
|
||||||
|
combinedScaleX
|
||||||
|
);
|
||||||
|
const dimy = this.getSizeAndScale(
|
||||||
|
bboxHeight,
|
||||||
|
this.ctx.canvas.height,
|
||||||
|
combinedScaleY
|
||||||
|
);
|
||||||
|
// Isolate blend modes from the main canvas.
|
||||||
|
const tmpCanvas = this._renderTileCanvas(owner, opIdx, dimx, dimy);
|
||||||
|
owner.ctx.drawImage(tmpCanvas.canvas, x0, y0, bboxWidth, bboxHeight);
|
||||||
|
owner.canvasFactory.destroy(tmpCanvas);
|
||||||
|
} else {
|
||||||
|
// No blend modes or transparency: render the tile directly onto owner.
|
||||||
|
this.setFillAndStrokeStyleToContext(owner, this.paintType, this.color);
|
||||||
|
this.clipBbox(owner, x0, y0, x1, y1);
|
||||||
|
owner.baseTransformStack.push(owner.baseTransform);
|
||||||
|
owner.baseTransform = getCurrentTransform(owner.ctx);
|
||||||
|
owner.executeOperatorList(this.operatorList);
|
||||||
|
owner.baseTransform = owner.baseTransformStack.pop();
|
||||||
|
}
|
||||||
|
|
||||||
owner.canvasFactory.destroy(tmpCanvas);
|
|
||||||
owner.restore();
|
owner.restore();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -892,14 +906,15 @@ class TilingPattern {
|
|||||||
clipBbox(graphics, x0, y0, x1, y1) {
|
clipBbox(graphics, x0, y0, x1, y1) {
|
||||||
const bboxWidth = x1 - x0;
|
const bboxWidth = x1 - x0;
|
||||||
const bboxHeight = y1 - y0;
|
const bboxHeight = y1 - y0;
|
||||||
graphics.ctx.rect(x0, y0, bboxWidth, bboxHeight);
|
const clip = new Path2D();
|
||||||
|
clip.rect(x0, y0, bboxWidth, bboxHeight);
|
||||||
Util.axialAlignedBoundingBox(
|
Util.axialAlignedBoundingBox(
|
||||||
[x0, y0, x1, y1],
|
[x0, y0, x1, y1],
|
||||||
getCurrentTransform(graphics.ctx),
|
getCurrentTransform(graphics.ctx),
|
||||||
graphics.current.minMax
|
graphics.current.minMax
|
||||||
);
|
);
|
||||||
graphics.clip();
|
graphics.ctx.clip(clip);
|
||||||
graphics.endPath();
|
graphics.current.updateClipFromPath();
|
||||||
}
|
}
|
||||||
|
|
||||||
setFillAndStrokeStyleToContext(graphics, paintType, color) {
|
setFillAndStrokeStyleToContext(graphics, paintType, color) {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user