diff --git a/src/display/canvas.js b/src/display/canvas.js index 13f0790a9123b..76c9730964769 100644 --- a/src/display/canvas.js +++ b/src/display/canvas.js @@ -483,6 +483,7 @@ class CanvasExtraState { // Default fore and background colors this.fillColor = "#000000"; this.strokeColor = "#000000"; + this.tilingPatternRectangle = null; this.patternFill = false; // Note: fill alpha applies to all non-stroking operations this.fillAlpha = 1; @@ -497,6 +498,7 @@ class CanvasExtraState { clone() { const clone = Object.create(this); clone.clipBox = this.clipBox.slice(); + clone.tilingPatternRectangle = this.tilingPatternRectangle?.slice(); return clone; } @@ -1610,8 +1612,7 @@ class CanvasGraphics { // Path constructPath(ops, args, minMax) { - const ctx = this.ctx; - const current = this.current; + const { ctx, current } = this; let x = current.x, y = current.y; let startX, startY; @@ -1649,6 +1650,13 @@ class CanvasGraphics { if (!isScalingMatrix) { current.updateRectMinMax(currentTransform, [x, y, xw, yh]); } + const dims = current.tilingPatternDims; + if (dims && isNaN(dims[0]) && x === 0 && y === 0) { + dims[0] = width; + dims[1] = height; + } else { + current.tilingPatternDims = null; + } ctx.closePath(); break; case OPS.moveTo: @@ -1658,6 +1666,7 @@ class CanvasGraphics { if (!isScalingMatrix) { current.updatePathMinMax(currentTransform, x, y); } + current.tilingPatternDims = null; break; case OPS.lineTo: x = args[j++]; @@ -1666,6 +1675,7 @@ class CanvasGraphics { if (!isScalingMatrix) { current.updatePathMinMax(currentTransform, x, y); } + current.tilingPatternDims = null; break; case OPS.curveTo: startX = x; @@ -1693,6 +1703,7 @@ class CanvasGraphics { minMaxForBezier ); j += 6; + current.tilingPatternDims = null; break; case OPS.curveTo2: startX = x; @@ -1720,6 +1731,7 @@ class CanvasGraphics { x = args[j + 2]; y = args[j + 3]; j += 4; + current.tilingPatternDims = null; break; case OPS.curveTo3: startX = x; @@ -1740,6 +1752,7 @@ class CanvasGraphics { minMaxForBezier ); j += 4; + current.tilingPatternDims = null; break; case OPS.closePath: ctx.closePath(); @@ -1796,8 +1809,22 @@ class CanvasGraphics { const fillColor = this.current.fillColor; const isPatternFill = this.current.patternFill; let needRestore = false; + const intersect = this.current.getClippedPathBoundingBox(); if (isPatternFill) { + const dims = this.current.tilingPatternDims; + if (dims && fillColor.canSkipPatternCanvas(dims)) { + // A rectangle with its origin at (0, 0) is filled with a tiling + // pattern. If the dimensions of the rectangle are smaller than the + // tile's ones we can directly draw the tile without having to use + // a pattern. + fillColor.drawPattern(this); + if (consumePath) { + this.consumePath(intersect); + } + this.current.tilingPatternDims = null; + return; + } ctx.save(); ctx.fillStyle = fillColor.getPattern( ctx, @@ -1808,7 +1835,6 @@ class CanvasGraphics { needRestore = true; } - const intersect = this.current.getClippedPathBoundingBox(); if (this.contentVisible && intersect !== null) { if (this.pendingEOFill) { ctx.fill("evenodd"); @@ -2381,8 +2407,11 @@ class CanvasGraphics { } setFillColorN() { - this.current.fillColor = this.getColorN_Pattern(arguments); + const pattern = (this.current.fillColor = + this.getColorN_Pattern(arguments)); this.current.patternFill = true; + this.current.tilingPatternDims = + pattern instanceof TilingPattern ? [NaN, NaN] : null; } setStrokeRGBColor(r, g, b) { diff --git a/src/display/pattern_helper.js b/src/display/pattern_helper.js index 4241695a23aa1..0f8d2ecff95b3 100644 --- a/src/display/pattern_helper.js +++ b/src/display/pattern_helper.js @@ -468,6 +468,21 @@ class TilingPattern { this.baseTransform = baseTransform; } + canSkipPatternCanvas([width, height]) { + return ( + width <= this.xstep + 1e-6 && + height <= this.ystep + 1e-6 && + Util.isIdentityMatrix(this.matrix) + ); + } + + drawPattern(owner) { + owner.save(); + owner.ctx.clip(); + owner.executeOperatorList(this.operatorList); + owner.restore(); + } + createPatternCanvas(owner) { const { bbox, diff --git a/src/shared/util.js b/src/shared/util.js index 19be8031ec8b1..0551436dc78e5 100644 --- a/src/shared/util.js +++ b/src/shared/util.js @@ -659,6 +659,17 @@ class Util { return `#${hexNumbers[r]}${hexNumbers[g]}${hexNumbers[b]}`; } + static isIdentityMatrix(m) { + return ( + Math.abs(m[0] - 1) <= 1e-6 && + Math.abs(m[1]) <= 1e-6 && + Math.abs(m[2]) <= 1e-6 && + Math.abs(m[3] - 1) <= 1e-6 && + Math.abs(m[4]) <= 1e-6 && + Math.abs(m[5]) <= 1e-6 + ); + } + // Apply a scaling matrix to some min/max values. // If a scaling factor is negative then min and max must be // swapped.