diff --git a/src/core/pattern.js b/src/core/pattern.js index eedd8b686..250c8a7c5 100644 --- a/src/core/pattern.js +++ b/src/core/pattern.js @@ -245,18 +245,17 @@ Shadings.RadialAxial = (function RadialAxialClosure() { unreachable(`getPattern type unknown: ${shadingType}`); } - const matrix = this.matrix; - if (matrix) { - p0 = Util.applyTransform(p0, matrix); - p1 = Util.applyTransform(p1, matrix); - if (shadingType === ShadingType.RADIAL) { - const scale = Util.singularValueDecompose2dScale(matrix); - r0 *= scale[0]; - r1 *= scale[1]; - } - } - - return ["RadialAxial", type, this.bbox, this.colorStops, p0, p1, r0, r1]; + return [ + "RadialAxial", + type, + this.bbox, + this.colorStops, + p0, + p1, + r0, + r1, + this.matrix, + ]; }, }; diff --git a/src/display/canvas.js b/src/display/canvas.js index 53e4449ec..3e8f80836 100644 --- a/src/display/canvas.js +++ b/src/display/canvas.js @@ -1386,24 +1386,11 @@ const CanvasGraphics = (function CanvasGraphicsClosure() { ctx.globalAlpha = this.current.strokeAlpha; if (this.contentVisible) { if (typeof strokeColor === "object" && strokeColor?.getPattern) { - // for patterns, we transform to pattern space, calculate - // the pattern, call stroke, and restore to user space - ctx.save(); - // The current transform will be replaced while building the pattern, - // but the line width needs to be adjusted by the current transform, - // so we must scale it. To properly fix this we should be using a - // pattern transform instead (see #10955). - const transform = ctx.mozCurrentTransform; - const scale = Util.singularValueDecompose2dScale(transform)[0]; - ctx.strokeStyle = strokeColor.getPattern(ctx, this); const lineWidth = this.getSinglePixelWidth(); - const scaledLineWidth = this.current.lineWidth * scale; - if (lineWidth < 0 && -lineWidth >= scaledLineWidth) { - ctx.resetTransform(); - ctx.lineWidth = Math.round(this._combinedScaleFactor); - } else { - ctx.lineWidth = Math.max(lineWidth, scaledLineWidth); - } + ctx.save(); + ctx.strokeStyle = strokeColor.getPattern(ctx, this); + // Prevent drawing too thin lines by enforcing a minimum line width. + ctx.lineWidth = Math.max(lineWidth, this.current.lineWidth); ctx.stroke(); ctx.restore(); } else { @@ -1444,9 +1431,6 @@ const CanvasGraphics = (function CanvasGraphicsClosure() { if (isPatternFill) { ctx.save(); - if (this.baseTransform) { - ctx.setTransform.apply(ctx, this.baseTransform); - } ctx.fillStyle = fillColor.getPattern(ctx, this); needRestore = true; } diff --git a/src/display/pattern_helper.js b/src/display/pattern_helper.js index 337003eb0..90a88a63a 100644 --- a/src/display/pattern_helper.js +++ b/src/display/pattern_helper.js @@ -17,6 +17,19 @@ import { FormatError, info, Util } from "../shared/util.js"; const ShadingIRs = {}; +let svgElement; + +// TODO: remove this when Firefox ESR supports DOMMatrix. +function createMatrix(matrix) { + if (typeof DOMMatrix !== "undefined") { + return new DOMMatrix(matrix); + } + if (!svgElement) { + svgElement = document.createElementNS("http://www.w3.org/2000/svg", "svg"); + } + return svgElement.createSVGMatrix(matrix); +} + function applyBoundingBox(ctx, bbox) { if (!bbox || typeof Path2D === "undefined") { return; @@ -37,21 +50,57 @@ ShadingIRs.RadialAxial = { const p1 = raw[5]; const r0 = raw[6]; const r1 = raw[7]; + const matrix = raw[8]; + return { - getPattern: function RadialAxial_getPattern(ctx) { - applyBoundingBox(ctx, bbox); + getPattern: function RadialAxial_getPattern(ctx, owner, shadingFill) { + const tmpCanvas = owner.cachedCanvases.getCanvas( + "pattern", + ctx.canvas.width, + ctx.canvas.height, + true + ); + + const tmpCtx = tmpCanvas.context; + tmpCtx.clearRect(0, 0, tmpCtx.canvas.width, tmpCtx.canvas.height); + tmpCtx.beginPath(); + tmpCtx.rect(0, 0, tmpCtx.canvas.width, tmpCtx.canvas.height); + + if (!shadingFill) { + tmpCtx.setTransform.apply(tmpCtx, owner.baseTransform); + if (matrix) { + tmpCtx.transform.apply(tmpCtx, matrix); + } + } else { + tmpCtx.setTransform.apply(tmpCtx, ctx.mozCurrentTransform); + } + applyBoundingBox(tmpCtx, bbox); + let grad; if (type === "axial") { - grad = ctx.createLinearGradient(p0[0], p0[1], p1[0], p1[1]); + grad = tmpCtx.createLinearGradient(p0[0], p0[1], p1[0], p1[1]); } else if (type === "radial") { - grad = ctx.createRadialGradient(p0[0], p0[1], r0, p1[0], p1[1], r1); + grad = tmpCtx.createRadialGradient( + p0[0], + p0[1], + r0, + p1[0], + p1[1], + r1 + ); } for (let i = 0, ii = colorStops.length; i < ii; ++i) { const c = colorStops[i]; grad.addColorStop(c[0], c[1]); } - return grad; + + tmpCtx.fillStyle = grad; + tmpCtx.fill(); + + const pattern = ctx.createPattern(tmpCanvas.canvas, "repeat"); + pattern.setTransform(createMatrix(ctx.mozCurrentTransformInverse)); + return pattern; }, }; }, @@ -505,19 +554,17 @@ const TilingPattern = (function TilingPatternClosure() { graphics.transform(dimx.scale, 0, 0, dimy.scale, 0, 0); - // transform coordinates to pattern space - graphics.transform(1, 0, 0, 1, -x0, -y0); - this.clipBbox(graphics, bbox, x0, y0, x1, y1); + graphics.baseTransform = graphics.ctx.mozCurrentTransform.slice(); + graphics.executeOperatorList(operatorList); - this.ctx.transform(1, 0, 0, 1, x0, y0); - - // Rescale canvas so that the ctx.createPattern call generates a pattern - // with the desired size. - this.ctx.scale(1 / dimx.scale, 1 / dimy.scale); - return tmpCanvas.canvas; + return { + canvas: tmpCanvas.canvas, + scaleX: dimx.scale, + scaleY: dimy.scale, + }; }, getSizeAndScale: function TilingPattern_getSizeAndScale( @@ -579,15 +626,34 @@ const TilingPattern = (function TilingPatternClosure() { } }, - getPattern: function TilingPattern_getPattern(ctx, owner) { + getPattern: function TilingPattern_getPattern(ctx, owner, shadingFill) { ctx = this.ctx; // PDF spec 8.7.2 NOTE 1: pattern's matrix is relative to initial matrix. - ctx.setTransform.apply(ctx, this.baseTransform); - ctx.transform.apply(ctx, this.matrix); + let matrix = ctx.mozCurrentTransformInverse; + if (!shadingFill) { + matrix = Util.transform(matrix, owner.baseTransform); + if (this.matrix) { + matrix = Util.transform(matrix, this.matrix); + } + } const temporaryPatternCanvas = this.createPatternCanvas(owner); - return ctx.createPattern(temporaryPatternCanvas, "repeat"); + let domMatrix = createMatrix(matrix); + // Rescale and so that the ctx.createPattern call generates a pattern with + // the desired size. + domMatrix = domMatrix.scale( + 1 / temporaryPatternCanvas.scaleX, + 1 / temporaryPatternCanvas.scaleY + ); + + const pattern = ctx.createPattern( + temporaryPatternCanvas.canvas, + "repeat" + ); + pattern.setTransform(domMatrix); + + return pattern; }, }; diff --git a/test/pdfs/.gitignore b/test/pdfs/.gitignore index 43d39f2d3..b5912bd60 100644 --- a/test/pdfs/.gitignore +++ b/test/pdfs/.gitignore @@ -42,6 +42,7 @@ !issue7406.pdf !issue7426.pdf !issue7439.pdf +!issue7847_radial.pdf !issue7446.pdf !issue7492.pdf !issue7544.pdf @@ -101,6 +102,7 @@ !issue11242_reduced.pdf !issue11279.pdf !issue11362.pdf +!issue13325_reduced.pdf !issue11578_reduced.pdf !issue11651.pdf !issue11878.pdf @@ -296,6 +298,7 @@ !bug1028735.pdf !bug1046314.pdf !bug1065245.pdf +!issue6769.pdf !bug1151216.pdf !bug1175962.pdf !bug1020226.pdf @@ -365,6 +368,7 @@ !issue5481.pdf !issue5567.pdf !issue5701.pdf +!issue6769_no_matrix.pdf !issue12007_reduced.pdf !issue5896.pdf !issue6010_1.pdf @@ -378,6 +382,7 @@ !issue13271.pdf !issue6298.pdf !issue6889.pdf +!issue11473.pdf !bug1001080.pdf !bug1671312_reduced.pdf !bug1671312_ArialNarrow.pdf diff --git a/test/pdfs/issue11473.pdf b/test/pdfs/issue11473.pdf new file mode 100644 index 000000000..048586d6f Binary files /dev/null and b/test/pdfs/issue11473.pdf differ diff --git a/test/pdfs/issue13325_reduced.pdf b/test/pdfs/issue13325_reduced.pdf new file mode 100644 index 000000000..2a120ffaf Binary files /dev/null and b/test/pdfs/issue13325_reduced.pdf differ diff --git a/test/pdfs/issue6769.pdf b/test/pdfs/issue6769.pdf new file mode 100644 index 000000000..5d928b007 --- /dev/null +++ b/test/pdfs/issue6769.pdf @@ -0,0 +1,114 @@ +%PDF-1.5 +%âãÏÓ +1 0 obj +<< +/Kids [2 0 R] +/Type /Pages +/Count 1 +>> +endobj +2 0 obj +<< +/Resources 3 0 R +/Type /Page +/Parent 1 0 R +/Contents 4 0 R +/MediaBox [0 0 100 100] +/Group +<< +/CS /DeviceRGB +/I true +/Type /Group +/S /Transparency +>> +>> +endobj +3 0 obj +<< +/ExtGState +<< +/a0 +<< +/CA 1 +/ca 1 +>> +>> +/Pattern +<< +/p5 5 0 R +>> +>> +endobj +5 0 obj +<< +/PatternType 2 +/Type /Pattern +/Shading +<< +/Coords [0 0 0 0 0 100] +/Extend [true true] +/Function 6 0 R +/ShadingType 3 +/Domain [0 1] +/ColorSpace /DeviceRGB +>> +/Matrix [1 0 0 -1 0 10] +>> +endobj +4 0 obj +<< +/Length 77 +>> +stream +q +/Pattern CS /p5 SCN /a0 gs +0.3 0 0 0.1 0 0 cm +50 w +100 100 m +300 700 l +S +Q + +endstream +endobj +6 0 obj +<< +/FunctionType 2 +/N 1 +/C1 [0 1 0] +/C0 [0 0 1] +/Domain [0 1] +>> +endobj +7 0 obj +<< +/Type /Catalog +/Pages 1 0 R +>> +endobj +8 0 obj +<< +/Creator (cairo 1.13.1 \(http://cairographics.org\)) +/Producer (cairo 1.13.1 \(http://cairographics.org\)) +>> +endobj xref +0 9 +0000000000 65535 f +0000000015 00000 n +0000000074 00000 n +0000000247 00000 n +0000000542 00000 n +0000000337 00000 n +0000000672 00000 n +0000000754 00000 n +0000000805 00000 n +trailer + +<< +/Info 8 0 R +/Root 7 0 R +/Size 9 +>> +startxref +934 +%%EOF diff --git a/test/pdfs/issue6769_no_matrix.pdf b/test/pdfs/issue6769_no_matrix.pdf new file mode 100644 index 000000000..cc7e00cb1 --- /dev/null +++ b/test/pdfs/issue6769_no_matrix.pdf @@ -0,0 +1,114 @@ +%PDF-1.5 +%âãÏÓ +1 0 obj +<< +/Kids [2 0 R] +/Type /Pages +/Count 1 +>> +endobj +2 0 obj +<< +/Resources 3 0 R +/Type /Page +/Parent 1 0 R +/Contents 4 0 R +/MediaBox [0 0 100 100] +/Group +<< +/CS /DeviceRGB +/I true +/Type /Group +/S /Transparency +>> +>> +endobj +3 0 obj +<< +/ExtGState +<< +/a0 +<< +/CA 1 +/ca 1 +>> +>> +/Pattern +<< +/p5 5 0 R +>> +>> +endobj +5 0 obj +<< +/PatternType 2 +/Type /Pattern +/Shading +<< +/Coords [0 0 0 0 0 100] +/Extend [true true] +/Function 6 0 R +/ShadingType 3 +/Domain [0 1] +/ColorSpace /DeviceRGB +>> +/Matrix [2 0 0 2 0 0] +>> +endobj +4 0 obj +<< +/Length 77 +>> +stream +q +/Pattern CS /p5 SCN /a0 gs +0.3 0 0 0.1 0 0 cm +50 w +100 100 m +300 700 l +S +Q + +endstream +endobj +6 0 obj +<< +/FunctionType 2 +/N 1 +/C1 [0 1 0] +/C0 [0 0 1] +/Domain [0 1] +>> +endobj +7 0 obj +<< +/Type /Catalog +/Pages 1 0 R +>> +endobj +8 0 obj +<< +/Creator (cairo 1.13.1 \(http://cairographics.org\)) +/Producer (cairo 1.13.1 \(http://cairographics.org\)) +>> +endobj xref +0 9 +0000000000 65535 f +0000000015 00000 n +0000000074 00000 n +0000000247 00000 n +0000000542 00000 n +0000000337 00000 n +0000000672 00000 n +0000000754 00000 n +0000000805 00000 n +trailer + +<< +/Info 8 0 R +/Root 7 0 R +/Size 9 +>> +startxref +934 +%%EOF diff --git a/test/pdfs/issue7847_radial.pdf b/test/pdfs/issue7847_radial.pdf new file mode 100644 index 000000000..892bd63b9 --- /dev/null +++ b/test/pdfs/issue7847_radial.pdf @@ -0,0 +1,130 @@ +%PDF-1.3 +%ÿÿÿÿ +6 0 obj +<< +/FunctionType 2 +/Domain [0 1] +/C0 [1 1 0] +/C1 [0 0.5019607843137255 0] +/N 1 +>> +endobj +7 0 obj +<< +/FunctionType 2 +/Domain [0 1] +/C0 [0 0.5019607843137255 0] +/C1 [1 1 1] +/N 1 +>> +endobj +8 0 obj +<< +/FunctionType 3 +/Domain [0 1] +/Functions [6 0 R 7 0 R] +/Bounds [0.5] +/Encode [0 1 0 1] +>> +endobj +9 0 obj +<< +/ShadingType 3 +/ColorSpace /DeviceRGB +/Coords [0.5 0.5 0 0.5 0.5 0.5] +/Function 8 0 R +/Extend [true true] +>> +endobj +10 0 obj +<< +/Type /Pattern +/PatternType 2 +/Shading 9 0 R +/Matrix [440 0 0 -200 20 220] +>> +endobj +11 0 obj +<< +/Type /ExtGState +/ca 1 +>> +endobj +5 0 obj +<< +/Type /Page +/Parent 1 0 R +/MediaBox [0 0 480 240] +/Contents 3 0 R +/Resources 4 0 R +>> +endobj +4 0 obj +<< +/ProcSet [/PDF /Text /ImageB /ImageC /ImageI] +/Pattern << +/Sh1 10 0 R +>> +/ExtGState << +/Gs1 11 0 R +>> +>> +endobj +3 0 obj +<< +/Length 66 +>> +stream +1 0 0 -1 0 240 cm +20 20 440 200 re +/Pattern cs +/Sh1 scn +/Gs1 gs +f + +endstream +endobj +12 0 obj +<< +/Producer (PDFKit) +/Creator (PDFKit) +/CreationDate (D:20161124005340Z) +>> +endobj +2 0 obj +<< +/Type /Catalog +/Pages 1 0 R +>> +endobj +1 0 obj +<< +/Type /Pages +/Count 1 +/Kids [5 0 R] +>> +endobj +xref +0 13 +0000000000 65535 f +0000001071 00000 n +0000001022 00000 n +0000000813 00000 n +0000000690 00000 n +0000000586 00000 n +0000000015 00000 n +0000000112 00000 n +0000000209 00000 n +0000000317 00000 n +0000000444 00000 n +0000000541 00000 n +0000000929 00000 n +trailer +<< +/Size 13 +/Root 2 0 R +/Info 12 0 R +>> +startxref +1128 +%%EOF diff --git a/test/test_manifest.json b/test/test_manifest.json index 61f33ff4d..4c95470f9 100644 --- a/test/test_manifest.json +++ b/test/test_manifest.json @@ -485,6 +485,12 @@ "lastPage": 2, "type": "load" }, + { "id": "issue7847_radial", + "file": "pdfs/issue7847_radial.pdf", + "md5": "59dc206ec5dd6e6f701943e4694e4147", + "rounds": 1, + "type": "eq" + }, { "id": "rotation", "file": "pdfs/rotation.pdf", "md5": "4fb25ada00ce7528569d9791c14decf5", @@ -637,6 +643,12 @@ "rounds": 1, "type": "eq" }, + { "id": "issue6769_no_matrix", + "file": "pdfs/issue6769_no_matrix.pdf", + "md5": "f88806137fb4592975052ff3508d7b1e", + "rounds": 1, + "type": "eq" + }, { "id": "unix01", "file": "pdfs/unix01.pdf", "md5": "2742999f0bf9b9c035dbb0736096e220", @@ -2552,6 +2564,12 @@ "rounds": 1, "type": "eq" }, + { "id": "issue6769", + "file": "pdfs/issue6769.pdf", + "md5": "724b94d6bd1e8010403328446d0ab061", + "rounds": 1, + "type": "eq" + }, { "id": "issue4304", "file": "pdfs/issue4304.pdf", "md5": "1b1205bf0d7c1ad22a154b60da8e694d", @@ -3976,6 +3994,12 @@ "link": true, "type": "eq" }, + { "id": "issue11473", + "file": "pdfs/issue11473.pdf", + "md5": "c530851b0523c81784ab4ea3115a7c67", + "rounds": 1, + "type": "eq" + }, { "id": "bug1140761", "file": "pdfs/bug1140761.pdf", "md5": "b74eced7634d4f248dc6265f8225d432", @@ -4392,6 +4416,12 @@ "type": "eq", "about": "Support for CMap GBKp-EUC-H" }, + { "id": "issue13325_reduced", + "file": "pdfs/issue13325_reduced.pdf", + "md5": "a9311230d2e3388070711be8748a1aa0", + "rounds": 1, + "type": "eq" + }, { "id": "issue2829", "file": "pdfs/issue2829.pdf", "md5": "f32b28cf8792f6ccc470446bfbb38584",