Track the clipping box and bounding box of the path.
This allows us to compose much smaller regions of soft mask making them much faster. This should also allow for further optimizations in the pattern code. For example locally I see issue #6573 go from 55s to 5s with this change. Fixes #6573
This commit is contained in:
parent
2d1f9ff7a3
commit
82681ea20c
@ -579,7 +579,7 @@ function compileType3Glyph(imgData) {
|
||||
}
|
||||
|
||||
class CanvasExtraState {
|
||||
constructor() {
|
||||
constructor(width, height) {
|
||||
// Are soft masks and alpha values shapes or opacities?
|
||||
this.alphaIsShape = false;
|
||||
this.fontSize = 0;
|
||||
@ -610,16 +610,55 @@ class CanvasExtraState {
|
||||
this.lineWidth = 1;
|
||||
this.activeSMask = null;
|
||||
this.transferMaps = null;
|
||||
|
||||
this.startNewPathAndClipBox([0, 0, width, height]);
|
||||
}
|
||||
|
||||
clone() {
|
||||
return Object.create(this);
|
||||
const clone = Object.create(this);
|
||||
clone.clipBox = this.clipBox.slice();
|
||||
return clone;
|
||||
}
|
||||
|
||||
setCurrentPoint(x, y) {
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
}
|
||||
|
||||
updatePathMinMax(transform, x, y) {
|
||||
[x, y] = Util.applyTransform([x, y], transform);
|
||||
this.minX = Math.min(this.minX, x);
|
||||
this.minY = Math.min(this.minY, y);
|
||||
this.maxX = Math.max(this.maxX, x);
|
||||
this.maxY = Math.max(this.maxY, y);
|
||||
}
|
||||
|
||||
updateCurvePathMinMax(transform, x0, y0, x1, y1, x2, y2, x3, y3) {
|
||||
const box = Util.bezierBoundingBox(x0, y0, x1, y1, x2, y2, x3, y3);
|
||||
this.updatePathMinMax(transform, box[0], box[1]);
|
||||
this.updatePathMinMax(transform, box[2], box[3]);
|
||||
}
|
||||
|
||||
getPathBoundingBox() {
|
||||
return [this.minX, this.minY, this.maxX, this.maxY];
|
||||
}
|
||||
|
||||
updateClipFromPath() {
|
||||
const intersect = Util.intersect(this.clipBox, this.getPathBoundingBox());
|
||||
this.startNewPathAndClipBox(intersect || [0, 0, 0, 0]);
|
||||
}
|
||||
|
||||
startNewPathAndClipBox(box) {
|
||||
this.clipBox = box;
|
||||
this.minX = Infinity;
|
||||
this.minY = Infinity;
|
||||
this.maxX = 0;
|
||||
this.maxY = 0;
|
||||
}
|
||||
|
||||
getClippedPathBoundingBox() {
|
||||
return Util.intersect(this.clipBox, this.getPathBoundingBox());
|
||||
}
|
||||
}
|
||||
|
||||
function putBinaryImageData(ctx, imgData, transferMaps = null) {
|
||||
@ -995,6 +1034,9 @@ function composeSMask(ctx, smask, layerCtx, layerBox) {
|
||||
const layerOffsetY = layerBox[1];
|
||||
const layerWidth = layerBox[2] - layerOffsetX;
|
||||
const layerHeight = layerBox[3] - layerOffsetY;
|
||||
if (layerWidth === 0 || layerHeight === 0) {
|
||||
return;
|
||||
}
|
||||
genericComposeSMask(
|
||||
smask.context,
|
||||
layerCtx,
|
||||
@ -1051,7 +1093,10 @@ class CanvasGraphics {
|
||||
optionalContentConfig
|
||||
) {
|
||||
this.ctx = canvasCtx;
|
||||
this.current = new CanvasExtraState();
|
||||
this.current = new CanvasExtraState(
|
||||
this.ctx.canvas.width,
|
||||
this.ctx.canvas.height
|
||||
);
|
||||
this.stateStack = [];
|
||||
this.pendingClip = null;
|
||||
this.pendingEOFill = false;
|
||||
@ -1538,8 +1583,14 @@ class CanvasGraphics {
|
||||
if (!this.current.activeSMask) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!dirtyBox) {
|
||||
dirtyBox = [0, 0, this.ctx.canvas.width, this.ctx.canvas.height];
|
||||
} else {
|
||||
dirtyBox[0] = Math.floor(dirtyBox[0]);
|
||||
dirtyBox[1] = Math.floor(dirtyBox[1]);
|
||||
dirtyBox[2] = Math.ceil(dirtyBox[2]);
|
||||
dirtyBox[3] = Math.ceil(dirtyBox[3]);
|
||||
}
|
||||
const smask = this.current.activeSMask;
|
||||
const suspendedCtx = this.suspendedCtx;
|
||||
@ -1589,6 +1640,7 @@ class CanvasGraphics {
|
||||
const current = this.current;
|
||||
let x = current.x,
|
||||
y = current.y;
|
||||
let startX, startY;
|
||||
for (let i = 0, j = 0, ii = ops.length; i < ii; i++) {
|
||||
switch (ops[i] | 0) {
|
||||
case OPS.rectangle:
|
||||
@ -1607,20 +1659,25 @@ class CanvasGraphics {
|
||||
ctx.lineTo(xw, yh);
|
||||
ctx.lineTo(x, yh);
|
||||
}
|
||||
|
||||
current.updatePathMinMax(ctx.mozCurrentTransform, x, y);
|
||||
current.updatePathMinMax(ctx.mozCurrentTransform, xw, yh);
|
||||
ctx.closePath();
|
||||
break;
|
||||
case OPS.moveTo:
|
||||
x = args[j++];
|
||||
y = args[j++];
|
||||
ctx.moveTo(x, y);
|
||||
current.updatePathMinMax(ctx.mozCurrentTransform, x, y);
|
||||
break;
|
||||
case OPS.lineTo:
|
||||
x = args[j++];
|
||||
y = args[j++];
|
||||
ctx.lineTo(x, y);
|
||||
current.updatePathMinMax(ctx.mozCurrentTransform, x, y);
|
||||
break;
|
||||
case OPS.curveTo:
|
||||
startX = x;
|
||||
startY = y;
|
||||
x = args[j + 4];
|
||||
y = args[j + 5];
|
||||
ctx.bezierCurveTo(
|
||||
@ -1631,9 +1688,22 @@ class CanvasGraphics {
|
||||
x,
|
||||
y
|
||||
);
|
||||
current.updateCurvePathMinMax(
|
||||
ctx.mozCurrentTransform,
|
||||
startX,
|
||||
startY,
|
||||
args[j],
|
||||
args[j + 1],
|
||||
args[j + 2],
|
||||
args[j + 3],
|
||||
x,
|
||||
y
|
||||
);
|
||||
j += 6;
|
||||
break;
|
||||
case OPS.curveTo2:
|
||||
startX = x;
|
||||
startY = y;
|
||||
ctx.bezierCurveTo(
|
||||
x,
|
||||
y,
|
||||
@ -1642,14 +1712,38 @@ class CanvasGraphics {
|
||||
args[j + 2],
|
||||
args[j + 3]
|
||||
);
|
||||
current.updateCurvePathMinMax(
|
||||
ctx.mozCurrentTransform,
|
||||
startX,
|
||||
startY,
|
||||
x,
|
||||
y,
|
||||
args[j],
|
||||
args[j + 1],
|
||||
args[j + 2],
|
||||
args[j + 3]
|
||||
);
|
||||
x = args[j + 2];
|
||||
y = args[j + 3];
|
||||
j += 4;
|
||||
break;
|
||||
case OPS.curveTo3:
|
||||
startX = x;
|
||||
startY = y;
|
||||
x = args[j + 2];
|
||||
y = args[j + 3];
|
||||
ctx.bezierCurveTo(args[j], args[j + 1], x, y, x, y);
|
||||
current.updateCurvePathMinMax(
|
||||
ctx.mozCurrentTransform,
|
||||
startX,
|
||||
startY,
|
||||
args[j],
|
||||
args[j + 1],
|
||||
x,
|
||||
y,
|
||||
x,
|
||||
y
|
||||
);
|
||||
j += 4;
|
||||
break;
|
||||
case OPS.closePath:
|
||||
@ -1702,7 +1796,7 @@ class CanvasGraphics {
|
||||
}
|
||||
}
|
||||
if (consumePath) {
|
||||
this.consumePath();
|
||||
this.consumePath(this.current.getClippedPathBoundingBox());
|
||||
}
|
||||
// Restore the global alpha to the fill alpha
|
||||
ctx.globalAlpha = this.current.fillAlpha;
|
||||
@ -1730,7 +1824,8 @@ class CanvasGraphics {
|
||||
needRestore = true;
|
||||
}
|
||||
|
||||
if (this.contentVisible) {
|
||||
const intersect = this.current.getClippedPathBoundingBox();
|
||||
if (this.contentVisible && intersect !== null) {
|
||||
if (this.pendingEOFill) {
|
||||
ctx.fill("evenodd");
|
||||
this.pendingEOFill = false;
|
||||
@ -1743,7 +1838,7 @@ class CanvasGraphics {
|
||||
ctx.restore();
|
||||
}
|
||||
if (consumePath) {
|
||||
this.consumePath();
|
||||
this.consumePath(intersect);
|
||||
}
|
||||
}
|
||||
|
||||
@ -2360,7 +2455,6 @@ class CanvasGraphics {
|
||||
);
|
||||
|
||||
const inv = ctx.mozCurrentTransformInverse;
|
||||
let dirtyBox = [0, 0, ctx.canvas.width, ctx.canvas.height];
|
||||
if (inv) {
|
||||
const canvas = ctx.canvas;
|
||||
const width = canvas.width;
|
||||
@ -2375,10 +2469,6 @@ class CanvasGraphics {
|
||||
const y0 = Math.min(bl[1], br[1], ul[1], ur[1]);
|
||||
const x1 = Math.max(bl[0], br[0], ul[0], ur[0]);
|
||||
const y1 = Math.max(bl[1], br[1], ul[1], ur[1]);
|
||||
dirtyBox = Util.getAxialAlignedBoundingBox(
|
||||
[x0, y0, x1, y1],
|
||||
ctx.mozCurrentTransform
|
||||
);
|
||||
|
||||
this.ctx.fillRect(x0, y0, x1 - x0, y1 - y0);
|
||||
} else {
|
||||
@ -2391,7 +2481,7 @@ class CanvasGraphics {
|
||||
this.ctx.fillRect(-1e10, -1e10, 2e10, 2e10);
|
||||
}
|
||||
|
||||
this.compose(dirtyBox);
|
||||
this.compose(this.current.getClippedPathBoundingBox());
|
||||
this.restore();
|
||||
}
|
||||
|
||||
@ -2421,6 +2511,16 @@ class CanvasGraphics {
|
||||
const width = bbox[2] - bbox[0];
|
||||
const height = bbox[3] - bbox[1];
|
||||
this.ctx.rect(bbox[0], bbox[1], width, height);
|
||||
this.current.updatePathMinMax(
|
||||
this.ctx.mozCurrentTransform,
|
||||
bbox[0],
|
||||
bbox[1]
|
||||
);
|
||||
this.current.updatePathMinMax(
|
||||
this.ctx.mozCurrentTransform,
|
||||
bbox[2],
|
||||
bbox[3]
|
||||
);
|
||||
this.clip();
|
||||
this.endPath();
|
||||
}
|
||||
@ -2511,6 +2611,8 @@ class CanvasGraphics {
|
||||
drawnHeight = MAX_GROUP_SIZE;
|
||||
}
|
||||
|
||||
this.current.startNewPathAndClipBox([0, 0, drawnWidth, drawnHeight]);
|
||||
|
||||
let cacheId = "groupAt" + this.groupLevel;
|
||||
if (group.smask) {
|
||||
// Using two cache entries is case if masks are used one after another.
|
||||
@ -2617,7 +2719,10 @@ class CanvasGraphics {
|
||||
beginAnnotation(id, rect, transform, matrix) {
|
||||
this.save();
|
||||
resetCtxToDefault(this.ctx);
|
||||
this.current = new CanvasExtraState();
|
||||
this.current = new CanvasExtraState(
|
||||
this.ctx.canvas.width,
|
||||
this.ctx.canvas.height
|
||||
);
|
||||
|
||||
if (Array.isArray(rect) && rect.length === 4) {
|
||||
const width = rect[2] - rect[0];
|
||||
@ -2950,9 +3055,12 @@ class CanvasGraphics {
|
||||
|
||||
// Helper functions
|
||||
|
||||
consumePath() {
|
||||
consumePath(clipBox) {
|
||||
if (this.pendingClip) {
|
||||
this.current.updateClipFromPath();
|
||||
}
|
||||
if (!this.pendingClip) {
|
||||
this.compose();
|
||||
this.compose(clipBox);
|
||||
}
|
||||
const ctx = this.ctx;
|
||||
if (this.pendingClip) {
|
||||
|
@ -872,6 +872,78 @@ class Util {
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// From https://github.com/adobe-webplatform/Snap.svg/blob/b365287722a72526000ac4bfcf0ce4cac2faa015/src/path.js#L852
|
||||
static bezierBoundingBox(x0, y0, x1, y1, x2, y2, x3, y3) {
|
||||
const tvalues = [],
|
||||
bounds = [[], []];
|
||||
let a, b, c, t, t1, t2, b2ac, sqrtb2ac;
|
||||
for (let i = 0; i < 2; ++i) {
|
||||
if (i === 0) {
|
||||
b = 6 * x0 - 12 * x1 + 6 * x2;
|
||||
a = -3 * x0 + 9 * x1 - 9 * x2 + 3 * x3;
|
||||
c = 3 * x1 - 3 * x0;
|
||||
} else {
|
||||
b = 6 * y0 - 12 * y1 + 6 * y2;
|
||||
a = -3 * y0 + 9 * y1 - 9 * y2 + 3 * y3;
|
||||
c = 3 * y1 - 3 * y0;
|
||||
}
|
||||
if (Math.abs(a) < 1e-12) {
|
||||
if (Math.abs(b) < 1e-12) {
|
||||
continue;
|
||||
}
|
||||
t = -c / b;
|
||||
if (0 < t && t < 1) {
|
||||
tvalues.push(t);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
b2ac = b * b - 4 * c * a;
|
||||
sqrtb2ac = Math.sqrt(b2ac);
|
||||
if (b2ac < 0) {
|
||||
continue;
|
||||
}
|
||||
t1 = (-b + sqrtb2ac) / (2 * a);
|
||||
if (0 < t1 && t1 < 1) {
|
||||
tvalues.push(t1);
|
||||
}
|
||||
t2 = (-b - sqrtb2ac) / (2 * a);
|
||||
if (0 < t2 && t2 < 1) {
|
||||
tvalues.push(t2);
|
||||
}
|
||||
}
|
||||
|
||||
let j = tvalues.length,
|
||||
mt;
|
||||
const jlen = j;
|
||||
while (j--) {
|
||||
t = tvalues[j];
|
||||
mt = 1 - t;
|
||||
bounds[0][j] =
|
||||
mt * mt * mt * x0 +
|
||||
3 * mt * mt * t * x1 +
|
||||
3 * mt * t * t * x2 +
|
||||
t * t * t * x3;
|
||||
bounds[1][j] =
|
||||
mt * mt * mt * y0 +
|
||||
3 * mt * mt * t * y1 +
|
||||
3 * mt * t * t * y2 +
|
||||
t * t * t * y3;
|
||||
}
|
||||
|
||||
bounds[0][jlen] = x0;
|
||||
bounds[1][jlen] = y0;
|
||||
bounds[0][jlen + 1] = x3;
|
||||
bounds[1][jlen + 1] = y3;
|
||||
bounds[0].length = bounds[1].length = jlen + 2;
|
||||
|
||||
return [
|
||||
Math.min(...bounds[0]),
|
||||
Math.min(...bounds[1]),
|
||||
Math.max(...bounds[0]),
|
||||
Math.max(...bounds[1]),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
const PDFStringTranslateTable = [
|
||||
|
Loading…
Reference in New Issue
Block a user