diff --git a/src/display/canvas.js b/src/display/canvas.js index 359ebd244..03a19114b 100644 --- a/src/display/canvas.js +++ b/src/display/canvas.js @@ -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(); diff --git a/src/display/pattern_helper.js b/src/display/pattern_helper.js index bc32894f7..d14c69530 100644 --- a/src/display/pattern_helper.js +++ b/src/display/pattern_helper.js @@ -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(); diff --git a/test/pdfs/.gitignore b/test/pdfs/.gitignore index 2e8519f82..37446d31d 100644 --- a/test/pdfs/.gitignore +++ b/test/pdfs/.gitignore @@ -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 diff --git a/test/pdfs/bug1755507.pdf b/test/pdfs/bug1755507.pdf new file mode 100644 index 000000000..ca733833b Binary files /dev/null and b/test/pdfs/bug1755507.pdf differ diff --git a/test/pdfs/issue14297.pdf b/test/pdfs/issue14297.pdf new file mode 100644 index 000000000..c69c3ebb1 Binary files /dev/null and b/test/pdfs/issue14297.pdf differ diff --git a/test/test_manifest.json b/test/test_manifest.json index b9302e61d..6984e8cbc 100644 --- a/test/test_manifest.json +++ b/test/test_manifest.json @@ -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",