Merge pull request #14175 from brendandahl/smask-v2
Use a new method for handling soft masks.
This commit is contained in:
commit
52372b9378
@ -60,6 +60,136 @@ const FULL_CHUNK_HEIGHT = 16;
|
|||||||
// Once the bug is fixed upstream, we can remove this constant and its use.
|
// Once the bug is fixed upstream, we can remove this constant and its use.
|
||||||
const LINEWIDTH_SCALE_FACTOR = 1.000001;
|
const LINEWIDTH_SCALE_FACTOR = 1.000001;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Overrides certain methods on a 2d ctx so that when they are called they
|
||||||
|
* will also call the same method on the destCtx. The methods that are
|
||||||
|
* overridden are all the transformation state modifiers, path creation, and
|
||||||
|
* save/restore. We only forward these specific methods because they are the
|
||||||
|
* only state modifiers that we cannot copy over when we switch contexts.
|
||||||
|
*
|
||||||
|
* To remove mirroring call `ctx._removeMirroring()`.
|
||||||
|
*
|
||||||
|
* @param {Object} ctx - The 2d canvas context that will duplicate its calls on
|
||||||
|
* the destCtx.
|
||||||
|
* @param {Object} destCtx - The 2d canvas context that will receive the
|
||||||
|
* forwarded calls.
|
||||||
|
*/
|
||||||
|
function mirrorContextOperations(ctx, destCtx) {
|
||||||
|
if (ctx._removeMirroring) {
|
||||||
|
throw new Error("Context is already forwarding operations.");
|
||||||
|
}
|
||||||
|
ctx.__originalSave = ctx.save;
|
||||||
|
ctx.__originalRestore = ctx.restore;
|
||||||
|
ctx.__originalRotate = ctx.rotate;
|
||||||
|
ctx.__originalScale = ctx.scale;
|
||||||
|
ctx.__originalTranslate = ctx.translate;
|
||||||
|
ctx.__originalTransform = ctx.transform;
|
||||||
|
ctx.__originalSetTransform = ctx.setTransform;
|
||||||
|
ctx.__originalResetTransform = ctx.resetTransform;
|
||||||
|
ctx.__originalClip = ctx.clip;
|
||||||
|
ctx.__originalMoveTo = ctx.moveTo;
|
||||||
|
ctx.__originalLineTo = ctx.lineTo;
|
||||||
|
ctx.__originalBezierCurveTo = ctx.bezierCurveTo;
|
||||||
|
ctx.__originalRect = ctx.rect;
|
||||||
|
ctx.__originalClosePath = ctx.closePath;
|
||||||
|
ctx.__originalBeginPath = ctx.beginPath;
|
||||||
|
|
||||||
|
ctx._removeMirroring = () => {
|
||||||
|
ctx.save = ctx.__originalSave;
|
||||||
|
ctx.restore = ctx.__originalRestore;
|
||||||
|
ctx.rotate = ctx.__originalRotate;
|
||||||
|
ctx.scale = ctx.__originalScale;
|
||||||
|
ctx.translate = ctx.__originalTranslate;
|
||||||
|
ctx.transform = ctx.__originalTransform;
|
||||||
|
ctx.setTransform = ctx.__originalSetTransform;
|
||||||
|
ctx.resetTransform = ctx.__originalResetTransform;
|
||||||
|
|
||||||
|
ctx.clip = ctx.__originalClip;
|
||||||
|
ctx.moveTo = ctx.__originalMoveTo;
|
||||||
|
ctx.lineTo = ctx.__originalLineTo;
|
||||||
|
ctx.bezierCurveTo = ctx.__originalBezierCurveTo;
|
||||||
|
ctx.rect = ctx.__originalRect;
|
||||||
|
ctx.closePath = ctx.__originalClosePath;
|
||||||
|
ctx.beginPath = ctx.__originalBeginPath;
|
||||||
|
delete ctx._removeMirroring;
|
||||||
|
};
|
||||||
|
|
||||||
|
ctx.save = function ctxSave() {
|
||||||
|
destCtx.save();
|
||||||
|
this.__originalSave();
|
||||||
|
};
|
||||||
|
|
||||||
|
ctx.restore = function ctxRestore() {
|
||||||
|
destCtx.restore();
|
||||||
|
this.__originalRestore();
|
||||||
|
};
|
||||||
|
|
||||||
|
ctx.translate = function ctxTranslate(x, y) {
|
||||||
|
destCtx.translate(x, y);
|
||||||
|
this.__originalTranslate(x, y);
|
||||||
|
};
|
||||||
|
|
||||||
|
ctx.scale = function ctxScale(x, y) {
|
||||||
|
destCtx.scale(x, y);
|
||||||
|
this.__originalScale(x, y);
|
||||||
|
};
|
||||||
|
|
||||||
|
ctx.transform = function ctxTransform(a, b, c, d, e, f) {
|
||||||
|
destCtx.transform(a, b, c, d, e, f);
|
||||||
|
this.__originalTransform(a, b, c, d, e, f);
|
||||||
|
};
|
||||||
|
|
||||||
|
ctx.setTransform = function ctxSetTransform(a, b, c, d, e, f) {
|
||||||
|
destCtx.setTransform(a, b, c, d, e, f);
|
||||||
|
this.__originalSetTransform(a, b, c, d, e, f);
|
||||||
|
};
|
||||||
|
|
||||||
|
ctx.resetTransform = function ctxResetTransform() {
|
||||||
|
destCtx.resetTransform();
|
||||||
|
this.__originalResetTransform();
|
||||||
|
};
|
||||||
|
|
||||||
|
ctx.rotate = function ctxRotate(angle) {
|
||||||
|
destCtx.rotate(angle);
|
||||||
|
this.__originalRotate(angle);
|
||||||
|
};
|
||||||
|
|
||||||
|
ctx.clip = function ctxRotate(rule) {
|
||||||
|
destCtx.clip(rule);
|
||||||
|
this.__originalClip(rule);
|
||||||
|
};
|
||||||
|
|
||||||
|
ctx.moveTo = function (x, y) {
|
||||||
|
destCtx.moveTo(x, y);
|
||||||
|
this.__originalMoveTo(x, y);
|
||||||
|
};
|
||||||
|
|
||||||
|
ctx.lineTo = function (x, y) {
|
||||||
|
destCtx.lineTo(x, y);
|
||||||
|
this.__originalLineTo(x, y);
|
||||||
|
};
|
||||||
|
|
||||||
|
ctx.bezierCurveTo = function (cp1x, cp1y, cp2x, cp2y, x, y) {
|
||||||
|
destCtx.bezierCurveTo(cp1x, cp1y, cp2x, cp2y, x, y);
|
||||||
|
this.__originalBezierCurveTo(cp1x, cp1y, cp2x, cp2y, x, y);
|
||||||
|
};
|
||||||
|
|
||||||
|
ctx.rect = function (x, y, width, height) {
|
||||||
|
destCtx.rect(x, y, width, height);
|
||||||
|
this.__originalRect(x, y, width, height);
|
||||||
|
};
|
||||||
|
|
||||||
|
ctx.closePath = function () {
|
||||||
|
destCtx.closePath();
|
||||||
|
this.__originalClosePath();
|
||||||
|
};
|
||||||
|
|
||||||
|
ctx.beginPath = function () {
|
||||||
|
destCtx.beginPath();
|
||||||
|
this.__originalBeginPath();
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
function addContextCurrentTransform(ctx) {
|
function addContextCurrentTransform(ctx) {
|
||||||
// If the context doesn't expose a `mozCurrentTransform`, add a JS based one.
|
// If the context doesn't expose a `mozCurrentTransform`, add a JS based one.
|
||||||
if (ctx.mozCurrentTransform) {
|
if (ctx.mozCurrentTransform) {
|
||||||
@ -449,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;
|
||||||
@ -479,18 +609,56 @@ class CanvasExtraState {
|
|||||||
this.strokeAlpha = 1;
|
this.strokeAlpha = 1;
|
||||||
this.lineWidth = 1;
|
this.lineWidth = 1;
|
||||||
this.activeSMask = null;
|
this.activeSMask = null;
|
||||||
this.resumeSMaskCtx = null; // nonclonable field (see the save method below)
|
|
||||||
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) {
|
||||||
@ -816,7 +984,11 @@ function genericComposeSMask(
|
|||||||
height,
|
height,
|
||||||
subtype,
|
subtype,
|
||||||
backdrop,
|
backdrop,
|
||||||
transferMap
|
transferMap,
|
||||||
|
layerOffsetX,
|
||||||
|
layerOffsetY,
|
||||||
|
maskOffsetX,
|
||||||
|
maskOffsetY
|
||||||
) {
|
) {
|
||||||
const hasBackdrop = !!backdrop;
|
const hasBackdrop = !!backdrop;
|
||||||
const r0 = hasBackdrop ? backdrop[0] : 0;
|
const r0 = hasBackdrop ? backdrop[0] : 0;
|
||||||
@ -835,41 +1007,55 @@ function genericComposeSMask(
|
|||||||
const chunkSize = Math.min(height, Math.ceil(PIXELS_TO_PROCESS / width));
|
const chunkSize = Math.min(height, Math.ceil(PIXELS_TO_PROCESS / width));
|
||||||
for (let row = 0; row < height; row += chunkSize) {
|
for (let row = 0; row < height; row += chunkSize) {
|
||||||
const chunkHeight = Math.min(chunkSize, height - row);
|
const chunkHeight = Math.min(chunkSize, height - row);
|
||||||
const maskData = maskCtx.getImageData(0, row, width, chunkHeight);
|
const maskData = maskCtx.getImageData(
|
||||||
const layerData = layerCtx.getImageData(0, row, width, chunkHeight);
|
layerOffsetX - maskOffsetX,
|
||||||
|
row + (layerOffsetY - maskOffsetY),
|
||||||
|
width,
|
||||||
|
chunkHeight
|
||||||
|
);
|
||||||
|
const layerData = layerCtx.getImageData(
|
||||||
|
layerOffsetX,
|
||||||
|
row + layerOffsetY,
|
||||||
|
width,
|
||||||
|
chunkHeight
|
||||||
|
);
|
||||||
|
|
||||||
if (hasBackdrop) {
|
if (hasBackdrop) {
|
||||||
composeSMaskBackdrop(maskData.data, r0, g0, b0);
|
composeSMaskBackdrop(maskData.data, r0, g0, b0);
|
||||||
}
|
}
|
||||||
composeFn(maskData.data, layerData.data, transferMap);
|
composeFn(maskData.data, layerData.data, transferMap);
|
||||||
|
|
||||||
maskCtx.putImageData(layerData, 0, row);
|
layerCtx.putImageData(layerData, layerOffsetX, row + layerOffsetY);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function composeSMask(ctx, smask, layerCtx) {
|
function composeSMask(ctx, smask, layerCtx, layerBox) {
|
||||||
const mask = smask.canvas;
|
const layerOffsetX = layerBox[0];
|
||||||
const maskCtx = smask.context;
|
const layerOffsetY = layerBox[1];
|
||||||
|
const layerWidth = layerBox[2] - layerOffsetX;
|
||||||
ctx.setTransform(
|
const layerHeight = layerBox[3] - layerOffsetY;
|
||||||
smask.scaleX,
|
if (layerWidth === 0 || layerHeight === 0) {
|
||||||
0,
|
return;
|
||||||
0,
|
}
|
||||||
smask.scaleY,
|
genericComposeSMask(
|
||||||
|
smask.context,
|
||||||
|
layerCtx,
|
||||||
|
layerWidth,
|
||||||
|
layerHeight,
|
||||||
|
smask.subtype,
|
||||||
|
smask.backdrop,
|
||||||
|
smask.transferMap,
|
||||||
|
layerOffsetX,
|
||||||
|
layerOffsetY,
|
||||||
smask.offsetX,
|
smask.offsetX,
|
||||||
smask.offsetY
|
smask.offsetY
|
||||||
);
|
);
|
||||||
|
ctx.save();
|
||||||
genericComposeSMask(
|
ctx.globalAlpha = 1;
|
||||||
maskCtx,
|
ctx.globalCompositeOperation = "source-over";
|
||||||
layerCtx,
|
ctx.setTransform(1, 0, 0, 1, 0, 0);
|
||||||
mask.width,
|
ctx.drawImage(layerCtx.canvas, 0, 0);
|
||||||
mask.height,
|
ctx.restore();
|
||||||
smask.subtype,
|
|
||||||
smask.backdrop,
|
|
||||||
smask.transferMap
|
|
||||||
);
|
|
||||||
ctx.drawImage(mask, 0, 0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function getImageSmoothingEnabled(transform, interpolate) {
|
function getImageSmoothingEnabled(transform, interpolate) {
|
||||||
@ -907,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;
|
||||||
@ -927,6 +1116,7 @@ class CanvasGraphics {
|
|||||||
this.smaskStack = [];
|
this.smaskStack = [];
|
||||||
this.smaskCounter = 0;
|
this.smaskCounter = 0;
|
||||||
this.tempSMask = null;
|
this.tempSMask = null;
|
||||||
|
this.suspendedCtx = null;
|
||||||
this.contentVisible = true;
|
this.contentVisible = true;
|
||||||
this.markedContentStack = [];
|
this.markedContentStack = [];
|
||||||
this.optionalContentConfig = optionalContentConfig;
|
this.optionalContentConfig = optionalContentConfig;
|
||||||
@ -1319,25 +1509,9 @@ class CanvasGraphics {
|
|||||||
this.ctx.globalCompositeOperation = value;
|
this.ctx.globalCompositeOperation = value;
|
||||||
break;
|
break;
|
||||||
case "SMask":
|
case "SMask":
|
||||||
if (this.current.activeSMask) {
|
|
||||||
// If SMask is currrenly used, it needs to be suspended or
|
|
||||||
// finished. Suspend only makes sense when at least one save()
|
|
||||||
// was performed and state needs to be reverted on restore().
|
|
||||||
if (
|
|
||||||
this.stateStack.length > 0 &&
|
|
||||||
this.stateStack[this.stateStack.length - 1].activeSMask ===
|
|
||||||
this.current.activeSMask
|
|
||||||
) {
|
|
||||||
this.suspendSMaskGroup();
|
|
||||||
} else {
|
|
||||||
this.endSMaskGroup();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
this.current.activeSMask = value ? this.tempSMask : null;
|
this.current.activeSMask = value ? this.tempSMask : null;
|
||||||
if (this.current.activeSMask) {
|
|
||||||
this.beginSMaskGroup();
|
|
||||||
}
|
|
||||||
this.tempSMask = null;
|
this.tempSMask = null;
|
||||||
|
this.checkSMaskState();
|
||||||
break;
|
break;
|
||||||
case "TR":
|
case "TR":
|
||||||
this.current.transferMaps = value;
|
this.current.transferMaps = value;
|
||||||
@ -1345,10 +1519,31 @@ class CanvasGraphics {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
beginSMaskGroup() {
|
checkSMaskState() {
|
||||||
const activeSMask = this.current.activeSMask;
|
const inSMaskMode = !!this.suspendedCtx;
|
||||||
const drawnWidth = activeSMask.canvas.width;
|
if (this.current.activeSMask && !inSMaskMode) {
|
||||||
const drawnHeight = activeSMask.canvas.height;
|
this.beginSMaskMode();
|
||||||
|
} else if (!this.current.activeSMask && inSMaskMode) {
|
||||||
|
this.endSMaskMode();
|
||||||
|
}
|
||||||
|
// Else, the state is okay and nothing needs to be done.
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Soft mask mode takes the current main drawing canvas and replaces it with
|
||||||
|
* a temporary canvas. Any drawing operations that happen on the temporary
|
||||||
|
* canvas need to be composed with the main canvas that was suspended (see
|
||||||
|
* `compose()`). The temporary canvas also duplicates many of its operations
|
||||||
|
* on the suspended canvas to keep them in sync, so that when the soft mask
|
||||||
|
* mode ends any clipping paths or transformations will still be active and in
|
||||||
|
* the right order on the canvas' graphics state stack.
|
||||||
|
*/
|
||||||
|
beginSMaskMode() {
|
||||||
|
if (this.suspendedCtx) {
|
||||||
|
throw new Error("beginSMaskMode called while already in smask mode");
|
||||||
|
}
|
||||||
|
const drawnWidth = this.ctx.canvas.width;
|
||||||
|
const drawnHeight = this.ctx.canvas.height;
|
||||||
const cacheId = "smaskGroupAt" + this.groupLevel;
|
const cacheId = "smaskGroupAt" + this.groupLevel;
|
||||||
const scratchCanvas = this.cachedCanvases.getCanvas(
|
const scratchCanvas = this.cachedCanvases.getCanvas(
|
||||||
cacheId,
|
cacheId,
|
||||||
@ -1356,84 +1551,57 @@ class CanvasGraphics {
|
|||||||
drawnHeight,
|
drawnHeight,
|
||||||
true
|
true
|
||||||
);
|
);
|
||||||
|
this.suspendedCtx = this.ctx;
|
||||||
|
this.ctx = scratchCanvas.context;
|
||||||
|
const ctx = this.ctx;
|
||||||
|
ctx.setTransform.apply(ctx, this.suspendedCtx.mozCurrentTransform);
|
||||||
|
copyCtxState(this.suspendedCtx, ctx);
|
||||||
|
mirrorContextOperations(ctx, this.suspendedCtx);
|
||||||
|
|
||||||
const currentCtx = this.ctx;
|
|
||||||
const currentTransform = currentCtx.mozCurrentTransform;
|
|
||||||
this.ctx.save();
|
|
||||||
|
|
||||||
const groupCtx = scratchCanvas.context;
|
|
||||||
groupCtx.scale(1 / activeSMask.scaleX, 1 / activeSMask.scaleY);
|
|
||||||
groupCtx.translate(-activeSMask.offsetX, -activeSMask.offsetY);
|
|
||||||
groupCtx.transform.apply(groupCtx, currentTransform);
|
|
||||||
|
|
||||||
activeSMask.startTransformInverse = groupCtx.mozCurrentTransformInverse;
|
|
||||||
|
|
||||||
copyCtxState(currentCtx, groupCtx);
|
|
||||||
this.ctx = groupCtx;
|
|
||||||
this.setGState([
|
this.setGState([
|
||||||
["BM", "source-over"],
|
["BM", "source-over"],
|
||||||
["ca", 1],
|
["ca", 1],
|
||||||
["CA", 1],
|
["CA", 1],
|
||||||
]);
|
]);
|
||||||
this.groupStack.push(currentCtx);
|
|
||||||
this.groupLevel++;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
suspendSMaskGroup() {
|
endSMaskMode() {
|
||||||
// Similar to endSMaskGroup, the intermediate canvas has to be composed
|
if (!this.suspendedCtx) {
|
||||||
// and future ctx state restored.
|
throw new Error("endSMaskMode called while not in smask mode");
|
||||||
const groupCtx = this.ctx;
|
}
|
||||||
this.groupLevel--;
|
// The soft mask is done, now restore the suspended canvas as the main
|
||||||
this.ctx = this.groupStack.pop();
|
// drawing canvas.
|
||||||
|
this.ctx._removeMirroring();
|
||||||
|
copyCtxState(this.ctx, this.suspendedCtx);
|
||||||
|
this.ctx = this.suspendedCtx;
|
||||||
|
|
||||||
composeSMask(this.ctx, this.current.activeSMask, groupCtx);
|
this.current.activeSMask = null;
|
||||||
|
this.suspendedCtx = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
compose(dirtyBox) {
|
||||||
|
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;
|
||||||
|
|
||||||
|
composeSMask(suspendedCtx, smask, this.ctx, dirtyBox);
|
||||||
|
// Whatever was drawn has been moved to the suspended canvas, now clear it
|
||||||
|
// out of the current canvas.
|
||||||
|
this.ctx.save();
|
||||||
|
this.ctx.setTransform(1, 0, 0, 1, 0, 0);
|
||||||
|
this.ctx.clearRect(0, 0, this.ctx.canvas.width, this.ctx.canvas.height);
|
||||||
this.ctx.restore();
|
this.ctx.restore();
|
||||||
this.ctx.save(); // save is needed since SMask will be resumed.
|
|
||||||
copyCtxState(groupCtx, this.ctx);
|
|
||||||
|
|
||||||
// Saving state for resuming.
|
|
||||||
this.current.resumeSMaskCtx = groupCtx;
|
|
||||||
// Transform was changed in the SMask canvas, reflecting this change on
|
|
||||||
// this.ctx.
|
|
||||||
const deltaTransform = Util.transform(
|
|
||||||
this.current.activeSMask.startTransformInverse,
|
|
||||||
groupCtx.mozCurrentTransform
|
|
||||||
);
|
|
||||||
this.ctx.transform.apply(this.ctx, deltaTransform);
|
|
||||||
|
|
||||||
// SMask was composed, the results at the groupCtx can be cleared.
|
|
||||||
groupCtx.save();
|
|
||||||
groupCtx.setTransform(1, 0, 0, 1, 0, 0);
|
|
||||||
groupCtx.clearRect(0, 0, groupCtx.canvas.width, groupCtx.canvas.height);
|
|
||||||
groupCtx.restore();
|
|
||||||
}
|
|
||||||
|
|
||||||
resumeSMaskGroup() {
|
|
||||||
// Resuming state saved by suspendSMaskGroup. We don't need to restore
|
|
||||||
// any groupCtx state since restore() command (the only caller) will do
|
|
||||||
// that for us. See also beginSMaskGroup.
|
|
||||||
const groupCtx = this.current.resumeSMaskCtx;
|
|
||||||
const currentCtx = this.ctx;
|
|
||||||
this.ctx = groupCtx;
|
|
||||||
this.groupStack.push(currentCtx);
|
|
||||||
this.groupLevel++;
|
|
||||||
}
|
|
||||||
|
|
||||||
endSMaskGroup() {
|
|
||||||
const groupCtx = this.ctx;
|
|
||||||
this.groupLevel--;
|
|
||||||
this.ctx = this.groupStack.pop();
|
|
||||||
|
|
||||||
composeSMask(this.ctx, this.current.activeSMask, groupCtx);
|
|
||||||
this.ctx.restore();
|
|
||||||
copyCtxState(groupCtx, this.ctx);
|
|
||||||
// Transform was changed in the SMask canvas, reflecting this change on
|
|
||||||
// this.ctx.
|
|
||||||
const deltaTransform = Util.transform(
|
|
||||||
this.current.activeSMask.startTransformInverse,
|
|
||||||
groupCtx.mozCurrentTransform
|
|
||||||
);
|
|
||||||
this.ctx.transform.apply(this.ctx, deltaTransform);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
save() {
|
save() {
|
||||||
@ -1441,36 +1609,22 @@ class CanvasGraphics {
|
|||||||
const old = this.current;
|
const old = this.current;
|
||||||
this.stateStack.push(old);
|
this.stateStack.push(old);
|
||||||
this.current = old.clone();
|
this.current = old.clone();
|
||||||
this.current.resumeSMaskCtx = null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
restore() {
|
restore() {
|
||||||
// SMask was suspended, we just need to resume it.
|
if (this.stateStack.length === 0 && this.current.activeSMask) {
|
||||||
if (this.current.resumeSMaskCtx) {
|
this.endSMaskMode();
|
||||||
this.resumeSMaskGroup();
|
|
||||||
}
|
|
||||||
// SMask has to be finished once there is no states that are using the
|
|
||||||
// same SMask.
|
|
||||||
if (
|
|
||||||
this.current.activeSMask !== null &&
|
|
||||||
(this.stateStack.length === 0 ||
|
|
||||||
this.stateStack[this.stateStack.length - 1].activeSMask !==
|
|
||||||
this.current.activeSMask)
|
|
||||||
) {
|
|
||||||
this.endSMaskGroup();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.stateStack.length !== 0) {
|
if (this.stateStack.length !== 0) {
|
||||||
this.current = this.stateStack.pop();
|
this.current = this.stateStack.pop();
|
||||||
this.ctx.restore();
|
this.ctx.restore();
|
||||||
|
this.checkSMaskState();
|
||||||
|
|
||||||
// Ensure that the clipping path is reset (fixes issue6413.pdf).
|
// Ensure that the clipping path is reset (fixes issue6413.pdf).
|
||||||
this.pendingClip = null;
|
this.pendingClip = null;
|
||||||
|
|
||||||
this._cachedGetSinglePixelWidth = null;
|
this._cachedGetSinglePixelWidth = null;
|
||||||
} else {
|
|
||||||
// We've finished all the SMask groups, reflect that in our state.
|
|
||||||
this.current.activeSMask = null;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1486,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:
|
||||||
@ -1504,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(
|
||||||
@ -1528,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,
|
||||||
@ -1539,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:
|
||||||
@ -1599,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;
|
||||||
@ -1627,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;
|
||||||
@ -1640,7 +1838,7 @@ class CanvasGraphics {
|
|||||||
ctx.restore();
|
ctx.restore();
|
||||||
}
|
}
|
||||||
if (consumePath) {
|
if (consumePath) {
|
||||||
this.consumePath();
|
this.consumePath(intersect);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2092,6 +2290,7 @@ class CanvasGraphics {
|
|||||||
current.x += x * textHScale;
|
current.x += x * textHScale;
|
||||||
}
|
}
|
||||||
ctx.restore();
|
ctx.restore();
|
||||||
|
this.compose();
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2282,6 +2481,7 @@ class CanvasGraphics {
|
|||||||
this.ctx.fillRect(-1e10, -1e10, 2e10, 2e10);
|
this.ctx.fillRect(-1e10, -1e10, 2e10, 2e10);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.compose(this.current.getClippedPathBoundingBox());
|
||||||
this.restore();
|
this.restore();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2311,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();
|
||||||
}
|
}
|
||||||
@ -2330,6 +2540,14 @@ class CanvasGraphics {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.save();
|
this.save();
|
||||||
|
// 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.
|
||||||
|
const suspendedCtx = this.suspendedCtx;
|
||||||
|
if (this.current.activeSMask) {
|
||||||
|
this.suspendedCtx = null;
|
||||||
|
this.current.activeSMask = null;
|
||||||
|
}
|
||||||
|
|
||||||
const currentCtx = this.ctx;
|
const currentCtx = this.ctx;
|
||||||
// TODO non-isolated groups - according to Rik at adobe non-isolated
|
// TODO non-isolated groups - according to Rik at adobe non-isolated
|
||||||
// group results aren't usually that different and they even have tools
|
// group results aren't usually that different and they even have tools
|
||||||
@ -2393,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.
|
||||||
@ -2432,6 +2652,7 @@ class CanvasGraphics {
|
|||||||
currentCtx.setTransform(1, 0, 0, 1, 0, 0);
|
currentCtx.setTransform(1, 0, 0, 1, 0, 0);
|
||||||
currentCtx.translate(offsetX, offsetY);
|
currentCtx.translate(offsetX, offsetY);
|
||||||
currentCtx.scale(scaleX, scaleY);
|
currentCtx.scale(scaleX, scaleY);
|
||||||
|
currentCtx.save();
|
||||||
}
|
}
|
||||||
// The transparency group inherits all off the current graphics state
|
// The transparency group inherits all off the current graphics state
|
||||||
// except the blend mode, soft mask, and alpha constants.
|
// except the blend mode, soft mask, and alpha constants.
|
||||||
@ -2442,11 +2663,11 @@ class CanvasGraphics {
|
|||||||
["ca", 1],
|
["ca", 1],
|
||||||
["CA", 1],
|
["CA", 1],
|
||||||
]);
|
]);
|
||||||
this.groupStack.push(currentCtx);
|
this.groupStack.push({
|
||||||
|
ctx: currentCtx,
|
||||||
|
suspendedCtx,
|
||||||
|
});
|
||||||
this.groupLevel++;
|
this.groupLevel++;
|
||||||
|
|
||||||
// Resetting mask state, masks will be applied on restore of the group.
|
|
||||||
this.current.activeSMask = null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
endGroup(group) {
|
endGroup(group) {
|
||||||
@ -2455,17 +2676,33 @@ class CanvasGraphics {
|
|||||||
}
|
}
|
||||||
this.groupLevel--;
|
this.groupLevel--;
|
||||||
const groupCtx = this.ctx;
|
const groupCtx = this.ctx;
|
||||||
this.ctx = this.groupStack.pop();
|
const { ctx, suspendedCtx } = this.groupStack.pop();
|
||||||
|
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.
|
||||||
this.ctx.imageSmoothingEnabled = false;
|
this.ctx.imageSmoothingEnabled = false;
|
||||||
|
|
||||||
|
if (suspendedCtx) {
|
||||||
|
this.suspendedCtx = suspendedCtx;
|
||||||
|
}
|
||||||
|
|
||||||
if (group.smask) {
|
if (group.smask) {
|
||||||
this.tempSMask = this.smaskStack.pop();
|
this.tempSMask = this.smaskStack.pop();
|
||||||
|
this.restore();
|
||||||
} else {
|
} else {
|
||||||
|
this.ctx.restore();
|
||||||
|
const currentMtx = this.ctx.mozCurrentTransform;
|
||||||
|
this.restore();
|
||||||
|
this.ctx.save();
|
||||||
|
this.ctx.setTransform.apply(this.ctx, currentMtx);
|
||||||
|
const dirtyBox = Util.getAxialAlignedBoundingBox(
|
||||||
|
[0, 0, groupCtx.canvas.width, groupCtx.canvas.height],
|
||||||
|
currentMtx
|
||||||
|
);
|
||||||
this.ctx.drawImage(groupCtx.canvas, 0, 0);
|
this.ctx.drawImage(groupCtx.canvas, 0, 0);
|
||||||
|
this.ctx.restore();
|
||||||
|
this.compose(dirtyBox);
|
||||||
}
|
}
|
||||||
this.restore();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
beginAnnotations() {
|
beginAnnotations() {
|
||||||
@ -2482,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];
|
||||||
@ -2531,6 +2771,7 @@ class CanvasGraphics {
|
|||||||
ctx.setTransform(1, 0, 0, 1, 0, 0);
|
ctx.setTransform(1, 0, 0, 1, 0, 0);
|
||||||
ctx.drawImage(maskCanvas, mask.offsetX, mask.offsetY);
|
ctx.drawImage(maskCanvas, mask.offsetX, mask.offsetY);
|
||||||
ctx.restore();
|
ctx.restore();
|
||||||
|
this.compose();
|
||||||
}
|
}
|
||||||
|
|
||||||
paintImageMaskXObjectRepeat(
|
paintImageMaskXObjectRepeat(
|
||||||
@ -2565,6 +2806,7 @@ class CanvasGraphics {
|
|||||||
ctx.drawImage(mask.canvas, x, y);
|
ctx.drawImage(mask.canvas, x, y);
|
||||||
}
|
}
|
||||||
ctx.restore();
|
ctx.restore();
|
||||||
|
this.compose();
|
||||||
}
|
}
|
||||||
|
|
||||||
paintImageMaskXObjectGroup(images) {
|
paintImageMaskXObjectGroup(images) {
|
||||||
@ -2610,6 +2852,7 @@ class CanvasGraphics {
|
|||||||
ctx.drawImage(maskCanvas.canvas, 0, 0, width, height, 0, -1, 1, 1);
|
ctx.drawImage(maskCanvas.canvas, 0, 0, width, height, 0, -1, 1, 1);
|
||||||
ctx.restore();
|
ctx.restore();
|
||||||
}
|
}
|
||||||
|
this.compose();
|
||||||
}
|
}
|
||||||
|
|
||||||
paintImageXObject(objId) {
|
paintImageXObject(objId) {
|
||||||
@ -2711,6 +2954,7 @@ class CanvasGraphics {
|
|||||||
height: height / ctx.mozCurrentTransformInverse[3],
|
height: height / ctx.mozCurrentTransformInverse[3],
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
this.compose();
|
||||||
this.restore();
|
this.restore();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2754,6 +2998,7 @@ class CanvasGraphics {
|
|||||||
}
|
}
|
||||||
ctx.restore();
|
ctx.restore();
|
||||||
}
|
}
|
||||||
|
this.compose();
|
||||||
}
|
}
|
||||||
|
|
||||||
paintSolidColorImageMask() {
|
paintSolidColorImageMask() {
|
||||||
@ -2761,6 +3006,7 @@ class CanvasGraphics {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.ctx.fillRect(0, 0, 1, 1);
|
this.ctx.fillRect(0, 0, 1, 1);
|
||||||
|
this.compose();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Marked content
|
// Marked content
|
||||||
@ -2809,7 +3055,13 @@ class CanvasGraphics {
|
|||||||
|
|
||||||
// Helper functions
|
// Helper functions
|
||||||
|
|
||||||
consumePath() {
|
consumePath(clipBox) {
|
||||||
|
if (this.pendingClip) {
|
||||||
|
this.current.updateClipFromPath();
|
||||||
|
}
|
||||||
|
if (!this.pendingClip) {
|
||||||
|
this.compose(clipBox);
|
||||||
|
}
|
||||||
const ctx = this.ctx;
|
const ctx = this.ctx;
|
||||||
if (this.pendingClip) {
|
if (this.pendingClip) {
|
||||||
if (this.pendingClip === EO_CLIP) {
|
if (this.pendingClip === EO_CLIP) {
|
||||||
|
@ -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 = [
|
||||||
|
5
test/pdfs/.gitignore
vendored
5
test/pdfs/.gitignore
vendored
@ -17,6 +17,7 @@
|
|||||||
!issue2391-1.pdf
|
!issue2391-1.pdf
|
||||||
!issue2391-2.pdf
|
!issue2391-2.pdf
|
||||||
!issue14046.pdf
|
!issue14046.pdf
|
||||||
|
!issue7891_bc1.pdf
|
||||||
!issue3214.pdf
|
!issue3214.pdf
|
||||||
!issue4665.pdf
|
!issue4665.pdf
|
||||||
!issue4684.pdf
|
!issue4684.pdf
|
||||||
@ -106,6 +107,7 @@
|
|||||||
!bug1057544.pdf
|
!bug1057544.pdf
|
||||||
!issue11150_reduced.pdf
|
!issue11150_reduced.pdf
|
||||||
!issue6127.pdf
|
!issue6127.pdf
|
||||||
|
!issue7891_bc0.pdf
|
||||||
!issue11242_reduced.pdf
|
!issue11242_reduced.pdf
|
||||||
!issue11279.pdf
|
!issue11279.pdf
|
||||||
!issue11362.pdf
|
!issue11362.pdf
|
||||||
@ -246,6 +248,7 @@
|
|||||||
!issue2931.pdf
|
!issue2931.pdf
|
||||||
!issue3323.pdf
|
!issue3323.pdf
|
||||||
!issue4304.pdf
|
!issue4304.pdf
|
||||||
|
!issue9017_reduced.pdf
|
||||||
!issue4379.pdf
|
!issue4379.pdf
|
||||||
!issue4550.pdf
|
!issue4550.pdf
|
||||||
!issue13316_reduced.pdf
|
!issue13316_reduced.pdf
|
||||||
@ -381,6 +384,7 @@
|
|||||||
!issue5044.pdf
|
!issue5044.pdf
|
||||||
!issue1512r.pdf
|
!issue1512r.pdf
|
||||||
!issue2128r.pdf
|
!issue2128r.pdf
|
||||||
|
!bug1703683_page2_reduced.pdf
|
||||||
!issue5540.pdf
|
!issue5540.pdf
|
||||||
!issue5549.pdf
|
!issue5549.pdf
|
||||||
!visibility_expressions.pdf
|
!visibility_expressions.pdf
|
||||||
@ -435,6 +439,7 @@
|
|||||||
!annotation-freetext.pdf
|
!annotation-freetext.pdf
|
||||||
!annotation-line.pdf
|
!annotation-line.pdf
|
||||||
!evaljs.pdf
|
!evaljs.pdf
|
||||||
|
!issue12798_page1_reduced.pdf
|
||||||
!annotation-line-without-appearance.pdf
|
!annotation-line-without-appearance.pdf
|
||||||
!bug1669099.pdf
|
!bug1669099.pdf
|
||||||
!annotation-square-circle.pdf
|
!annotation-square-circle.pdf
|
||||||
|
BIN
test/pdfs/bug1703683_page2_reduced.pdf
Normal file
BIN
test/pdfs/bug1703683_page2_reduced.pdf
Normal file
Binary file not shown.
BIN
test/pdfs/issue12798_page1_reduced.pdf
Normal file
BIN
test/pdfs/issue12798_page1_reduced.pdf
Normal file
Binary file not shown.
BIN
test/pdfs/issue7891_bc0.pdf
Normal file
BIN
test/pdfs/issue7891_bc0.pdf
Normal file
Binary file not shown.
BIN
test/pdfs/issue7891_bc1.pdf
Normal file
BIN
test/pdfs/issue7891_bc1.pdf
Normal file
Binary file not shown.
BIN
test/pdfs/issue9017_reduced.pdf
Normal file
BIN
test/pdfs/issue9017_reduced.pdf
Normal file
Binary file not shown.
@ -530,6 +530,12 @@
|
|||||||
"rounds": 1,
|
"rounds": 1,
|
||||||
"type": "eq"
|
"type": "eq"
|
||||||
},
|
},
|
||||||
|
{ "id": "issue12798_page1_reduced",
|
||||||
|
"file": "pdfs/issue12798_page1_reduced.pdf",
|
||||||
|
"md5": "f4c3e91c181b510929ade67c1e34c5c5",
|
||||||
|
"rounds": 1,
|
||||||
|
"type": "eq"
|
||||||
|
},
|
||||||
{ "id": "hmm-pdf",
|
{ "id": "hmm-pdf",
|
||||||
"file": "pdfs/hmm.pdf",
|
"file": "pdfs/hmm.pdf",
|
||||||
"md5": "e08467e60101ee5f4a59716e86db6dc9",
|
"md5": "e08467e60101ee5f4a59716e86db6dc9",
|
||||||
@ -769,6 +775,12 @@
|
|||||||
"lastPage": 1,
|
"lastPage": 1,
|
||||||
"type": "eq"
|
"type": "eq"
|
||||||
},
|
},
|
||||||
|
{ "id": "bug1703683_page2_reduced",
|
||||||
|
"file": "pdfs/bug1703683_page2_reduced.pdf",
|
||||||
|
"md5": "2b6d5d617438cf72c76c25f46ec6ad75",
|
||||||
|
"rounds": 1,
|
||||||
|
"type": "eq"
|
||||||
|
},
|
||||||
{ "id": "issue6707",
|
{ "id": "issue6707",
|
||||||
"file": "pdfs/issue6707.pdf",
|
"file": "pdfs/issue6707.pdf",
|
||||||
"md5": "068ceaec23d265b1d38dfa6ab279f017",
|
"md5": "068ceaec23d265b1d38dfa6ab279f017",
|
||||||
@ -883,6 +895,12 @@
|
|||||||
"type": "eq",
|
"type": "eq",
|
||||||
"annotations": true
|
"annotations": true
|
||||||
},
|
},
|
||||||
|
{ "id": "issue9017_reduced",
|
||||||
|
"file": "pdfs/issue9017_reduced.pdf",
|
||||||
|
"md5": "8b45b3ea91778d6d98f407620d645f48",
|
||||||
|
"rounds": 1,
|
||||||
|
"type": "eq"
|
||||||
|
},
|
||||||
{ "id": "issue4934",
|
{ "id": "issue4934",
|
||||||
"file": "pdfs/issue4934.pdf",
|
"file": "pdfs/issue4934.pdf",
|
||||||
"md5": "6099da44f677702ae65a648b51a2226d",
|
"md5": "6099da44f677702ae65a648b51a2226d",
|
||||||
@ -3450,6 +3468,12 @@
|
|||||||
"link": true,
|
"link": true,
|
||||||
"type": "eq"
|
"type": "eq"
|
||||||
},
|
},
|
||||||
|
{ "id": "issue7891_bc0",
|
||||||
|
"file": "pdfs/issue7891_bc0.pdf",
|
||||||
|
"md5": "744a22244a4e4708b7f1691eec155fc8",
|
||||||
|
"rounds": 1,
|
||||||
|
"type": "eq"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"id": "issue6165",
|
"id": "issue6165",
|
||||||
"file": "pdfs/issue6165.pdf",
|
"file": "pdfs/issue6165.pdf",
|
||||||
@ -4537,6 +4561,12 @@
|
|||||||
"link": true,
|
"link": true,
|
||||||
"type": "load"
|
"type": "load"
|
||||||
},
|
},
|
||||||
|
{ "id": "issue7891_bc1",
|
||||||
|
"file": "pdfs/issue7891_bc1.pdf",
|
||||||
|
"md5": "86b1796da7dad09f93ce68a8ad495a24",
|
||||||
|
"rounds": 1,
|
||||||
|
"type": "eq"
|
||||||
|
},
|
||||||
{ "id": "issue3062",
|
{ "id": "issue3062",
|
||||||
"file": "pdfs/issue3062.pdf",
|
"file": "pdfs/issue3062.pdf",
|
||||||
"md5": "206715f1258f0e117df4180d98dd4d68",
|
"md5": "206715f1258f0e117df4180d98dd4d68",
|
||||||
|
Loading…
x
Reference in New Issue
Block a user