Merge pull request #18015 from calixteman/rm_eval_font_loader

Simplify the way to pass the glyph drawing instructions from the worker to the main thread
This commit is contained in:
calixteman 2024-04-28 23:27:47 +02:00 committed by GitHub
commit 85e64b5c16
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
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 = { const properties = {
type, type,
name: fontName.name, name: fontName.name,
@ -4403,7 +4412,7 @@ class PartialEvaluator {
loadedName: baseDict.loadedName, loadedName: baseDict.loadedName,
composite, composite,
fixedPitch: false, fixedPitch: false,
fontMatrix: dict.getArray("FontMatrix") || FONT_IDENTITY_MATRIX, fontMatrix,
firstChar, firstChar,
lastChar, lastChar,
toUnicode, toUnicode,

View file

@ -16,6 +16,7 @@
import { import {
bytesToString, bytesToString,
FONT_IDENTITY_MATRIX, FONT_IDENTITY_MATRIX,
FontRenderOps,
FormatError, FormatError,
unreachable, unreachable,
warn, warn,
@ -180,13 +181,13 @@ function lookupCmap(ranges, unicode) {
function compileGlyf(code, cmds, font) { function compileGlyf(code, cmds, font) {
function moveTo(x, y) { function moveTo(x, y) {
cmds.push({ cmd: "moveTo", args: [x, y] }); cmds.add(FontRenderOps.MOVE_TO, [x, y]);
} }
function lineTo(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) { 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; let i = 0;
@ -247,20 +248,22 @@ function compileGlyf(code, cmds, font) {
if (subglyph) { if (subglyph) {
// TODO: the transform should be applied only if there is a scale: // TODO: the transform should be applied only if there is a scale:
// https://github.com/freetype/freetype/blob/edd4fedc5427cf1cf1f4b045e53ff91eb282e9d4/src/truetype/ttgload.c#L1205 // https://github.com/freetype/freetype/blob/edd4fedc5427cf1cf1f4b045e53ff91eb282e9d4/src/truetype/ttgload.c#L1205
cmds.push( cmds.add(FontRenderOps.SAVE);
{ cmd: "save" }, cmds.add(FontRenderOps.TRANSFORM, [
{ scaleX,
cmd: "transform", scale01,
args: [scaleX, scale01, scale10, scaleY, x, y], scale10,
} scaleY,
); x,
y,
]);
if (!(flags & 0x02)) { if (!(flags & 0x02)) {
// TODO: we must use arg1 and arg2 to make something similar to: // TODO: we must use arg1 and arg2 to make something similar to:
// https://github.com/freetype/freetype/blob/edd4fedc5427cf1cf1f4b045e53ff91eb282e9d4/src/truetype/ttgload.c#L1209 // https://github.com/freetype/freetype/blob/edd4fedc5427cf1cf1f4b045e53ff91eb282e9d4/src/truetype/ttgload.c#L1209
} }
compileGlyf(subglyph, cmds, font); compileGlyf(subglyph, cmds, font);
cmds.push({ cmd: "restore" }); cmds.add(FontRenderOps.RESTORE);
} }
} while (flags & 0x20); } while (flags & 0x20);
} else { } else {
@ -365,13 +368,13 @@ function compileGlyf(code, cmds, font) {
function compileCharString(charStringCode, cmds, font, glyphId) { function compileCharString(charStringCode, cmds, font, glyphId) {
function moveTo(x, y) { function moveTo(x, y) {
cmds.push({ cmd: "moveTo", args: [x, y] }); cmds.add(FontRenderOps.MOVE_TO, [x, y]);
} }
function lineTo(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) { 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 = []; const stack = [];
@ -544,7 +547,8 @@ function compileCharString(charStringCode, cmds, font, glyphId) {
const bchar = stack.pop(); const bchar = stack.pop();
y = stack.pop(); y = stack.pop();
x = 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( let cmap = lookupCmap(
font.cmap, font.cmap,
String.fromCharCode(font.glyphNameMap[StandardEncoding[achar]]) String.fromCharCode(font.glyphNameMap[StandardEncoding[achar]])
@ -555,7 +559,7 @@ function compileCharString(charStringCode, cmds, font, glyphId) {
font, font,
cmap.glyphId cmap.glyphId
); );
cmds.push({ cmd: "restore" }); cmds.add(FontRenderOps.RESTORE);
cmap = lookupCmap( cmap = lookupCmap(
font.cmap, font.cmap,
@ -741,6 +745,27 @@ function compileCharString(charStringCode, cmds, font, glyphId) {
const NOOP = []; 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 { class CompiledFont {
constructor(fontMatrix) { constructor(fontMatrix) {
if (this.constructor === CompiledFont) { if (this.constructor === CompiledFont) {
@ -757,8 +782,10 @@ class CompiledFont {
let fn = this.compiledGlyphs[glyphId]; let fn = this.compiledGlyphs[glyphId];
if (!fn) { if (!fn) {
try { try {
fn = this.compileGlyph(this.glyphs[glyphId], glyphId); fn = this.compiledGlyphs[glyphId] = this.compileGlyph(
this.compiledGlyphs[glyphId] = fn; this.glyphs[glyphId],
glyphId
);
} catch (ex) { } catch (ex) {
// Avoid attempting to re-compile a corrupt glyph. // Avoid attempting to re-compile a corrupt glyph.
this.compiledGlyphs[glyphId] = NOOP; this.compiledGlyphs[glyphId] = NOOP;
@ -793,16 +820,14 @@ class CompiledFont {
} }
} }
const cmds = [ const cmds = new Commands();
{ cmd: "save" }, cmds.add(FontRenderOps.SAVE);
{ cmd: "transform", args: fontMatrix.slice() }, cmds.add(FontRenderOps.TRANSFORM, fontMatrix.slice());
{ cmd: "scale", args: ["size", "-size"] }, cmds.add(FontRenderOps.SCALE);
];
this.compileGlyphImpl(code, cmds, glyphId); this.compileGlyphImpl(code, cmds, glyphId);
cmds.add(FontRenderOps.RESTORE);
cmds.push({ cmd: "restore" }); return cmds.cmds;
return cmds;
} }
compileGlyphImpl() { compileGlyphImpl() {

View file

@ -169,8 +169,8 @@ const DefaultStandardFontDataFactory =
* pixels, i.e. width * height. Images above this value will not be rendered. * pixels, i.e. width * height. Images above this value will not be rendered.
* Use -1 for no limit, which is also the default value. * Use -1 for no limit, which is also the default value.
* @property {boolean} [isEvalSupported] - Determines if we can evaluate strings * @property {boolean} [isEvalSupported] - Determines if we can evaluate strings
* as JavaScript. Primarily used to improve performance of font rendering, and * as JavaScript. Primarily used to improve performance of PDF functions.
* when parsing PDF functions. The default value is `true`. * The default value is `true`.
* @property {boolean} [isOffscreenCanvasSupported] - Determines if we can use * @property {boolean} [isOffscreenCanvasSupported] - Determines if we can use
* `OffscreenCanvas` in the worker. Primarily used to improve performance of * `OffscreenCanvas` in the worker. Primarily used to improve performance of
* image conversion/rendering. * image conversion/rendering.
@ -384,7 +384,6 @@ function getDocument(src) {
}; };
const transportParams = { const transportParams = {
ignoreErrors, ignoreErrors,
isEvalSupported,
disableFontFace, disableFontFace,
fontExtraProperties, fontExtraProperties,
enableXfa, enableXfa,
@ -2744,7 +2743,6 @@ class WorkerTransport {
? (font, url) => globalThis.FontInspector.fontAdded(font, url) ? (font, url) => globalThis.FontInspector.fontAdded(font, url)
: null; : null;
const font = new FontFaceObject(exportedData, { const font = new FontFaceObject(exportedData, {
isEvalSupported: params.isEvalSupported,
disableFontFace: params.disableFontFace, disableFontFace: params.disableFontFace,
ignoreErrors: params.ignoreErrors, ignoreErrors: params.ignoreErrors,
inspectFont, inspectFont,

View file

@ -16,7 +16,7 @@
import { import {
assert, assert,
bytesToString, bytesToString,
FeatureTest, FontRenderOps,
isNodeJS, isNodeJS,
shadow, shadow,
string32, string32,
@ -362,19 +362,13 @@ class FontLoader {
class FontFaceObject { class FontFaceObject {
constructor( constructor(
translatedData, translatedData,
{ { disableFontFace = false, ignoreErrors = false, inspectFont = null }
isEvalSupported = true,
disableFontFace = false,
ignoreErrors = false,
inspectFont = null,
}
) { ) {
this.compiledGlyphs = Object.create(null); this.compiledGlyphs = Object.create(null);
// importing translated data // importing translated data
for (const i in translatedData) { for (const i in translatedData) {
this[i] = translatedData[i]; this[i] = translatedData[i];
} }
this.isEvalSupported = isEvalSupported !== false;
this.disableFontFace = disableFontFace === true; this.disableFontFace = disableFontFace === true;
this.ignoreErrors = ignoreErrors === true; this.ignoreErrors = ignoreErrors === true;
this._inspectFont = inspectFont; this._inspectFont = inspectFont;
@ -440,35 +434,85 @@ class FontFaceObject {
throw ex; throw ex;
} }
warn(`getPathGenerator - ignoring character: "${ex}".`); warn(`getPathGenerator - ignoring character: "${ex}".`);
}
if (!Array.isArray(cmds) || cmds.length === 0) {
return (this.compiledGlyphs[character] = function (c, size) { return (this.compiledGlyphs[character] = function (c, size) {
// No-op function, to allow rendering to continue. // No-op function, to allow rendering to continue.
}); });
} }
// If we can, compile cmds into JS for MAXIMUM SPEED... const commands = [];
if (this.isEvalSupported && FeatureTest.isEvalSupported) { for (let i = 0, ii = cmds.length; i < ii; ) {
const jsBuf = []; switch (cmds[i++]) {
for (const current of cmds) { case FontRenderOps.BEZIER_CURVE_TO:
const args = current.args !== undefined ? current.args.join(",") : ""; {
jsBuf.push("c.", current.cmd, "(", args, ");\n"); 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 glyphDrawer(ctx, size) {
return (this.compiledGlyphs[character] = function (c, size) { commands[0](ctx);
for (const current of cmds) { commands[1](ctx);
if (current.cmd === "scale") { ctx.scale(size, -size);
current.args = [size, -size]; for (let i = 2, ii = commands.length; i < ii; i++) {
} commands[i](ctx);
// eslint-disable-next-line prefer-spread
c[current.cmd].apply(c, current.args);
} }
}); });
} }

View file

@ -1073,6 +1073,18 @@ function getUuid() {
const AnnotationPrefix = "pdfjs_internal_id_"; 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 { export {
AbortException, AbortException,
AnnotationActionEventType, AnnotationActionEventType,
@ -1095,6 +1107,7 @@ export {
DocumentActionEventType, DocumentActionEventType,
FeatureTest, FeatureTest,
FONT_IDENTITY_MATRIX, FONT_IDENTITY_MATRIX,
FontRenderOps,
FormatError, FormatError,
getModificationDate, getModificationDate,
getUuid, getUuid,