1
0
Fork 0
mirror of https://github.com/mozilla/pdf.js.git synced 2025-04-22 16:18:08 +02:00

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.
This commit is contained in:
Calixte Denizet 2024-04-27 17:13:34 +02:00
parent 90d4b9c2c0
commit 551e63901c
5 changed files with 149 additions and 60 deletions

View file

@ -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,
@ -4403,7 +4412,7 @@ class PartialEvaluator {
loadedName: baseDict.loadedName,
composite,
fixedPitch: false,
fontMatrix: dict.getArray("FontMatrix") || FONT_IDENTITY_MATRIX,
fontMatrix,
firstChar,
lastChar,
toUnicode,

View file

@ -16,6 +16,7 @@
import {
bytesToString,
FONT_IDENTITY_MATRIX,
FontRenderOps,
FormatError,
unreachable,
warn,
@ -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;
@ -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 {
@ -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 = [];
@ -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]])
@ -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,
@ -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) {
@ -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;
@ -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() {

View file

@ -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.
@ -384,7 +384,6 @@ function getDocument(src) {
};
const transportParams = {
ignoreErrors,
isEvalSupported,
disableFontFace,
fontExtraProperties,
enableXfa,
@ -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,

View file

@ -16,7 +16,7 @@
import {
assert,
bytesToString,
FeatureTest,
FontRenderOps,
isNodeJS,
shadow,
string32,
@ -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;
@ -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);
}
});
}

View file

@ -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,
@ -1095,6 +1107,7 @@ export {
DocumentActionEventType,
FeatureTest,
FONT_IDENTITY_MATRIX,
FontRenderOps,
FormatError,
getModificationDate,
getUuid,