Skip to content

Commit

Permalink
Merge pull request #19189 from calixteman/improve_font_drawer
Browse files Browse the repository at this point in the history
Improve perfs of the font renderer
  • Loading branch information
calixteman authored Dec 9, 2024
2 parents 5dc2d25 + 2b05924 commit 99eefb7
Show file tree
Hide file tree
Showing 4 changed files with 88 additions and 149 deletions.
86 changes: 49 additions & 37 deletions src/core/font_renderer.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,13 @@
import {
bytesToString,
FONT_IDENTITY_MATRIX,
FontRenderOps,
FormatError,
unreachable,
Util,
warn,
} from "../shared/util.js";
import { CFFParser } from "./cff_parser.js";
import { getGlyphsUnicode } from "./glyphlist.js";
import { isNumberArray } from "./core_utils.js";
import { StandardEncoding } from "./encodings.js";
import { Stream } from "./stream.js";

Expand Down Expand Up @@ -182,13 +181,13 @@ function lookupCmap(ranges, unicode) {

function compileGlyf(code, cmds, font) {
function moveTo(x, y) {
cmds.add(FontRenderOps.MOVE_TO, [x, y]);
cmds.add("M", [x, y]);
}
function lineTo(x, y) {
cmds.add(FontRenderOps.LINE_TO, [x, y]);
cmds.add("L", [x, y]);
}
function quadraticCurveTo(xa, ya, x, y) {
cmds.add(FontRenderOps.QUADRATIC_CURVE_TO, [xa, ya, x, y]);
cmds.add("Q", [xa, ya, x, y]);
}

let i = 0;
Expand Down Expand Up @@ -249,22 +248,15 @@ function compileGlyf(code, cmds, font) {
if (subglyph) {
// TODO: the transform should be applied only if there is a scale:
// https://github.com/freetype/freetype/blob/edd4fedc5427cf1cf1f4b045e53ff91eb282e9d4/src/truetype/ttgload.c#L1205
cmds.add(FontRenderOps.SAVE);
cmds.add(FontRenderOps.TRANSFORM, [
scaleX,
scale01,
scale10,
scaleY,
x,
y,
]);
cmds.save();
cmds.transform([scaleX, scale01, scale10, scaleY, x, y]);

if (!(flags & 0x02)) {
// TODO: we must use arg1 and arg2 to make something similar to:
// https://github.com/freetype/freetype/blob/edd4fedc5427cf1cf1f4b045e53ff91eb282e9d4/src/truetype/ttgload.c#L1209
}
compileGlyf(subglyph, cmds, font);
cmds.add(FontRenderOps.RESTORE);
cmds.restore();
}
} while (flags & 0x20);
} else {
Expand Down Expand Up @@ -369,13 +361,13 @@ function compileGlyf(code, cmds, font) {

function compileCharString(charStringCode, cmds, font, glyphId) {
function moveTo(x, y) {
cmds.add(FontRenderOps.MOVE_TO, [x, y]);
cmds.add("M", [x, y]);
}
function lineTo(x, y) {
cmds.add(FontRenderOps.LINE_TO, [x, y]);
cmds.add("L", [x, y]);
}
function bezierCurveTo(x1, y1, x2, y2, x, y) {
cmds.add(FontRenderOps.BEZIER_CURVE_TO, [x1, y1, x2, y2, x, y]);
cmds.add("C", [x1, y1, x2, y2, x, y]);
}

const stack = [];
Expand Down Expand Up @@ -548,8 +540,8 @@ function compileCharString(charStringCode, cmds, font, glyphId) {
const bchar = stack.pop();
y = stack.pop();
x = stack.pop();
cmds.add(FontRenderOps.SAVE);
cmds.add(FontRenderOps.TRANSLATE, [x, y]);
cmds.save();
cmds.translate(x, y);
let cmap = lookupCmap(
font.cmap,
String.fromCharCode(font.glyphNameMap[StandardEncoding[achar]])
Expand All @@ -560,7 +552,7 @@ function compileCharString(charStringCode, cmds, font, glyphId) {
font,
cmap.glyphId
);
cmds.add(FontRenderOps.RESTORE);
cmds.restore();

cmap = lookupCmap(
font.cmap,
Expand Down Expand Up @@ -744,27 +736,49 @@ function compileCharString(charStringCode, cmds, font, glyphId) {
parse(charStringCode);
}

const NOOP = [];
const NOOP = "";

class Commands {
cmds = [];

transformStack = [];

currentTransform = [1, 0, 0, 1, 0, 0];

add(cmd, args) {
if (args) {
if (!isNumberArray(args, null)) {
warn(
`Commands.add - "${cmd}" has at least one non-number arg: "${args}".`
);
// "Fix" the wrong args by replacing them with 0.
const newArgs = args.map(arg => (typeof arg === "number" ? arg : 0));
this.cmds.push(cmd, ...newArgs);
} else {
this.cmds.push(cmd, ...args);
const [a, b, c, d, e, f] = this.currentTransform;
for (let i = 0, ii = args.length; i < ii; i += 2) {
const x = args[i];
const y = args[i + 1];
args[i] = a * x + c * y + e;
args[i + 1] = b * x + d * y + f;
}
this.cmds.push(`${cmd}${args.join(" ")}`);
} else {
this.cmds.push(cmd);
}
}

transform(transf) {
this.currentTransform = Util.transform(this.currentTransform, transf);
}

translate(x, y) {
this.transform([1, 0, 0, 1, x, y]);
}

save() {
this.transformStack.push(this.currentTransform.slice());
}

restore() {
this.currentTransform = this.transformStack.pop() || [1, 0, 0, 1, 0, 0];
}

getSVG() {
return this.cmds.join("");
}
}

class CompiledFont {
Expand All @@ -785,7 +799,7 @@ class CompiledFont {
const { charCode, glyphId } = lookupCmap(this.cmap, unicode);
let fn = this.compiledGlyphs[glyphId],
compileEx;
if (!fn) {
if (fn === undefined) {
try {
fn = this.compileGlyph(this.glyphs[glyphId], glyphId);
} catch (ex) {
Expand Down Expand Up @@ -822,13 +836,11 @@ class CompiledFont {
}

const cmds = new Commands();
cmds.add(FontRenderOps.SAVE);
cmds.add(FontRenderOps.TRANSFORM, fontMatrix.slice());
cmds.add(FontRenderOps.SCALE);
cmds.transform(fontMatrix.slice());
this.compileGlyphImpl(code, cmds, glyphId);
cmds.add(FontRenderOps.RESTORE);
cmds.add("Z");

return cmds.cmds;
return cmds.getSVG();
}

compileGlyphImpl() {
Expand Down
53 changes: 38 additions & 15 deletions src/display/canvas.js
Original file line number Diff line number Diff line change
Expand Up @@ -1885,15 +1885,19 @@ class CanvasGraphics {
return;
}

ctx.save();
ctx.beginPath();
for (const path of paths) {
ctx.setTransform(...path.transform);
ctx.translate(path.x, path.y);
path.addToPath(ctx, path.fontSize);
const newPath = new Path2D();
const invTransf = ctx.getTransform().invertSelf();
for (const { transform, x, y, fontSize, path } of paths) {
newPath.addPath(
path,
new DOMMatrix(transform)
.preMultiplySelf(invTransf)
.translate(x, y)
.scale(fontSize, -fontSize)
);
}
ctx.restore();
ctx.clip();

ctx.clip(newPath);
ctx.beginPath();
delete this.pendingTextPaths;
}
Expand Down Expand Up @@ -2002,6 +2006,15 @@ class CanvasGraphics {
this.moveText(0, this.current.leading);
}

#getScaledPath(path, currentTransform, transform) {
const newPath = new Path2D();
newPath.addPath(
path,
new DOMMatrix(transform).invertSelf().multiplySelf(currentTransform)
);
return newPath;
}

paintChar(character, x, y, patternFillTransform, patternStrokeTransform) {
const ctx = this.ctx;
const current = this.current;
Expand All @@ -2016,38 +2029,48 @@ class CanvasGraphics {
const patternFill = current.patternFill && !font.missingFile;
const patternStroke = current.patternStroke && !font.missingFile;

let addToPath;
let path;
if (
font.disableFontFace ||
isAddToPathSet ||
patternFill ||
patternStroke
) {
addToPath = font.getPathGenerator(this.commonObjs, character);
path = font.getPathGenerator(this.commonObjs, character);
}

if (font.disableFontFace || patternFill || patternStroke) {
ctx.save();
ctx.translate(x, y);
ctx.beginPath();
addToPath(ctx, fontSize);
ctx.scale(fontSize, -fontSize);
if (
fillStrokeMode === TextRenderingMode.FILL ||
fillStrokeMode === TextRenderingMode.FILL_STROKE
) {
if (patternFillTransform) {
const currentTransform = ctx.getTransform();
ctx.setTransform(...patternFillTransform);
ctx.fill(
this.#getScaledPath(path, currentTransform, patternFillTransform)
);
} else {
ctx.fill(path);
}
ctx.fill();
}
if (
fillStrokeMode === TextRenderingMode.STROKE ||
fillStrokeMode === TextRenderingMode.FILL_STROKE
) {
if (patternStrokeTransform) {
const currentTransform = ctx.getTransform();
ctx.setTransform(...patternStrokeTransform);
ctx.stroke(
this.#getScaledPath(path, currentTransform, patternStrokeTransform)
);
} else {
ctx.lineWidth /= fontSize;
ctx.stroke(path);
}
ctx.stroke();
}
ctx.restore();
} else {
Expand All @@ -2072,7 +2095,7 @@ class CanvasGraphics {
x,
y,
fontSize,
addToPath,
path,
});
}
}
Expand Down
85 changes: 1 addition & 84 deletions src/display/font_loader.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@

import {
assert,
FontRenderOps,
isNodeJS,
shadow,
string32,
Expand Down Expand Up @@ -427,89 +426,7 @@ class FontFaceObject {
} catch (ex) {
warn(`getPathGenerator - ignoring character: "${ex}".`);
}

if (!Array.isArray(cmds) || cmds.length === 0) {
return (this.compiledGlyphs[character] = function (c, size) {
// No-op function, to allow rendering to continue.
});
}

const commands = [];
for (let i = 0, ii = cmds.length; i < ii; ) {
switch (cmds[i++]) {
case FontRenderOps.BEZIER_CURVE_TO:
{
const [a, b, c, d, e, f] = cmds.slice(i, i + 6);
commands.push(ctx => ctx.bezierCurveTo(a, b, c, d, e, f));
i += 6;
}
break;
case FontRenderOps.MOVE_TO:
{
const [a, b] = cmds.slice(i, i + 2);
commands.push(ctx => ctx.moveTo(a, b));
i += 2;
}
break;
case FontRenderOps.LINE_TO:
{
const [a, b] = cmds.slice(i, i + 2);
commands.push(ctx => ctx.lineTo(a, b));
i += 2;
}
break;
case FontRenderOps.QUADRATIC_CURVE_TO:
{
const [a, b, c, d] = cmds.slice(i, i + 4);
commands.push(ctx => ctx.quadraticCurveTo(a, b, c, d));
i += 4;
}
break;
case FontRenderOps.RESTORE:
commands.push(ctx => ctx.restore());
break;
case FontRenderOps.SAVE:
commands.push(ctx => ctx.save());
break;
case FontRenderOps.SCALE:
// The scale command must be at the third position, after save and
// transform (for the font matrix) commands (see also
// font_renderer.js).
// The goal is to just scale the canvas and then run the commands loop
// without the need to pass the size parameter to each command.
assert(
commands.length === 2,
"Scale command is only valid at the third position."
);
break;
case FontRenderOps.TRANSFORM:
{
const [a, b, c, d, e, f] = cmds.slice(i, i + 6);
commands.push(ctx => ctx.transform(a, b, c, d, e, f));
i += 6;
}
break;
case FontRenderOps.TRANSLATE:
{
const [a, b] = cmds.slice(i, i + 2);
commands.push(ctx => ctx.translate(a, b));
i += 2;
}
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);
commands[1](ctx);
ctx.scale(size, -size);
for (let i = 2, ii = commands.length; i < ii; i++) {
commands[i](ctx);
}
});
return (this.compiledGlyphs[character] = new Path2D(cmds || ""));
}
}

Expand Down
Loading

0 comments on commit 99eefb7

Please sign in to comment.