diff --git a/src/display/canvas.js b/src/display/canvas.js index 098741ce9..b468ff111 100644 --- a/src/display/canvas.js +++ b/src/display/canvas.js @@ -484,6 +484,7 @@ class CanvasExtraState { this.fillColor = "#000000"; this.strokeColor = "#000000"; this.patternFill = false; + this.patternStroke = false; // Note: fill alpha applies to all non-stroking operations this.fillAlpha = 1; this.strokeAlpha = 1; @@ -2001,7 +2002,7 @@ class CanvasGraphics { this.moveText(0, this.current.leading); } - paintChar(character, x, y, patternTransform) { + paintChar(character, x, y, patternFillTransform, patternStrokeTransform) { const ctx = this.ctx; const current = this.current; const font = current.font; @@ -2013,30 +2014,39 @@ class CanvasGraphics { textRenderingMode & TextRenderingMode.ADD_TO_PATH_FLAG ); const patternFill = current.patternFill && !font.missingFile; + const patternStroke = current.patternStroke && !font.missingFile; let addToPath; - if (font.disableFontFace || isAddToPathSet || patternFill) { + if ( + font.disableFontFace || + isAddToPathSet || + patternFill || + patternStroke + ) { addToPath = font.getPathGenerator(this.commonObjs, character); } - if (font.disableFontFace || patternFill) { + if (font.disableFontFace || patternFill || patternStroke) { ctx.save(); ctx.translate(x, y); ctx.beginPath(); addToPath(ctx, fontSize); - if (patternTransform) { - ctx.setTransform(...patternTransform); - } if ( fillStrokeMode === TextRenderingMode.FILL || fillStrokeMode === TextRenderingMode.FILL_STROKE ) { + if (patternFillTransform) { + ctx.setTransform(...patternFillTransform); + } ctx.fill(); } if ( fillStrokeMode === TextRenderingMode.STROKE || fillStrokeMode === TextRenderingMode.FILL_STROKE ) { + if (patternStrokeTransform) { + ctx.setTransform(...patternStrokeTransform); + } ctx.stroke(); } ctx.restore(); @@ -2127,7 +2137,7 @@ class CanvasGraphics { ctx.scale(textHScale, 1); } - let patternTransform; + let patternFillTransform, patternStrokeTransform; if (current.patternFill) { ctx.save(); const pattern = current.fillColor.getPattern( @@ -2136,11 +2146,24 @@ class CanvasGraphics { getCurrentTransformInverse(ctx), PathType.FILL ); - patternTransform = getCurrentTransform(ctx); + patternFillTransform = getCurrentTransform(ctx); ctx.restore(); ctx.fillStyle = pattern; } + if (current.patternStroke) { + ctx.save(); + const pattern = current.strokeColor.getPattern( + ctx, + this, + getCurrentTransformInverse(ctx), + PathType.STROKE + ); + patternStrokeTransform = getCurrentTransform(ctx); + ctx.restore(); + ctx.strokeStyle = pattern; + } + let lineWidth = current.lineWidth; const scale = current.textMatrixScale; if (scale === 0 || lineWidth === 0) { @@ -2233,7 +2256,13 @@ class CanvasGraphics { // common case ctx.fillText(character, scaledX, scaledY); } else { - this.paintChar(character, scaledX, scaledY, patternTransform); + this.paintChar( + character, + scaledX, + scaledY, + patternFillTransform, + patternStrokeTransform + ); if (accent) { const scaledAccentX = scaledX + (fontSize * accent.offset.x) / fontSizeScale; @@ -2243,7 +2272,8 @@ class CanvasGraphics { accent.fontChar, scaledAccentX, scaledAccentY, - patternTransform + patternFillTransform, + patternStrokeTransform ); } } @@ -2379,6 +2409,7 @@ class CanvasGraphics { setStrokeColorN() { this.current.strokeColor = this.getColorN_Pattern(arguments); + this.current.patternStroke = true; } setFillColorN() { @@ -2392,10 +2423,12 @@ class CanvasGraphics { g, b ); + this.current.patternStroke = false; } setStrokeTransparent() { this.ctx.strokeStyle = this.current.strokeColor = "transparent"; + this.current.patternStroke = false; } setFillRGBColor(r, g, b) { diff --git a/src/display/font_loader.js b/src/display/font_loader.js index 1ec7160c8..f0012eb35 100644 --- a/src/display/font_loader.js +++ b/src/display/font_loader.js @@ -498,6 +498,9 @@ class FontFaceObject { break; } } + // From https://learn.microsoft.com/en-us/typography/opentype/spec/cff2#paths + // All contours must be closed with a lineto operation. + commands.push(ctx => ctx.closePath()); return (this.compiledGlyphs[character] = function glyphDrawer(ctx, size) { commands[0](ctx); diff --git a/test/pdfs/issue19022.pdf.link b/test/pdfs/issue19022.pdf.link new file mode 100644 index 000000000..dc560b607 --- /dev/null +++ b/test/pdfs/issue19022.pdf.link @@ -0,0 +1 @@ +https://github.com/user-attachments/files/17703776/linear-gradient-on-rect_text.pdf diff --git a/test/test_manifest.json b/test/test_manifest.json index 65cff4331..47c214b50 100644 --- a/test/test_manifest.json +++ b/test/test_manifest.json @@ -10757,5 +10757,13 @@ "md5": "73e8cd32bd063e42fcc4b270c78549b1", "rounds": 1, "type": "eq" + }, + { + "id": "issue19022", + "file": "pdfs/issue19022.pdf", + "md5": "7d7a9c45f93a9db269800855ccffe7cd", + "rounds": 1, + "type": "eq", + "link": true } ]