diff --git a/src/display/canvas.js b/src/display/canvas.js index 4815f260a..c5a1d21d2 100644 --- a/src/display/canvas.js +++ b/src/display/canvas.js @@ -13,6 +13,11 @@ * limitations under the License. */ +import { + createMatrix, + getShadingPattern, + TilingPattern, +} from "./pattern_helper.js"; import { FONT_IDENTITY_MATRIX, IDENTITY_MATRIX, @@ -27,7 +32,6 @@ import { Util, warn, } from "../shared/util.js"; -import { getShadingPattern, TilingPattern } from "./pattern_helper.js"; // contexts store most of the state we need natively. // However, PDF needs a bit more state, which we store here. @@ -193,6 +197,17 @@ function addContextCurrentTransform(ctx) { }; } +function getAdjustmentTransformation(transform, width, height) { + // The pattern will be created at the size of the current page or form object, + // but the mask is usually scaled differently and offset, so we must account + // for these to shift and rescale the pattern to the correctly location. + let patternTransform = createMatrix(transform); + patternTransform = patternTransform.scale(1 / width, -1 / height); + patternTransform = patternTransform.translate(0, -height); + patternTransform = patternTransform.inverse(); + return patternTransform; +} + class CachedCanvases { constructor(canvasFactory) { this.canvasFactory = canvasFactory; @@ -2294,8 +2309,16 @@ const CanvasGraphics = (function CanvasGraphicsClosure() { maskCtx.globalCompositeOperation = "source-in"; + let patternTransform = null; + if (isPatternFill) { + patternTransform = getAdjustmentTransformation( + ctx.mozCurrentTransform, + width, + height + ); + } maskCtx.fillStyle = isPatternFill - ? fillColor.getPattern(maskCtx, this) + ? fillColor.getPattern(maskCtx, this, false, patternTransform) : fillColor; maskCtx.fillRect(0, 0, width, height); @@ -2332,14 +2355,23 @@ const CanvasGraphics = (function CanvasGraphicsClosure() { maskCtx.globalCompositeOperation = "source-in"; + const ctx = this.ctx; + let patternTransform = null; + if (isPatternFill) { + patternTransform = getAdjustmentTransformation( + ctx.mozCurrentTransform, + width, + height + ); + } + maskCtx.fillStyle = isPatternFill - ? fillColor.getPattern(maskCtx, this) + ? fillColor.getPattern(maskCtx, this, false, patternTransform) : fillColor; maskCtx.fillRect(0, 0, width, height); maskCtx.restore(); - const ctx = this.ctx; for (let i = 0, ii = positions.length; i < ii; i += 2) { ctx.save(); ctx.transform( @@ -2381,8 +2413,17 @@ const CanvasGraphics = (function CanvasGraphicsClosure() { maskCtx.globalCompositeOperation = "source-in"; + let patternTransform = null; + if (isPatternFill) { + patternTransform = getAdjustmentTransformation( + ctx.mozCurrentTransform, + width, + height + ); + } + maskCtx.fillStyle = isPatternFill - ? fillColor.getPattern(maskCtx, this) + ? fillColor.getPattern(maskCtx, this, false, patternTransform) : fillColor; maskCtx.fillRect(0, 0, width, height); diff --git a/src/display/pattern_helper.js b/src/display/pattern_helper.js index 7545a7408..84abe7057 100644 --- a/src/display/pattern_helper.js +++ b/src/display/pattern_helper.js @@ -72,11 +72,11 @@ class RadialAxialShadingPattern extends BaseShadingPattern { this._matrix = IR[8]; } - getPattern(ctx, owner, shadingFill) { + getPattern(ctx, owner, shadingFill = false, patternTransform = null) { const tmpCanvas = owner.cachedCanvases.getCanvas( "pattern", - ctx.canvas.width, - ctx.canvas.height, + owner.ctx.canvas.width, + owner.ctx.canvas.height, true ); @@ -121,7 +121,11 @@ class RadialAxialShadingPattern extends BaseShadingPattern { tmpCtx.fill(); const pattern = ctx.createPattern(tmpCanvas.canvas, "repeat"); - pattern.setTransform(createMatrix(ctx.mozCurrentTransformInverse)); + if (patternTransform) { + pattern.setTransform(patternTransform); + } else { + pattern.setTransform(createMatrix(ctx.mozCurrentTransformInverse)); + } return pattern; } } @@ -376,7 +380,7 @@ class MeshShadingPattern extends BaseShadingPattern { }; } - getPattern(ctx, owner, shadingFill) { + getPattern(ctx, owner, shadingFill = false, patternTransform = null) { applyBoundingBox(ctx, this._bbox); let scale; if (shadingFill) { @@ -599,7 +603,7 @@ class TilingPattern { } } - getPattern(ctx, owner, shadingFill) { + getPattern(ctx, owner, shadingFill = false, patternTransform = null) { ctx = this.ctx; // PDF spec 8.7.2 NOTE 1: pattern's matrix is relative to initial matrix. let matrix = ctx.mozCurrentTransformInverse; @@ -627,4 +631,4 @@ class TilingPattern { } } -export { getShadingPattern, TilingPattern }; +export { createMatrix, getShadingPattern, TilingPattern }; diff --git a/test/pdfs/.gitignore b/test/pdfs/.gitignore index 40bf67330..ebf34cacb 100644 --- a/test/pdfs/.gitignore +++ b/test/pdfs/.gitignore @@ -406,6 +406,7 @@ !issue13193.pdf !annotation-underline-without-appearance.pdf !issue269_2.pdf +!issue13372.pdf !annotation-strikeout.pdf !annotation-strikeout-without-appearance.pdf !annotation-squiggly.pdf diff --git a/test/pdfs/issue13372.pdf b/test/pdfs/issue13372.pdf new file mode 100644 index 000000000..4cfbcd89e Binary files /dev/null and b/test/pdfs/issue13372.pdf differ diff --git a/test/test_manifest.json b/test/test_manifest.json index cbcc08743..355966334 100644 --- a/test/test_manifest.json +++ b/test/test_manifest.json @@ -1566,6 +1566,12 @@ "rounds": 1, "type": "eq" }, + { "id": "issue13372", + "file": "pdfs/issue13372.pdf", + "md5": "0bc5329623fd554174c5e7653f904e28", + "rounds": 1, + "type": "eq" + }, { "id": "simpletype3font-text", "file": "pdfs/simpletype3font.pdf", "md5": "b374c7543920840c61999e9e86939f99",