Skip to content

Commit 1844583

Browse files
calixtemanpull[bot]
authored andcommitted
Simplify the way to pass the glyph drawing instructions from the worker to the main thread
and remove the use of eval in the font loader.
1 parent 4c46061 commit 1844583

File tree

5 files changed

+150
-61
lines changed

5 files changed

+150
-61
lines changed

src/core/evaluator.js

+10-1
Original file line numberDiff line numberDiff line change
@@ -4391,6 +4391,15 @@ class PartialEvaluator {
43914391
}
43924392
}
43934393

4394+
let fontMatrix = dict.getArray("FontMatrix");
4395+
if (
4396+
!Array.isArray(fontMatrix) ||
4397+
fontMatrix.length !== 6 ||
4398+
fontMatrix.some(x => typeof x !== "number")
4399+
) {
4400+
fontMatrix = FONT_IDENTITY_MATRIX;
4401+
}
4402+
43944403
const properties = {
43954404
type,
43964405
name: fontName.name,
@@ -4403,7 +4412,7 @@ class PartialEvaluator {
44034412
loadedName: baseDict.loadedName,
44044413
composite,
44054414
fixedPitch: false,
4406-
fontMatrix: dict.getArray("FontMatrix") || FONT_IDENTITY_MATRIX,
4415+
fontMatrix,
44074416
firstChar,
44084417
lastChar,
44094418
toUnicode,

src/core/font_renderer.js

+51-26
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
import {
1717
bytesToString,
1818
FONT_IDENTITY_MATRIX,
19+
FontRenderOps,
1920
FormatError,
2021
unreachable,
2122
warn,
@@ -180,13 +181,13 @@ function lookupCmap(ranges, unicode) {
180181

181182
function compileGlyf(code, cmds, font) {
182183
function moveTo(x, y) {
183-
cmds.push({ cmd: "moveTo", args: [x, y] });
184+
cmds.add(FontRenderOps.MOVE_TO, [x, y]);
184185
}
185186
function lineTo(x, y) {
186-
cmds.push({ cmd: "lineTo", args: [x, y] });
187+
cmds.add(FontRenderOps.LINE_TO, [x, y]);
187188
}
188189
function quadraticCurveTo(xa, ya, x, y) {
189-
cmds.push({ cmd: "quadraticCurveTo", args: [xa, ya, x, y] });
190+
cmds.add(FontRenderOps.QUADRATIC_CURVE_TO, [xa, ya, x, y]);
190191
}
191192

192193
let i = 0;
@@ -247,20 +248,22 @@ function compileGlyf(code, cmds, font) {
247248
if (subglyph) {
248249
// TODO: the transform should be applied only if there is a scale:
249250
// https://github.com/freetype/freetype/blob/edd4fedc5427cf1cf1f4b045e53ff91eb282e9d4/src/truetype/ttgload.c#L1205
250-
cmds.push(
251-
{ cmd: "save" },
252-
{
253-
cmd: "transform",
254-
args: [scaleX, scale01, scale10, scaleY, x, y],
255-
}
256-
);
251+
cmds.add(FontRenderOps.SAVE);
252+
cmds.add(FontRenderOps.TRANSFORM, [
253+
scaleX,
254+
scale01,
255+
scale10,
256+
scaleY,
257+
x,
258+
y,
259+
]);
257260

258261
if (!(flags & 0x02)) {
259262
// TODO: we must use arg1 and arg2 to make something similar to:
260263
// https://github.com/freetype/freetype/blob/edd4fedc5427cf1cf1f4b045e53ff91eb282e9d4/src/truetype/ttgload.c#L1209
261264
}
262265
compileGlyf(subglyph, cmds, font);
263-
cmds.push({ cmd: "restore" });
266+
cmds.add(FontRenderOps.RESTORE);
264267
}
265268
} while (flags & 0x20);
266269
} else {
@@ -365,13 +368,13 @@ function compileGlyf(code, cmds, font) {
365368

366369
function compileCharString(charStringCode, cmds, font, glyphId) {
367370
function moveTo(x, y) {
368-
cmds.push({ cmd: "moveTo", args: [x, y] });
371+
cmds.add(FontRenderOps.MOVE_TO, [x, y]);
369372
}
370373
function lineTo(x, y) {
371-
cmds.push({ cmd: "lineTo", args: [x, y] });
374+
cmds.add(FontRenderOps.LINE_TO, [x, y]);
372375
}
373376
function bezierCurveTo(x1, y1, x2, y2, x, y) {
374-
cmds.push({ cmd: "bezierCurveTo", args: [x1, y1, x2, y2, x, y] });
377+
cmds.add(FontRenderOps.BEZIER_CURVE_TO, [x1, y1, x2, y2, x, y]);
375378
}
376379

377380
const stack = [];
@@ -544,7 +547,8 @@ function compileCharString(charStringCode, cmds, font, glyphId) {
544547
const bchar = stack.pop();
545548
y = stack.pop();
546549
x = stack.pop();
547-
cmds.push({ cmd: "save" }, { cmd: "translate", args: [x, y] });
550+
cmds.add(FontRenderOps.SAVE);
551+
cmds.add(FontRenderOps.TRANSLATE, [x, y]);
548552
let cmap = lookupCmap(
549553
font.cmap,
550554
String.fromCharCode(font.glyphNameMap[StandardEncoding[achar]])
@@ -555,7 +559,7 @@ function compileCharString(charStringCode, cmds, font, glyphId) {
555559
font,
556560
cmap.glyphId
557561
);
558-
cmds.push({ cmd: "restore" });
562+
cmds.add(FontRenderOps.RESTORE);
559563

560564
cmap = lookupCmap(
561565
font.cmap,
@@ -741,6 +745,27 @@ function compileCharString(charStringCode, cmds, font, glyphId) {
741745

742746
const NOOP = [];
743747

748+
class Commands {
749+
cmds = [];
750+
751+
add(cmd, args) {
752+
if (args) {
753+
if (args.some(arg => typeof arg !== "number")) {
754+
warn(
755+
`Commands.add - "${cmd}" has at least one non-number arg: "${args}".`
756+
);
757+
// "Fix" the wrong args by replacing them with 0.
758+
const newArgs = args.map(arg => (typeof arg === "number" ? arg : 0));
759+
this.cmds.push(cmd, ...newArgs);
760+
} else {
761+
this.cmds.push(cmd, ...args);
762+
}
763+
} else {
764+
this.cmds.push(cmd);
765+
}
766+
}
767+
}
768+
744769
class CompiledFont {
745770
constructor(fontMatrix) {
746771
if (this.constructor === CompiledFont) {
@@ -757,8 +782,10 @@ class CompiledFont {
757782
let fn = this.compiledGlyphs[glyphId];
758783
if (!fn) {
759784
try {
760-
fn = this.compileGlyph(this.glyphs[glyphId], glyphId);
761-
this.compiledGlyphs[glyphId] = fn;
785+
fn = this.compiledGlyphs[glyphId] = this.compileGlyph(
786+
this.glyphs[glyphId],
787+
glyphId
788+
);
762789
} catch (ex) {
763790
// Avoid attempting to re-compile a corrupt glyph.
764791
this.compiledGlyphs[glyphId] = NOOP;
@@ -793,16 +820,14 @@ class CompiledFont {
793820
}
794821
}
795822

796-
const cmds = [
797-
{ cmd: "save" },
798-
{ cmd: "transform", args: fontMatrix.slice() },
799-
{ cmd: "scale", args: ["size", "-size"] },
800-
];
823+
const cmds = new Commands();
824+
cmds.add(FontRenderOps.SAVE);
825+
cmds.add(FontRenderOps.TRANSFORM, fontMatrix.slice());
826+
cmds.add(FontRenderOps.SCALE);
801827
this.compileGlyphImpl(code, cmds, glyphId);
828+
cmds.add(FontRenderOps.RESTORE);
802829

803-
cmds.push({ cmd: "restore" });
804-
805-
return cmds;
830+
return cmds.cmds;
806831
}
807832

808833
compileGlyphImpl() {

src/display/api.js

+2-4
Original file line numberDiff line numberDiff line change
@@ -169,8 +169,8 @@ const DefaultStandardFontDataFactory =
169169
* pixels, i.e. width * height. Images above this value will not be rendered.
170170
* Use -1 for no limit, which is also the default value.
171171
* @property {boolean} [isEvalSupported] - Determines if we can evaluate strings
172-
* as JavaScript. Primarily used to improve performance of font rendering, and
173-
* when parsing PDF functions. The default value is `true`.
172+
* as JavaScript. Primarily used to improve performance of PDF functions.
173+
* The default value is `true`.
174174
* @property {boolean} [isOffscreenCanvasSupported] - Determines if we can use
175175
* `OffscreenCanvas` in the worker. Primarily used to improve performance of
176176
* image conversion/rendering.
@@ -384,7 +384,6 @@ function getDocument(src) {
384384
};
385385
const transportParams = {
386386
ignoreErrors,
387-
isEvalSupported,
388387
disableFontFace,
389388
fontExtraProperties,
390389
enableXfa,
@@ -2744,7 +2743,6 @@ class WorkerTransport {
27442743
? (font, url) => globalThis.FontInspector.fontAdded(font, url)
27452744
: null;
27462745
const font = new FontFaceObject(exportedData, {
2747-
isEvalSupported: params.isEvalSupported,
27482746
disableFontFace: params.disableFontFace,
27492747
ignoreErrors: params.ignoreErrors,
27502748
inspectFont,

src/display/font_loader.js

+74-30
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
import {
1717
assert,
1818
bytesToString,
19-
FeatureTest,
19+
FontRenderOps,
2020
isNodeJS,
2121
shadow,
2222
string32,
@@ -362,19 +362,13 @@ class FontLoader {
362362
class FontFaceObject {
363363
constructor(
364364
translatedData,
365-
{
366-
isEvalSupported = true,
367-
disableFontFace = false,
368-
ignoreErrors = false,
369-
inspectFont = null,
370-
}
365+
{ disableFontFace = false, ignoreErrors = false, inspectFont = null }
371366
) {
372367
this.compiledGlyphs = Object.create(null);
373368
// importing translated data
374369
for (const i in translatedData) {
375370
this[i] = translatedData[i];
376371
}
377-
this.isEvalSupported = isEvalSupported !== false;
378372
this.disableFontFace = disableFontFace === true;
379373
this.ignoreErrors = ignoreErrors === true;
380374
this._inspectFont = inspectFont;
@@ -440,35 +434,85 @@ class FontFaceObject {
440434
throw ex;
441435
}
442436
warn(`getPathGenerator - ignoring character: "${ex}".`);
437+
}
443438

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

449-
// If we can, compile cmds into JS for MAXIMUM SPEED...
450-
if (this.isEvalSupported && FeatureTest.isEvalSupported) {
451-
const jsBuf = [];
452-
for (const current of cmds) {
453-
const args = current.args !== undefined ? current.args.join(",") : "";
454-
jsBuf.push("c.", current.cmd, "(", args, ");\n");
445+
const commands = [];
446+
for (let i = 0, ii = cmds.length; i < ii; ) {
447+
switch (cmds[i++]) {
448+
case FontRenderOps.BEZIER_CURVE_TO:
449+
{
450+
const [a, b, c, d, e, f] = cmds.slice(i, i + 6);
451+
commands.push(ctx => ctx.bezierCurveTo(a, b, c, d, e, f));
452+
i += 6;
453+
}
454+
break;
455+
case FontRenderOps.MOVE_TO:
456+
{
457+
const [a, b] = cmds.slice(i, i + 2);
458+
commands.push(ctx => ctx.moveTo(a, b));
459+
i += 2;
460+
}
461+
break;
462+
case FontRenderOps.LINE_TO:
463+
{
464+
const [a, b] = cmds.slice(i, i + 2);
465+
commands.push(ctx => ctx.lineTo(a, b));
466+
i += 2;
467+
}
468+
break;
469+
case FontRenderOps.QUADRATIC_CURVE_TO:
470+
{
471+
const [a, b, c, d] = cmds.slice(i, i + 4);
472+
commands.push(ctx => ctx.quadraticCurveTo(a, b, c, d));
473+
i += 4;
474+
}
475+
break;
476+
case FontRenderOps.RESTORE:
477+
commands.push(ctx => ctx.restore());
478+
break;
479+
case FontRenderOps.SAVE:
480+
commands.push(ctx => ctx.save());
481+
break;
482+
case FontRenderOps.SCALE:
483+
// The scale command must be at the third position, after save and
484+
// transform (for the font matrix) commands (see also
485+
// font_renderer.js).
486+
// The goal is to just scale the canvas and then run the commands loop
487+
// without the need to pass the size parameter to each command.
488+
assert(
489+
commands.length === 2,
490+
"Scale command is only valid at the third position."
491+
);
492+
break;
493+
case FontRenderOps.TRANSFORM:
494+
{
495+
const [a, b, c, d, e, f] = cmds.slice(i, i + 6);
496+
commands.push(ctx => ctx.transform(a, b, c, d, e, f));
497+
i += 6;
498+
}
499+
break;
500+
case FontRenderOps.TRANSLATE:
501+
{
502+
const [a, b] = cmds.slice(i, i + 2);
503+
commands.push(ctx => ctx.translate(a, b));
504+
i += 2;
505+
}
506+
break;
455507
}
456-
// eslint-disable-next-line no-new-func
457-
return (this.compiledGlyphs[character] = new Function(
458-
"c",
459-
"size",
460-
jsBuf.join("")
461-
));
462-
}
463-
// ... but fall back on using Function.prototype.apply() if we're
464-
// blocked from using eval() for whatever reason (like CSP policies).
465-
return (this.compiledGlyphs[character] = function (c, size) {
466-
for (const current of cmds) {
467-
if (current.cmd === "scale") {
468-
current.args = [size, -size];
469-
}
470-
// eslint-disable-next-line prefer-spread
471-
c[current.cmd].apply(c, current.args);
508+
}
509+
510+
return (this.compiledGlyphs[character] = function glyphDrawer(ctx, size) {
511+
commands[0](ctx);
512+
commands[1](ctx);
513+
ctx.scale(size, -size);
514+
for (let i = 2, ii = commands.length; i < ii; i++) {
515+
commands[i](ctx);
472516
}
473517
});
474518
}

src/shared/util.js

+13
Original file line numberDiff line numberDiff line change
@@ -1073,6 +1073,18 @@ function getUuid() {
10731073

10741074
const AnnotationPrefix = "pdfjs_internal_id_";
10751075

1076+
const FontRenderOps = {
1077+
BEZIER_CURVE_TO: 0,
1078+
MOVE_TO: 1,
1079+
LINE_TO: 2,
1080+
QUADRATIC_CURVE_TO: 3,
1081+
RESTORE: 4,
1082+
SAVE: 5,
1083+
SCALE: 6,
1084+
TRANSFORM: 7,
1085+
TRANSLATE: 8,
1086+
};
1087+
10761088
export {
10771089
AbortException,
10781090
AnnotationActionEventType,
@@ -1095,6 +1107,7 @@ export {
10951107
DocumentActionEventType,
10961108
FeatureTest,
10971109
FONT_IDENTITY_MATRIX,
1110+
FontRenderOps,
10981111
FormatError,
10991112
getModificationDate,
11001113
getUuid,

0 commit comments

Comments
 (0)