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 {
|
class CanvasExtraState {
|
||||||
constructor() {
|
constructor(width, height) {
|
||||||
// Are soft masks and alpha values shapes or opacities?
|
// Are soft masks and alpha values shapes or opacities?
|
||||||
this.alphaIsShape = false;
|
this.alphaIsShape = false;
|
||||||
this.fontSize = 0;
|
this.fontSize = 0;
|
||||||
@ -610,16 +610,55 @@ class CanvasExtraState {
|
|||||||
this.lineWidth = 1;
|
this.lineWidth = 1;
|
||||||
this.activeSMask = null;
|
this.activeSMask = null;
|
||||||
this.transferMaps = null;
|
this.transferMaps = null;
|
||||||
|
|
||||||
|
this.startNewPathAndClipBox([0, 0, width, height]);
|
||||||
}
|
}
|
||||||
|
|
||||||
clone() {
|
clone() {
|
||||||
return Object.create(this);
|
const clone = Object.create(this);
|
||||||
|
clone.clipBox = this.clipBox.slice();
|
||||||
|
return clone;
|
||||||
}
|
}
|
||||||
|
|
||||||
setCurrentPoint(x, y) {
|
setCurrentPoint(x, y) {
|
||||||
this.x = x;
|
this.x = x;
|
||||||
this.y = y;
|
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) {
|
function putBinaryImageData(ctx, imgData, transferMaps = null) {
|
||||||
@ -995,6 +1034,9 @@ function composeSMask(ctx, smask, layerCtx, layerBox) {
|
|||||||
const layerOffsetY = layerBox[1];
|
const layerOffsetY = layerBox[1];
|
||||||
const layerWidth = layerBox[2] - layerOffsetX;
|
const layerWidth = layerBox[2] - layerOffsetX;
|
||||||
const layerHeight = layerBox[3] - layerOffsetY;
|
const layerHeight = layerBox[3] - layerOffsetY;
|
||||||
|
if (layerWidth === 0 || layerHeight === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
genericComposeSMask(
|
genericComposeSMask(
|
||||||
smask.context,
|
smask.context,
|
||||||
layerCtx,
|
layerCtx,
|
||||||
@ -1051,7 +1093,10 @@ class CanvasGraphics {
|
|||||||
optionalContentConfig
|
optionalContentConfig
|
||||||
) {
|
) {
|
||||||
this.ctx = canvasCtx;
|
this.ctx = canvasCtx;
|
||||||
this.current = new CanvasExtraState();
|
this.current = new CanvasExtraState(
|
||||||
|
this.ctx.canvas.width,
|
||||||
|
this.ctx.canvas.height
|
||||||
|
);
|
||||||
this.stateStack = [];
|
this.stateStack = [];
|
||||||
this.pendingClip = null;
|
this.pendingClip = null;
|
||||||
this.pendingEOFill = false;
|
this.pendingEOFill = false;
|
||||||
@ -1538,8 +1583,14 @@ class CanvasGraphics {
|
|||||||
if (!this.current.activeSMask) {
|
if (!this.current.activeSMask) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!dirtyBox) {
|
if (!dirtyBox) {
|
||||||
dirtyBox = [0, 0, this.ctx.canvas.width, this.ctx.canvas.height];
|
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 smask = this.current.activeSMask;
|
||||||
const suspendedCtx = this.suspendedCtx;
|
const suspendedCtx = this.suspendedCtx;
|
||||||
@ -1589,6 +1640,7 @@ class CanvasGraphics {
|
|||||||
const current = this.current;
|
const current = this.current;
|
||||||
let x = current.x,
|
let x = current.x,
|
||||||
y = current.y;
|
y = current.y;
|
||||||
|
let startX, startY;
|
||||||
for (let i = 0, j = 0, ii = ops.length; i < ii; i++) {
|
for (let i = 0, j = 0, ii = ops.length; i < ii; i++) {
|
||||||
switch (ops[i] | 0) {
|
switch (ops[i] | 0) {
|
||||||
case OPS.rectangle:
|
case OPS.rectangle:
|
||||||
@ -1607,20 +1659,25 @@ class CanvasGraphics {
|
|||||||
ctx.lineTo(xw, yh);
|
ctx.lineTo(xw, yh);
|
||||||
ctx.lineTo(x, yh);
|
ctx.lineTo(x, yh);
|
||||||
}
|
}
|
||||||
|
current.updatePathMinMax(ctx.mozCurrentTransform, x, y);
|
||||||
|
current.updatePathMinMax(ctx.mozCurrentTransform, xw, yh);
|
||||||
ctx.closePath();
|
ctx.closePath();
|
||||||
break;
|
break;
|
||||||
case OPS.moveTo:
|
case OPS.moveTo:
|
||||||
x = args[j++];
|
x = args[j++];
|
||||||
y = args[j++];
|
y = args[j++];
|
||||||
ctx.moveTo(x, y);
|
ctx.moveTo(x, y);
|
||||||
|
current.updatePathMinMax(ctx.mozCurrentTransform, x, y);
|
||||||
break;
|
break;
|
||||||
case OPS.lineTo:
|
case OPS.lineTo:
|
||||||
x = args[j++];
|
x = args[j++];
|
||||||
y = args[j++];
|
y = args[j++];
|
||||||
ctx.lineTo(x, y);
|
ctx.lineTo(x, y);
|
||||||
|
current.updatePathMinMax(ctx.mozCurrentTransform, x, y);
|
||||||
break;
|
break;
|
||||||
case OPS.curveTo:
|
case OPS.curveTo:
|
||||||
|
startX = x;
|
||||||
|
startY = y;
|
||||||
x = args[j + 4];
|
x = args[j + 4];
|
||||||
y = args[j + 5];
|
y = args[j + 5];
|
||||||
ctx.bezierCurveTo(
|
ctx.bezierCurveTo(
|
||||||
@ -1631,9 +1688,22 @@ class CanvasGraphics {
|
|||||||
x,
|
x,
|
||||||
y
|
y
|
||||||
);
|
);
|
||||||
|
current.updateCurvePathMinMax(
|
||||||
|
ctx.mozCurrentTransform,
|
||||||
|
startX,
|
||||||
|
startY,
|
||||||
|
args[j],
|
||||||
|
args[j + 1],
|
||||||
|
args[j + 2],
|
||||||
|
args[j + 3],
|
||||||
|
x,
|
||||||
|
y
|
||||||
|
);
|
||||||
j += 6;
|
j += 6;
|
||||||
break;
|
break;
|
||||||
case OPS.curveTo2:
|
case OPS.curveTo2:
|
||||||
|
startX = x;
|
||||||
|
startY = y;
|
||||||
ctx.bezierCurveTo(
|
ctx.bezierCurveTo(
|
||||||
x,
|
x,
|
||||||
y,
|
y,
|
||||||
@ -1642,14 +1712,38 @@ class CanvasGraphics {
|
|||||||
args[j + 2],
|
args[j + 2],
|
||||||
args[j + 3]
|
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];
|
x = args[j + 2];
|
||||||
y = args[j + 3];
|
y = args[j + 3];
|
||||||
j += 4;
|
j += 4;
|
||||||
break;
|
break;
|
||||||
case OPS.curveTo3:
|
case OPS.curveTo3:
|
||||||
|
startX = x;
|
||||||
|
startY = y;
|
||||||
x = args[j + 2];
|
x = args[j + 2];
|
||||||
y = args[j + 3];
|
y = args[j + 3];
|
||||||
ctx.bezierCurveTo(args[j], args[j + 1], x, y, x, y);
|
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;
|
j += 4;
|
||||||
break;
|
break;
|
||||||
case OPS.closePath:
|
case OPS.closePath:
|
||||||
@ -1702,7 +1796,7 @@ class CanvasGraphics {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (consumePath) {
|
if (consumePath) {
|
||||||
this.consumePath();
|
this.consumePath(this.current.getClippedPathBoundingBox());
|
||||||
}
|
}
|
||||||
// Restore the global alpha to the fill alpha
|
// Restore the global alpha to the fill alpha
|
||||||
ctx.globalAlpha = this.current.fillAlpha;
|
ctx.globalAlpha = this.current.fillAlpha;
|
||||||
@ -1730,7 +1824,8 @@ class CanvasGraphics {
|
|||||||
needRestore = true;
|
needRestore = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.contentVisible) {
|
const intersect = this.current.getClippedPathBoundingBox();
|
||||||
|
if (this.contentVisible && intersect !== null) {
|
||||||
if (this.pendingEOFill) {
|
if (this.pendingEOFill) {
|
||||||
ctx.fill("evenodd");
|
ctx.fill("evenodd");
|
||||||
this.pendingEOFill = false;
|
this.pendingEOFill = false;
|
||||||
@ -1743,7 +1838,7 @@ class CanvasGraphics {
|
|||||||
ctx.restore();
|
ctx.restore();
|
||||||
}
|
}
|
||||||
if (consumePath) {
|
if (consumePath) {
|
||||||
this.consumePath();
|
this.consumePath(intersect);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2360,7 +2455,6 @@ class CanvasGraphics {
|
|||||||
);
|
);
|
||||||
|
|
||||||
const inv = ctx.mozCurrentTransformInverse;
|
const inv = ctx.mozCurrentTransformInverse;
|
||||||
let dirtyBox = [0, 0, ctx.canvas.width, ctx.canvas.height];
|
|
||||||
if (inv) {
|
if (inv) {
|
||||||
const canvas = ctx.canvas;
|
const canvas = ctx.canvas;
|
||||||
const width = canvas.width;
|
const width = canvas.width;
|
||||||
@ -2375,10 +2469,6 @@ class CanvasGraphics {
|
|||||||
const y0 = Math.min(bl[1], br[1], ul[1], ur[1]);
|
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 x1 = Math.max(bl[0], br[0], ul[0], ur[0]);
|
||||||
const y1 = Math.max(bl[1], br[1], ul[1], ur[1]);
|
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);
|
this.ctx.fillRect(x0, y0, x1 - x0, y1 - y0);
|
||||||
} else {
|
} else {
|
||||||
@ -2391,7 +2481,7 @@ class CanvasGraphics {
|
|||||||
this.ctx.fillRect(-1e10, -1e10, 2e10, 2e10);
|
this.ctx.fillRect(-1e10, -1e10, 2e10, 2e10);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.compose(dirtyBox);
|
this.compose(this.current.getClippedPathBoundingBox());
|
||||||
this.restore();
|
this.restore();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2421,6 +2511,16 @@ class CanvasGraphics {
|
|||||||
const width = bbox[2] - bbox[0];
|
const width = bbox[2] - bbox[0];
|
||||||
const height = bbox[3] - bbox[1];
|
const height = bbox[3] - bbox[1];
|
||||||
this.ctx.rect(bbox[0], bbox[1], width, height);
|
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.clip();
|
||||||
this.endPath();
|
this.endPath();
|
||||||
}
|
}
|
||||||
@ -2511,6 +2611,8 @@ class CanvasGraphics {
|
|||||||
drawnHeight = MAX_GROUP_SIZE;
|
drawnHeight = MAX_GROUP_SIZE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.current.startNewPathAndClipBox([0, 0, drawnWidth, drawnHeight]);
|
||||||
|
|
||||||
let cacheId = "groupAt" + this.groupLevel;
|
let cacheId = "groupAt" + this.groupLevel;
|
||||||
if (group.smask) {
|
if (group.smask) {
|
||||||
// Using two cache entries is case if masks are used one after another.
|
// Using two cache entries is case if masks are used one after another.
|
||||||
@ -2617,7 +2719,10 @@ class CanvasGraphics {
|
|||||||
beginAnnotation(id, rect, transform, matrix) {
|
beginAnnotation(id, rect, transform, matrix) {
|
||||||
this.save();
|
this.save();
|
||||||
resetCtxToDefault(this.ctx);
|
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) {
|
if (Array.isArray(rect) && rect.length === 4) {
|
||||||
const width = rect[2] - rect[0];
|
const width = rect[2] - rect[0];
|
||||||
@ -2950,9 +3055,12 @@ class CanvasGraphics {
|
|||||||
|
|
||||||
// Helper functions
|
// Helper functions
|
||||||
|
|
||||||
consumePath() {
|
consumePath(clipBox) {
|
||||||
|
if (this.pendingClip) {
|
||||||
|
this.current.updateClipFromPath();
|
||||||
|
}
|
||||||
if (!this.pendingClip) {
|
if (!this.pendingClip) {
|
||||||
this.compose();
|
this.compose(clipBox);
|
||||||
}
|
}
|
||||||
const ctx = this.ctx;
|
const ctx = this.ctx;
|
||||||
if (this.pendingClip) {
|
if (this.pendingClip) {
|
||||||
|
@ -872,6 +872,78 @@ class Util {
|
|||||||
|
|
||||||
return result;
|
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 = [
|
const PDFStringTranslateTable = [
|
||||||
|
Loading…
x
Reference in New Issue
Block a user