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:
parent
90d4b9c2c0
commit
551e63901c
@ -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,
|
||||||
|
|||||||
@ -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() {
|
||||||
|
|||||||
@ -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,
|
||||||
|
|||||||
@ -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);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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,
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user