Fix canvas state getting out of sync from smasks. (bug 1755507)
Soft masks can be enabled/disabled at anytime and at different points in the save/restore stack. This can lead to the amount of save/restores becoming unbalanced across the two canvases. Instead of save/restoring on the temporary canvas change it so we only track state on the main (suspended canvas). I was also getting an out balance stack from patterns, so I've also fixed that and added a warning that will at least show up on chrome. It would be nice to add this so Firefox at some point too. Fixes #11328, #14297 and bug 1755507
This commit is contained in:
parent
f8b2a99ddc
commit
7def6d12c8
@ -191,6 +191,10 @@ function mirrorContextOperations(ctx, destCtx) {
|
||||
}
|
||||
|
||||
function addContextCurrentTransform(ctx) {
|
||||
if (ctx._transformStack) {
|
||||
// Reset the transform stack.
|
||||
ctx._transformStack = [];
|
||||
}
|
||||
// If the context doesn't expose a `mozCurrentTransform`, add a JS based one.
|
||||
if (ctx.mozCurrentTransform) {
|
||||
return;
|
||||
@ -265,6 +269,9 @@ function addContextCurrentTransform(ctx) {
|
||||
};
|
||||
|
||||
ctx.restore = function ctxRestore() {
|
||||
if (this._transformStack.length === 0) {
|
||||
warn("Tried to restore a ctx when the stack was already empty.");
|
||||
}
|
||||
const prev = this._transformStack.pop();
|
||||
if (prev) {
|
||||
this._transformMatrix = prev;
|
||||
@ -1242,7 +1249,7 @@ class CanvasGraphics {
|
||||
|
||||
endDrawing() {
|
||||
// Finishing all opened operations such as SMask group painting.
|
||||
while (this.stateStack.length || this.current.activeSMask !== null) {
|
||||
while (this.stateStack.length || this.inSMaskMode) {
|
||||
this.restore();
|
||||
}
|
||||
|
||||
@ -1503,8 +1510,12 @@ class CanvasGraphics {
|
||||
}
|
||||
}
|
||||
|
||||
get inSMaskMode() {
|
||||
return !!this.suspendedCtx;
|
||||
}
|
||||
|
||||
checkSMaskState() {
|
||||
const inSMaskMode = !!this.suspendedCtx;
|
||||
const inSMaskMode = this.inSMaskMode;
|
||||
if (this.current.activeSMask && !inSMaskMode) {
|
||||
this.beginSMaskMode();
|
||||
} else if (!this.current.activeSMask && inSMaskMode) {
|
||||
@ -1523,7 +1534,7 @@ class CanvasGraphics {
|
||||
* the right order on the canvas' graphics state stack.
|
||||
*/
|
||||
beginSMaskMode() {
|
||||
if (this.suspendedCtx) {
|
||||
if (this.inSMaskMode) {
|
||||
throw new Error("beginSMaskMode called while already in smask mode");
|
||||
}
|
||||
const drawnWidth = this.ctx.canvas.width;
|
||||
@ -1550,7 +1561,7 @@ class CanvasGraphics {
|
||||
}
|
||||
|
||||
endSMaskMode() {
|
||||
if (!this.suspendedCtx) {
|
||||
if (!this.inSMaskMode) {
|
||||
throw new Error("endSMaskMode called while not in smask mode");
|
||||
}
|
||||
// The soft mask is done, now restore the suspended canvas as the main
|
||||
@ -1559,7 +1570,6 @@ class CanvasGraphics {
|
||||
copyCtxState(this.ctx, this.suspendedCtx);
|
||||
this.ctx = this.suspendedCtx;
|
||||
|
||||
this.current.activeSMask = null;
|
||||
this.suspendedCtx = null;
|
||||
}
|
||||
|
||||
@ -1589,20 +1599,36 @@ class CanvasGraphics {
|
||||
}
|
||||
|
||||
save() {
|
||||
this.ctx.save();
|
||||
if (this.inSMaskMode) {
|
||||
// SMask mode may be turned on/off causing us to lose graphics state.
|
||||
// Copy the temporary canvas state to the main(suspended) canvas to keep
|
||||
// it in sync.
|
||||
copyCtxState(this.ctx, this.suspendedCtx);
|
||||
// Don't bother calling save on the temporary canvas since state is not
|
||||
// saved there.
|
||||
this.suspendedCtx.save();
|
||||
} else {
|
||||
this.ctx.save();
|
||||
}
|
||||
const old = this.current;
|
||||
this.stateStack.push(old);
|
||||
this.current = old.clone();
|
||||
}
|
||||
|
||||
restore() {
|
||||
if (this.stateStack.length === 0 && this.current.activeSMask) {
|
||||
if (this.stateStack.length === 0 && this.inSMaskMode) {
|
||||
this.endSMaskMode();
|
||||
}
|
||||
|
||||
if (this.stateStack.length !== 0) {
|
||||
this.current = this.stateStack.pop();
|
||||
this.ctx.restore();
|
||||
if (this.inSMaskMode) {
|
||||
// Graphics state is stored on the main(suspended) canvas. Restore its
|
||||
// state then copy it over to the temporary canvas.
|
||||
this.suspendedCtx.restore();
|
||||
copyCtxState(this.suspendedCtx, this.ctx);
|
||||
} else {
|
||||
this.ctx.restore();
|
||||
}
|
||||
this.checkSMaskState();
|
||||
|
||||
// Ensure that the clipping path is reset (fixes issue6413.pdf).
|
||||
@ -2525,9 +2551,8 @@ class CanvasGraphics {
|
||||
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;
|
||||
if (this.inSMaskMode) {
|
||||
this.endSMaskMode();
|
||||
this.current.activeSMask = null;
|
||||
}
|
||||
|
||||
@ -2646,10 +2671,7 @@ class CanvasGraphics {
|
||||
["ca", 1],
|
||||
["CA", 1],
|
||||
]);
|
||||
this.groupStack.push({
|
||||
ctx: currentCtx,
|
||||
suspendedCtx,
|
||||
});
|
||||
this.groupStack.push(currentCtx);
|
||||
this.groupLevel++;
|
||||
}
|
||||
|
||||
@ -2659,16 +2681,12 @@ class CanvasGraphics {
|
||||
}
|
||||
this.groupLevel--;
|
||||
const groupCtx = this.ctx;
|
||||
const { ctx, suspendedCtx } = this.groupStack.pop();
|
||||
const ctx = this.groupStack.pop();
|
||||
this.ctx = ctx;
|
||||
// Turn off image smoothing to avoid sub pixel interpolation which can
|
||||
// look kind of blurry for some pdfs.
|
||||
this.ctx.imageSmoothingEnabled = false;
|
||||
|
||||
if (suspendedCtx) {
|
||||
this.suspendedCtx = suspendedCtx;
|
||||
}
|
||||
|
||||
if (group.smask) {
|
||||
this.tempSMask = this.smaskStack.pop();
|
||||
this.restore();
|
||||
|
@ -577,6 +577,10 @@ class TilingPattern {
|
||||
tmpCtx.translate(-(dimx.scale * adjustedX0), -(dimy.scale * adjustedY0));
|
||||
graphics.transform(dimx.scale, 0, 0, dimy.scale, 0, 0);
|
||||
|
||||
// To match CanvasGraphics beginDrawing we must save the context here or
|
||||
// else we end up with unbalanced save/restores.
|
||||
tmpCtx.save();
|
||||
|
||||
this.clipBbox(graphics, adjustedX0, adjustedY0, adjustedX1, adjustedY1);
|
||||
|
||||
graphics.baseTransform = graphics.ctx.mozCurrentTransform.slice();
|
||||
|
2
test/pdfs/.gitignore
vendored
2
test/pdfs/.gitignore
vendored
@ -175,6 +175,7 @@
|
||||
!issue8565.pdf
|
||||
!clippath.pdf
|
||||
!issue8795_reduced.pdf
|
||||
!bug1755507.pdf
|
||||
!close-path-bug.pdf
|
||||
!issue6019.pdf
|
||||
!issue6621.pdf
|
||||
@ -491,6 +492,7 @@
|
||||
!pr12564.pdf
|
||||
!pr12828.pdf
|
||||
!secHandler.pdf
|
||||
!issue14297.pdf
|
||||
!rc_annotation.pdf
|
||||
!issue14267.pdf
|
||||
!PDFBOX-4352-0.pdf
|
||||
|
BIN
test/pdfs/bug1755507.pdf
Normal file
BIN
test/pdfs/bug1755507.pdf
Normal file
Binary file not shown.
BIN
test/pdfs/issue14297.pdf
Normal file
BIN
test/pdfs/issue14297.pdf
Normal file
Binary file not shown.
@ -2157,6 +2157,12 @@
|
||||
"type": "eq",
|
||||
"about": "Glyph that gets mapped to unicode non-breaking-space."
|
||||
},
|
||||
{ "id": "issue14297",
|
||||
"file": "pdfs/issue14297.pdf",
|
||||
"md5": "46eb3d4d4bc47c8009fc4c699d213a18",
|
||||
"rounds": 1,
|
||||
"type": "eq"
|
||||
},
|
||||
{ "id": "simpletype3font",
|
||||
"file": "pdfs/simpletype3font.pdf",
|
||||
"md5": "b374c7543920840c61999e9e86939f99",
|
||||
@ -2506,6 +2512,12 @@
|
||||
"lastPage": 2,
|
||||
"type": "eq"
|
||||
},
|
||||
{ "id": "bug1755507",
|
||||
"file": "pdfs/bug1755507.pdf",
|
||||
"md5": "319d73b8fd680cdc583d69b7f7ab29e9",
|
||||
"rounds": 1,
|
||||
"type": "eq"
|
||||
},
|
||||
{ "id": "issue9367",
|
||||
"file": "pdfs/issue9367.pdf",
|
||||
"md5": "81a2c6f1fe5d1bb00ff0479aa6547155",
|
||||
|
Loading…
Reference in New Issue
Block a user