Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Simplify the way to pass the glyph drawing instructions from the worker to the main thread #18015

Merged
merged 1 commit into from
Apr 28, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
11 changes: 10 additions & 1 deletion src/core/evaluator.js
Original file line number Diff line number Diff line change
Expand Up @@ -4391,6 +4391,15 @@ class PartialEvaluator {
}
}

let fontMatrix = dict.getArray("FontMatrix");
if (
!Array.isArray(fontMatrix) ||
fontMatrix.length !== 6 ||
fontMatrix.some(x => typeof x !== "number")
) {
fontMatrix = FONT_IDENTITY_MATRIX;
}

const properties = {
type,
name: fontName.name,
Expand All @@ -4403,7 +4412,7 @@ class PartialEvaluator {
loadedName: baseDict.loadedName,
composite,
fixedPitch: false,
fontMatrix: dict.getArray("FontMatrix") || FONT_IDENTITY_MATRIX,
fontMatrix,
firstChar,
lastChar,
toUnicode,
Expand Down
77 changes: 51 additions & 26 deletions src/core/font_renderer.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
import {
bytesToString,
FONT_IDENTITY_MATRIX,
FontRenderOps,
FormatError,
unreachable,
warn,
Expand Down Expand Up @@ -180,13 +181,13 @@ function lookupCmap(ranges, unicode) {

function compileGlyf(code, cmds, font) {
function moveTo(x, y) {
cmds.push({ cmd: "moveTo", args: [x, y] });
cmds.add(FontRenderOps.MOVE_TO, [x, y]);
}
function lineTo(x, y) {
cmds.push({ cmd: "lineTo", args: [x, y] });
cmds.add(FontRenderOps.LINE_TO, [x, y]);
}
function quadraticCurveTo(xa, ya, x, y) {
cmds.push({ cmd: "quadraticCurveTo", args: [xa, ya, x, y] });
cmds.add(FontRenderOps.QUADRATIC_CURVE_TO, [xa, ya, x, y]);
}

let i = 0;
Expand Down Expand Up @@ -247,20 +248,22 @@ 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.push(
{ cmd: "save" },
{
cmd: "transform",
args: [scaleX, scale01, scale10, scaleY, x, y],
}
);
cmds.add(FontRenderOps.SAVE);
cmds.add(FontRenderOps.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.push({ cmd: "restore" });
cmds.add(FontRenderOps.RESTORE);
}
} while (flags & 0x20);
} else {
Expand Down Expand Up @@ -365,13 +368,13 @@ function compileGlyf(code, cmds, font) {

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

const stack = [];
Expand Down Expand Up @@ -544,7 +547,8 @@ function compileCharString(charStringCode, cmds, font, glyphId) {
const bchar = stack.pop();
y = stack.pop();
x = stack.pop();
cmds.push({ cmd: "save" }, { cmd: "translate", args: [x, y] });
cmds.add(FontRenderOps.SAVE);
cmds.add(FontRenderOps.TRANSLATE, [x, y]);
let cmap = lookupCmap(
font.cmap,
String.fromCharCode(font.glyphNameMap[StandardEncoding[achar]])
Expand All @@ -555,7 +559,7 @@ function compileCharString(charStringCode, cmds, font, glyphId) {
font,
cmap.glyphId
);
cmds.push({ cmd: "restore" });
cmds.add(FontRenderOps.RESTORE);

cmap = lookupCmap(
font.cmap,
Expand Down Expand Up @@ -741,6 +745,27 @@ function compileCharString(charStringCode, cmds, font, glyphId) {

const NOOP = [];

class Commands {
cmds = [];

add(cmd, args) {
if (args) {
if (args.some(arg => typeof arg !== "number")) {
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);
}
} else {
this.cmds.push(cmd);
}
}
}

class CompiledFont {
constructor(fontMatrix) {
if (this.constructor === CompiledFont) {
Expand All @@ -757,8 +782,10 @@ class CompiledFont {
let fn = this.compiledGlyphs[glyphId];
if (!fn) {
try {
fn = this.compileGlyph(this.glyphs[glyphId], glyphId);
this.compiledGlyphs[glyphId] = fn;
fn = this.compiledGlyphs[glyphId] = this.compileGlyph(
this.glyphs[glyphId],
glyphId
);
} catch (ex) {
// Avoid attempting to re-compile a corrupt glyph.
this.compiledGlyphs[glyphId] = NOOP;
Expand Down Expand Up @@ -793,16 +820,14 @@ class CompiledFont {
}
}

const cmds = [
{ cmd: "save" },
{ cmd: "transform", args: fontMatrix.slice() },
{ cmd: "scale", args: ["size", "-size"] },
];
const cmds = new Commands();
cmds.add(FontRenderOps.SAVE);
cmds.add(FontRenderOps.TRANSFORM, fontMatrix.slice());
cmds.add(FontRenderOps.SCALE);
this.compileGlyphImpl(code, cmds, glyphId);
cmds.add(FontRenderOps.RESTORE);

cmds.push({ cmd: "restore" });

return cmds;
return cmds.cmds;
}

compileGlyphImpl() {
Expand Down
6 changes: 2 additions & 4 deletions src/display/api.js
Original file line number Diff line number Diff line change
Expand Up @@ -169,8 +169,8 @@ const DefaultStandardFontDataFactory =
* pixels, i.e. width * height. Images above this value will not be rendered.
* Use -1 for no limit, which is also the default value.
* @property {boolean} [isEvalSupported] - Determines if we can evaluate strings
* as JavaScript. Primarily used to improve performance of font rendering, and
* when parsing PDF functions. The default value is `true`.
* as JavaScript. Primarily used to improve performance of PDF functions.
* The default value is `true`.
* @property {boolean} [isOffscreenCanvasSupported] - Determines if we can use
* `OffscreenCanvas` in the worker. Primarily used to improve performance of
* image conversion/rendering.
Expand Down Expand Up @@ -384,7 +384,6 @@ function getDocument(src) {
};
const transportParams = {
ignoreErrors,
isEvalSupported,
disableFontFace,
fontExtraProperties,
enableXfa,
Expand Down Expand Up @@ -2744,7 +2743,6 @@ class WorkerTransport {
? (font, url) => globalThis.FontInspector.fontAdded(font, url)
: null;
const font = new FontFaceObject(exportedData, {
isEvalSupported: params.isEvalSupported,
disableFontFace: params.disableFontFace,
ignoreErrors: params.ignoreErrors,
inspectFont,
Expand Down
104 changes: 74 additions & 30 deletions src/display/font_loader.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
import {
assert,
bytesToString,
FeatureTest,
FontRenderOps,
isNodeJS,
shadow,
string32,
Expand Down Expand Up @@ -362,19 +362,13 @@ class FontLoader {
class FontFaceObject {
constructor(
translatedData,
{
isEvalSupported = true,
disableFontFace = false,
ignoreErrors = false,
inspectFont = null,
}
{ disableFontFace = false, ignoreErrors = false, inspectFont = null }
) {
this.compiledGlyphs = Object.create(null);
// importing translated data
for (const i in translatedData) {
this[i] = translatedData[i];
}
this.isEvalSupported = isEvalSupported !== false;
this.disableFontFace = disableFontFace === true;
this.ignoreErrors = ignoreErrors === true;
this._inspectFont = inspectFont;
Expand Down Expand Up @@ -440,35 +434,85 @@ class FontFaceObject {
throw 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.
});
}

// If we can, compile cmds into JS for MAXIMUM SPEED...
if (this.isEvalSupported && FeatureTest.isEvalSupported) {
const jsBuf = [];
for (const current of cmds) {
const args = current.args !== undefined ? current.args.join(",") : "";
jsBuf.push("c.", current.cmd, "(", args, ");\n");
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;
}
// eslint-disable-next-line no-new-func
return (this.compiledGlyphs[character] = new Function(
"c",
"size",
jsBuf.join("")
));
}
// ... but fall back on using Function.prototype.apply() if we're
// blocked from using eval() for whatever reason (like CSP policies).
return (this.compiledGlyphs[character] = function (c, size) {
for (const current of cmds) {
if (current.cmd === "scale") {
current.args = [size, -size];
}
// eslint-disable-next-line prefer-spread
c[current.cmd].apply(c, current.args);
}

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);
}
});
}
Expand Down
13 changes: 13 additions & 0 deletions src/shared/util.js
Original file line number Diff line number Diff line change
Expand Up @@ -1073,6 +1073,18 @@ function getUuid() {

const AnnotationPrefix = "pdfjs_internal_id_";

const FontRenderOps = {
BEZIER_CURVE_TO: 0,
MOVE_TO: 1,
LINE_TO: 2,
QUADRATIC_CURVE_TO: 3,
RESTORE: 4,
SAVE: 5,
SCALE: 6,
TRANSFORM: 7,
TRANSLATE: 8,
};

export {
AbortException,
AnnotationActionEventType,
Expand All @@ -1095,6 +1107,7 @@ export {
DocumentActionEventType,
FeatureTest,
FONT_IDENTITY_MATRIX,
FontRenderOps,
FormatError,
getModificationDate,
getUuid,
Expand Down