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,
|
||||
warn,
|
||||
} from "../shared/util.js";
|
||||
import { CheckedOperatorList, OperatorList } from "./operator_list.js";
|
||||
import { CMapFactory, IdentityCMap } from "./cmap.js";
|
||||
import { Cmd, Dict, EOF, isName, Name, Ref, RefSet } from "./primitives.js";
|
||||
import {
|
||||
@ -85,7 +86,6 @@ import { getGlyphsUnicode } from "./glyphlist.js";
|
||||
import { getMetrics } from "./metrics.js";
|
||||
import { getUnicodeForGlyph } from "./unicode.js";
|
||||
import { MurmurHash3_64 } from "../shared/murmurhash3.js";
|
||||
import { OperatorList } from "./operator_list.js";
|
||||
import { PDFImage } from "./image.js";
|
||||
import { Stream } from "./stream.js";
|
||||
|
||||
@ -501,7 +501,17 @@ class PartialEvaluator {
|
||||
if (optionalContent !== undefined) {
|
||||
operatorList.addOp(OPS.beginMarkedContentProps, ["OC", optionalContent]);
|
||||
}
|
||||
|
||||
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) {
|
||||
groupOptions = {
|
||||
matrix,
|
||||
@ -509,6 +519,7 @@ class PartialEvaluator {
|
||||
smask,
|
||||
isolated: false,
|
||||
knockout: false,
|
||||
needsIsolation: false,
|
||||
};
|
||||
|
||||
const groupSubtype = group.get("S");
|
||||
@ -532,30 +543,30 @@ class PartialEvaluator {
|
||||
smask.backdrop = colorSpace.getRgbHex(smask.backdrop, 0);
|
||||
}
|
||||
|
||||
operatorList.addOp(OPS.beginGroup, [groupOptions]);
|
||||
}
|
||||
|
||||
// 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];
|
||||
newOpList = new CheckedOperatorList();
|
||||
} else {
|
||||
newOpList = operatorList;
|
||||
operatorList.addOp(OPS.paintFormXObjectBegin, args);
|
||||
|
||||
const localResources = dict.get("Resources");
|
||||
}
|
||||
|
||||
await this.getOperatorList({
|
||||
stream: xobj,
|
||||
task,
|
||||
resources: localResources instanceof Dict ? localResources : resources,
|
||||
operatorList,
|
||||
operatorList: newOpList,
|
||||
initialState,
|
||||
prevRefs: seenRefs,
|
||||
});
|
||||
operatorList.addOp(OPS.paintFormXObjectEnd, []);
|
||||
|
||||
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]);
|
||||
} else {
|
||||
operatorList.addOp(OPS.paintFormXObjectEnd, []);
|
||||
}
|
||||
|
||||
if (optionalContent !== undefined) {
|
||||
@ -972,7 +983,7 @@ class PartialEvaluator {
|
||||
localTilingPatternCache
|
||||
) {
|
||||
// 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
|
||||
// is missing some /Resources entries (fixes issue6541.pdf).
|
||||
const patternResources = Dict.merge({
|
||||
@ -988,10 +999,12 @@ class PartialEvaluator {
|
||||
})
|
||||
.then(function () {
|
||||
const operatorListIR = tilingOpList.getIR();
|
||||
const { needsIsolation } = tilingOpList;
|
||||
const tilingPatternIR = getTilingPatternIR(
|
||||
operatorListIR,
|
||||
patternDict,
|
||||
color
|
||||
color,
|
||||
needsIsolation
|
||||
);
|
||||
// Add the dependencies to the parent operator list so they are
|
||||
// resolved before the sub operator list is executed synchronously.
|
||||
@ -1001,6 +1014,7 @@ class PartialEvaluator {
|
||||
if (patternDict.objId) {
|
||||
localTilingPatternCache.set(/* name = */ null, patternDict.objId, {
|
||||
operatorListIR,
|
||||
needsIsolation,
|
||||
dict: patternDict,
|
||||
});
|
||||
}
|
||||
@ -1578,7 +1592,8 @@ class PartialEvaluator {
|
||||
const tilingPatternIR = getTilingPatternIR(
|
||||
localTilingPattern.operatorListIR,
|
||||
localTilingPattern.dict,
|
||||
color
|
||||
color,
|
||||
localTilingPattern.needsIsolation
|
||||
);
|
||||
operatorList.addOp(fn, tilingPatternIR);
|
||||
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 bbox = lookupNormalRect(dict.getArray("BBox"), null);
|
||||
// Ensure that the pattern has a non-zero width and height, to prevent errors
|
||||
@ -1174,6 +1174,7 @@ function getTilingPatternIR(operatorList, dict, color) {
|
||||
ystep,
|
||||
paintType,
|
||||
tilingType,
|
||||
needsIsolation,
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
@ -2608,7 +2608,8 @@ class CanvasGraphics {
|
||||
this.save(opIdx);
|
||||
// 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.
|
||||
if (this.inSMaskMode) {
|
||||
const { inSMaskMode } = this;
|
||||
if (inSMaskMode) {
|
||||
this.endSMaskMode();
|
||||
this.current.activeSMask = null;
|
||||
}
|
||||
@ -2637,6 +2638,28 @@ class CanvasGraphics {
|
||||
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);
|
||||
if (group.matrix) {
|
||||
currentCtx.transform(...group.matrix);
|
||||
@ -2756,6 +2779,12 @@ class CanvasGraphics {
|
||||
this.groupLevel--;
|
||||
const groupCtx = this.ctx;
|
||||
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;
|
||||
// Turn off image smoothing to avoid sub pixel interpolation which can
|
||||
// look kind of blurry for some pdfs.
|
||||
|
||||
@ -614,6 +614,7 @@ class TilingPattern {
|
||||
this.ystep = IR[6];
|
||||
this.paintType = IR[7];
|
||||
this.tilingType = IR[8];
|
||||
this.needsIsolation = IR[9] ?? true;
|
||||
this.ctx = ctx;
|
||||
this.canvasGraphicsFactory = canvasGraphicsFactory;
|
||||
this.baseTransform = baseTransform;
|
||||
@ -702,23 +703,6 @@ class TilingPattern {
|
||||
// Draws a single tile directly onto owner, clipped to path.
|
||||
drawPattern(owner, path, useEOFill = false, [n, m], opIdx) {
|
||||
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();
|
||||
if (useEOFill) {
|
||||
@ -730,9 +714,39 @@ class TilingPattern {
|
||||
// by setTransform.
|
||||
owner.ctx.setTransform(...this.patternBaseMatrix);
|
||||
owner.ctx.translate(n * this.xstep, m * this.ystep);
|
||||
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.restore();
|
||||
}
|
||||
|
||||
@ -892,14 +906,15 @@ class TilingPattern {
|
||||
clipBbox(graphics, x0, y0, x1, y1) {
|
||||
const bboxWidth = x1 - x0;
|
||||
const bboxHeight = y1 - y0;
|
||||
graphics.ctx.rect(x0, y0, bboxWidth, bboxHeight);
|
||||
const clip = new Path2D();
|
||||
clip.rect(x0, y0, bboxWidth, bboxHeight);
|
||||
Util.axialAlignedBoundingBox(
|
||||
[x0, y0, x1, y1],
|
||||
getCurrentTransform(graphics.ctx),
|
||||
graphics.current.minMax
|
||||
);
|
||||
graphics.clip();
|
||||
graphics.endPath();
|
||||
graphics.ctx.clip(clip);
|
||||
graphics.current.updateClipFromPath();
|
||||
}
|
||||
|
||||
setFillAndStrokeStyleToContext(graphics, paintType, color) {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user