From 1485c1d1dad83ebf2c936b8074c4010ddb4f2a44 Mon Sep 17 00:00:00 2001 From: Yury Delendik Date: Sat, 9 Apr 2016 18:46:15 -0500 Subject: [PATCH 1/3] Suspending/resuming SMask operation during setGState/restore. --- src/display/canvas.js | 83 ++++++++++++++++++++++++++++++++++++++----- 1 file changed, 75 insertions(+), 8 deletions(-) diff --git a/src/display/canvas.js b/src/display/canvas.js index 5df5a73c1..09beae8ad 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; } @@ -977,7 +978,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 +1016,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 +1028,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 +1073,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 +1888,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 +1909,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) { From 63f62a0e5350b944d369c2902228d48726561c34 Mon Sep 17 00:00:00 2001 From: Yury Delendik Date: Sat, 9 Apr 2016 18:50:11 -0500 Subject: [PATCH 2/3] Finishing SMask at the end of operators list. --- src/display/canvas.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/display/canvas.js b/src/display/canvas.js index 09beae8ad..e9b356356 100644 --- a/src/display/canvas.js +++ b/src/display/canvas.js @@ -870,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) { From d76db416f49286cda4276de40f6d9bb7c761242a Mon Sep 17 00:00:00 2001 From: Yury Delendik Date: Sun, 10 Apr 2016 16:39:15 -0500 Subject: [PATCH 3/3] Adds more SMask tests. --- test/pdfs/.gitignore | 1 + test/pdfs/bug1199237.pdf.link | 1 + test/pdfs/bug852992_reduced.pdf | Bin 0 -> 2191 bytes test/pdfs/issue6165.pdf.link | 1 + test/test_manifest.json | 27 +++++++++++++++++++++++++++ 5 files changed, 30 insertions(+) create mode 100644 test/pdfs/bug1199237.pdf.link create mode 100644 test/pdfs/bug852992_reduced.pdf create mode 100644 test/pdfs/issue6165.pdf.link 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 0000000000000000000000000000000000000000..c4b1f9cde73e6ebeead5478b3fe6c695aa3a899e GIT binary patch literal 2191 zcmcguO>^2X5Y1V?VlO#@ErT%3WHJ*VIMXDAKn|VB(f6(n!c(u;b&ZbPxHI+|=5_QkF~C=kQe1g-AX9|_1>Cus#ImiQ zpq|4IRi>Be81u2I9Y2|9WTa8~*G@YEkq36#r_it;F-Py?#qEO{8yD4w&~4FfUK8a< z=_Lm7GibU9S0H6TSzcFy(1%J(06kmcrlf$N%R2gUPDHgMcYm2H^G4?~`;;dWLCXRa zNzexFgLk%^$BE8j^Gr;w@JvN*ft>ycg!}J9A%04fDAmhF!5ya7RiERr8b)oK=v69{ zY(Y&M+f=>L^43_9^mICB-kD0K<{lcHdB1Po5pa^-u&CE_1ooLnH`ylPCG8b850)I?PLYrJqkB1t zlc_ByU}gKeYb{?H1e%+&Cp#sY gs5SoXV++s%tm`0|*0N=4EWNxL;Qx>P