diff --git a/src/display/canvas.js b/src/display/canvas.js index 5df5a73c1..e9b356356 100644 --- a/src/display/canvas.js +++ b/src/display/canvas.js @@ -432,7 +432,8 @@ var CanvasExtraState = (function CanvasExtraStateClosure() { this.fillAlpha = 1; this.strokeAlpha = 1; this.lineWidth = 1; - this.activeSMask = null; // nonclonable field (see the save method below) + this.activeSMask = null; + this.resumeSMaskCtx = null; // nonclonable field (see the save method below) this.old = old; } @@ -869,6 +870,11 @@ var CanvasGraphics = (function CanvasGraphicsClosure() { }, endDrawing: function CanvasGraphics_endDrawing() { + // Finishing all opened operations such as SMask group painting. + if (this.current.activeSMask !== null) { + this.endSMaskGroup(); + } + this.ctx.restore(); if (this.transparentCanvas) { @@ -977,7 +983,16 @@ var CanvasGraphics = (function CanvasGraphicsClosure() { break; case 'SMask': if (this.current.activeSMask) { - this.endSMaskGroup(); + // 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; if (this.current.activeSMask) { @@ -1006,6 +1021,8 @@ var CanvasGraphics = (function CanvasGraphicsClosure() { groupCtx.translate(-activeSMask.offsetX, -activeSMask.offsetY); groupCtx.transform.apply(groupCtx, currentTransform); + activeSMask.startTransformInverse = groupCtx.mozCurrentTransformInverse; + copyCtxState(currentCtx, groupCtx); this.ctx = groupCtx; this.setGState([ @@ -1016,6 +1033,43 @@ var CanvasGraphics = (function CanvasGraphicsClosure() { this.groupStack.push(currentCtx); this.groupLevel++; }, + suspendSMaskGroup: function CanvasGraphics_endSMaskGroup() { + // Similar to endSMaskGroup, the intermediate canvas has to be composed + // and future ctx state restored. + var groupCtx = this.ctx; + this.groupLevel--; + this.ctx = this.groupStack.pop(); + + composeSMask(this.ctx, this.current.activeSMask, groupCtx); + 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. + var 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: function CanvasGraphics_endSMaskGroup() { + // 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. + var groupCtx = this.current.resumeSMaskCtx; + var currentCtx = this.ctx; + this.ctx = groupCtx; + this.groupStack.push(currentCtx); + this.groupLevel++; + }, endSMaskGroup: function CanvasGraphics_endSMaskGroup() { var groupCtx = this.ctx; this.groupLevel--; @@ -1024,20 +1078,34 @@ var CanvasGraphics = (function CanvasGraphicsClosure() { 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. + var deltaTransform = Util.transform( + this.current.activeSMask.startTransformInverse, + groupCtx.mozCurrentTransform); + this.ctx.transform.apply(this.ctx, deltaTransform); }, save: function CanvasGraphics_save() { this.ctx.save(); var old = this.current; this.stateStack.push(old); this.current = old.clone(); - this.current.activeSMask = null; + this.current.resumeSMaskCtx = null; }, restore: function CanvasGraphics_restore() { - if (this.stateStack.length !== 0) { - if (this.current.activeSMask !== null) { - this.endSMaskGroup(); - } + // SMask was suspended, we just need to resume it. + if (this.current.resumeSMaskCtx) { + 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) { this.current = this.stateStack.pop(); this.ctx.restore(); @@ -1825,7 +1893,8 @@ var CanvasGraphics = (function CanvasGraphicsClosure() { scaleY: scaleY, subtype: group.smask.subtype, backdrop: group.smask.backdrop, - transferMap: group.smask.transferMap || null + transferMap: group.smask.transferMap || null, + startTransformInverse: null, // used during suspend operation }); } else { // Setup the current ctx so when the group is popped we draw it at the @@ -1845,6 +1914,9 @@ var CanvasGraphics = (function CanvasGraphicsClosure() { ]); this.groupStack.push(currentCtx); this.groupLevel++; + + // Reseting mask state, masks will be applied on restore of the group. + this.current.activeSMask = null; }, endGroup: function CanvasGraphics_endGroup(group) { diff --git a/test/pdfs/.gitignore b/test/pdfs/.gitignore index dc676a0c6..6fd483b22 100644 --- a/test/pdfs/.gitignore +++ b/test/pdfs/.gitignore @@ -215,6 +215,7 @@ !issue6069.pdf !issue6106.pdf !issue6296.pdf +!bug852992_reduced.pdf !issue6298.pdf !issue6889.pdf !bug1001080.pdf diff --git a/test/pdfs/bug1199237.pdf.link b/test/pdfs/bug1199237.pdf.link new file mode 100644 index 000000000..21b7e134e --- /dev/null +++ b/test/pdfs/bug1199237.pdf.link @@ -0,0 +1 @@ +https://bugzilla.mozilla.org/attachment.cgi?id=8653724 diff --git a/test/pdfs/bug852992_reduced.pdf b/test/pdfs/bug852992_reduced.pdf new file mode 100644 index 000000000..c4b1f9cde Binary files /dev/null and b/test/pdfs/bug852992_reduced.pdf differ diff --git a/test/pdfs/issue6165.pdf.link b/test/pdfs/issue6165.pdf.link new file mode 100644 index 000000000..db380e7b6 --- /dev/null +++ b/test/pdfs/issue6165.pdf.link @@ -0,0 +1 @@ +http://web.archive.org/web/20160405054842/http://www.sec.gov/investor/pubs/sec-guide-to-proxy-brochures.pdf diff --git a/test/test_manifest.json b/test/test_manifest.json index 7495f2e1d..5f7f175ee 100644 --- a/test/test_manifest.json +++ b/test/test_manifest.json @@ -1503,6 +1503,33 @@ "link": true, "type": "eq" }, + { + "id": "bug852992", + "file": "pdfs/bug852992_reduced.pdf", + "md5": "c11439fe3b7f8bc39d89dcff58c50a0c", + "rounds": 1, + "type": "eq" + }, + { + "id": "bug1199237", + "file": "pdfs/bug1199237.pdf", + "md5": "e9a63d3207ccc65a4955d5723546e962", + "rounds": 1, + "firstPage": 1, + "lastPage": 1, + "link": true, + "type": "eq" + }, + { + "id": "issue6165", + "file": "pdfs/issue6165.pdf", + "md5": "84ebde43b9121aa2ef8026388a4f4244", + "rounds": 1, + "firstPage": 1, + "lastPage": 1, + "link": true, + "type": "eq" + }, { "id": "issue6019-text", "file": "pdfs/issue6019.pdf",