Apply gradient when stroking text

It fixes #19022.

I noticed that the glyph contours weren't correct (for T and x) and
because we forgot to close the contour.
This commit is contained in:
Calixte Denizet 2024-11-11 15:38:12 +01:00
parent bff6738966
commit 79e1f155ac
4 changed files with 55 additions and 10 deletions

View File

@ -484,6 +484,7 @@ class CanvasExtraState {
this.fillColor = "#000000"; this.fillColor = "#000000";
this.strokeColor = "#000000"; this.strokeColor = "#000000";
this.patternFill = false; this.patternFill = false;
this.patternStroke = false;
// Note: fill alpha applies to all non-stroking operations // Note: fill alpha applies to all non-stroking operations
this.fillAlpha = 1; this.fillAlpha = 1;
this.strokeAlpha = 1; this.strokeAlpha = 1;
@ -2001,7 +2002,7 @@ class CanvasGraphics {
this.moveText(0, this.current.leading); this.moveText(0, this.current.leading);
} }
paintChar(character, x, y, patternTransform) { paintChar(character, x, y, patternFillTransform, patternStrokeTransform) {
const ctx = this.ctx; const ctx = this.ctx;
const current = this.current; const current = this.current;
const font = current.font; const font = current.font;
@ -2013,30 +2014,39 @@ class CanvasGraphics {
textRenderingMode & TextRenderingMode.ADD_TO_PATH_FLAG textRenderingMode & TextRenderingMode.ADD_TO_PATH_FLAG
); );
const patternFill = current.patternFill && !font.missingFile; const patternFill = current.patternFill && !font.missingFile;
const patternStroke = current.patternStroke && !font.missingFile;
let addToPath; let addToPath;
if (font.disableFontFace || isAddToPathSet || patternFill) { if (
font.disableFontFace ||
isAddToPathSet ||
patternFill ||
patternStroke
) {
addToPath = font.getPathGenerator(this.commonObjs, character); addToPath = font.getPathGenerator(this.commonObjs, character);
} }
if (font.disableFontFace || patternFill) { if (font.disableFontFace || patternFill || patternStroke) {
ctx.save(); ctx.save();
ctx.translate(x, y); ctx.translate(x, y);
ctx.beginPath(); ctx.beginPath();
addToPath(ctx, fontSize); addToPath(ctx, fontSize);
if (patternTransform) {
ctx.setTransform(...patternTransform);
}
if ( if (
fillStrokeMode === TextRenderingMode.FILL || fillStrokeMode === TextRenderingMode.FILL ||
fillStrokeMode === TextRenderingMode.FILL_STROKE fillStrokeMode === TextRenderingMode.FILL_STROKE
) { ) {
if (patternFillTransform) {
ctx.setTransform(...patternFillTransform);
}
ctx.fill(); ctx.fill();
} }
if ( if (
fillStrokeMode === TextRenderingMode.STROKE || fillStrokeMode === TextRenderingMode.STROKE ||
fillStrokeMode === TextRenderingMode.FILL_STROKE fillStrokeMode === TextRenderingMode.FILL_STROKE
) { ) {
if (patternStrokeTransform) {
ctx.setTransform(...patternStrokeTransform);
}
ctx.stroke(); ctx.stroke();
} }
ctx.restore(); ctx.restore();
@ -2127,7 +2137,7 @@ class CanvasGraphics {
ctx.scale(textHScale, 1); ctx.scale(textHScale, 1);
} }
let patternTransform; let patternFillTransform, patternStrokeTransform;
if (current.patternFill) { if (current.patternFill) {
ctx.save(); ctx.save();
const pattern = current.fillColor.getPattern( const pattern = current.fillColor.getPattern(
@ -2136,11 +2146,24 @@ class CanvasGraphics {
getCurrentTransformInverse(ctx), getCurrentTransformInverse(ctx),
PathType.FILL PathType.FILL
); );
patternTransform = getCurrentTransform(ctx); patternFillTransform = getCurrentTransform(ctx);
ctx.restore(); ctx.restore();
ctx.fillStyle = pattern; 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; let lineWidth = current.lineWidth;
const scale = current.textMatrixScale; const scale = current.textMatrixScale;
if (scale === 0 || lineWidth === 0) { if (scale === 0 || lineWidth === 0) {
@ -2233,7 +2256,13 @@ class CanvasGraphics {
// common case // common case
ctx.fillText(character, scaledX, scaledY); ctx.fillText(character, scaledX, scaledY);
} else { } else {
this.paintChar(character, scaledX, scaledY, patternTransform); this.paintChar(
character,
scaledX,
scaledY,
patternFillTransform,
patternStrokeTransform
);
if (accent) { if (accent) {
const scaledAccentX = const scaledAccentX =
scaledX + (fontSize * accent.offset.x) / fontSizeScale; scaledX + (fontSize * accent.offset.x) / fontSizeScale;
@ -2243,7 +2272,8 @@ class CanvasGraphics {
accent.fontChar, accent.fontChar,
scaledAccentX, scaledAccentX,
scaledAccentY, scaledAccentY,
patternTransform patternFillTransform,
patternStrokeTransform
); );
} }
} }
@ -2379,6 +2409,7 @@ class CanvasGraphics {
setStrokeColorN() { setStrokeColorN() {
this.current.strokeColor = this.getColorN_Pattern(arguments); this.current.strokeColor = this.getColorN_Pattern(arguments);
this.current.patternStroke = true;
} }
setFillColorN() { setFillColorN() {
@ -2392,10 +2423,12 @@ class CanvasGraphics {
g, g,
b b
); );
this.current.patternStroke = false;
} }
setStrokeTransparent() { setStrokeTransparent() {
this.ctx.strokeStyle = this.current.strokeColor = "transparent"; this.ctx.strokeStyle = this.current.strokeColor = "transparent";
this.current.patternStroke = false;
} }
setFillRGBColor(r, g, b) { setFillRGBColor(r, g, b) {

View File

@ -498,6 +498,9 @@ class FontFaceObject {
break; 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) { return (this.compiledGlyphs[character] = function glyphDrawer(ctx, size) {
commands[0](ctx); commands[0](ctx);

View File

@ -0,0 +1 @@
https://github.com/user-attachments/files/17703776/linear-gradient-on-rect_text.pdf

View File

@ -10757,5 +10757,13 @@
"md5": "73e8cd32bd063e42fcc4b270c78549b1", "md5": "73e8cd32bd063e42fcc4b270c78549b1",
"rounds": 1, "rounds": 1,
"type": "eq" "type": "eq"
},
{
"id": "issue19022",
"file": "pdfs/issue19022.pdf",
"md5": "7d7a9c45f93a9db269800855ccffe7cd",
"rounds": 1,
"type": "eq",
"link": true
} }
] ]