Skip to content

Commit 4bed737

Browse files
committed
[WIP] Serialize font data into an ArrayBuffer
This PR serializes font data into an ArrayBuffer that is then transfered from the worker to the main thread. It's more efficient than the current solution which clones the "export data" object which includes the font data as a Uint8Array. It prepares us to switch to a SharedArrayBuffer in the future, which would allow us to share the font data with multiple agents, which would be crucial for the upcoming "renderer" worker.
1 parent 3432c19 commit 4bed737

8 files changed

Lines changed: 920 additions & 19 deletions

File tree

src/core/evaluator.js

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ import { BaseStream } from "./base_stream.js";
7272
import { bidi } from "./bidi.js";
7373
import { ColorSpace } from "./colorspace.js";
7474
import { ColorSpaceUtils } from "./colorspace_utils.js";
75+
import { FontInfo } from "../shared/obj-bin-transform.js";
7576
import { getFontSubstitution } from "./font_substitutions.js";
7677
import { getGlyphsUnicode } from "./glyphlist.js";
7778
import { getMetrics } from "./metrics.js";
@@ -4709,12 +4710,21 @@ class TranslatedFont {
47094710
return;
47104711
}
47114712
this.#sent = true;
4712-
4713-
handler.send("commonobj", [
4714-
this.loadedName,
4715-
"Font",
4716-
this.font.exportData(),
4717-
]);
4713+
const fontData = this.font.exportData();
4714+
const transfer = [];
4715+
if (fontData.data) {
4716+
if (fontData.data.charProcOperatorList) {
4717+
fontData.charProcOperatorList = fontData.data.charProcOperatorList;
4718+
}
4719+
fontData.data = FontInfo.write(fontData.data);
4720+
transfer.push(fontData.data);
4721+
}
4722+
handler.send("commonobj", [this.loadedName, "Font", fontData], transfer);
4723+
// future path: switch to a SharedArrayBuffer
4724+
// const sab = new SharedArrayBuffer(data.byteLength);
4725+
// const view = new Uint8Array(sab);
4726+
// view.set(new Uint8Array(data));
4727+
// handler.send("commonobj", [this.loadedName, "Font", sab]);
47184728
}
47194729

47204730
fallback(handler, evaluatorOptions) {

src/core/fonts.js

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1144,19 +1144,27 @@ class Font {
11441144
}
11451145

11461146
exportData() {
1147-
const exportDataProps = this.fontExtraProperties
1148-
? [...EXPORT_DATA_PROPERTIES, ...EXPORT_DATA_EXTRA_PROPERTIES]
1149-
: EXPORT_DATA_PROPERTIES;
1150-
11511147
const data = Object.create(null);
1152-
for (const prop of exportDataProps) {
1148+
for (const prop of EXPORT_DATA_PROPERTIES) {
11531149
const value = this[prop];
11541150
// Ignore properties that haven't been explicitly set.
11551151
if (value !== undefined) {
11561152
data[prop] = value;
11571153
}
11581154
}
1159-
return data;
1155+
1156+
if (!this.fontExtraProperties) {
1157+
return { data };
1158+
}
1159+
1160+
const extra = Object.create(null);
1161+
for (const prop of EXPORT_DATA_EXTRA_PROPERTIES) {
1162+
const value = this[prop];
1163+
if (value !== undefined) {
1164+
extra[prop] = value;
1165+
}
1166+
}
1167+
return { data, extra };
11601168
}
11611169

11621170
fallbackToSystemFont(properties) {

src/display/api.js

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ import { DOMCMapReaderFactory } from "display-cmap_reader_factory";
6767
import { DOMFilterFactory } from "./filter_factory.js";
6868
import { DOMStandardFontDataFactory } from "display-standard_fontdata_factory";
6969
import { DOMWasmFactory } from "display-wasm_factory";
70+
import { FontInfo } from "../shared/obj-bin-transform.js";
7071
import { GlobalWorkerOptions } from "./worker_options.js";
7172
import { Metadata } from "./metadata.js";
7273
import { OptionalContentConfig } from "./optional_content_config.js";
@@ -2760,11 +2761,17 @@ class WorkerTransport {
27602761
break;
27612762
}
27622763

2764+
const fontData = new FontInfo(exportedData);
27632765
const inspectFont =
27642766
this._params.pdfBug && globalThis.FontInspector?.enabled
27652767
? (font, url) => globalThis.FontInspector.fontAdded(font, url)
27662768
: null;
2767-
const font = new FontFaceObject(exportedData, inspectFont);
2769+
const font = new FontFaceObject(
2770+
fontData,
2771+
inspectFont,
2772+
exportedData.extra,
2773+
exportedData.charProcOperatorList
2774+
);
27682775

27692776
this.fontLoader
27702777
.bind(font)
@@ -2776,7 +2783,7 @@ class WorkerTransport {
27762783
// rather than waiting for a `PDFDocumentProxy.cleanup` call.
27772784
// Since `font.data` could be very large, e.g. in some cases
27782785
// multiple megabytes, this will help reduce memory usage.
2779-
font.data = null;
2786+
font.clearData();
27802787
}
27812788
this.commonObjs.resolve(id, font);
27822789
});

src/display/font_loader.js

Lines changed: 106 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -355,12 +355,11 @@ class FontLoader {
355355
}
356356

357357
class FontFaceObject {
358-
constructor(translatedData, inspectFont = null) {
358+
#fontData;
359+
360+
constructor(translatedData, inspectFont = null, extra, charProcOperatorList) {
359361
this.compiledGlyphs = Object.create(null);
360-
// importing translated data
361-
for (const i in translatedData) {
362-
this[i] = translatedData[i];
363-
}
362+
this.#fontData = translatedData;
364363
if (typeof PDFJSDev === "undefined" || PDFJSDev.test("TESTING")) {
365364
if (typeof this.disableFontFace !== "boolean") {
366365
unreachable("disableFontFace must be available.");
@@ -370,6 +369,12 @@ class FontFaceObject {
370369
}
371370
}
372371
this._inspectFont = inspectFont;
372+
if (extra) {
373+
Object.assign(this, extra);
374+
}
375+
if (charProcOperatorList) {
376+
this.charProcOperatorList = charProcOperatorList;
377+
}
373378
}
374379

375380
createNativeFontFace() {
@@ -438,6 +443,102 @@ class FontFaceObject {
438443
}
439444
return (this.compiledGlyphs[character] = path);
440445
}
446+
447+
get black() {
448+
return this.#fontData.black;
449+
}
450+
451+
get bold() {
452+
return this.#fontData.bold;
453+
}
454+
455+
get disableFontFace() {
456+
return this.#fontData.disableFontFace ?? false;
457+
}
458+
459+
get fontExtraProperties() {
460+
return this.#fontData.fontExtraProperties ?? false;
461+
}
462+
463+
get isInvalidPDFjsFont() {
464+
return this.#fontData.isInvalidPDFjsFont;
465+
}
466+
467+
get isType3Font() {
468+
return this.#fontData.isType3Font;
469+
}
470+
471+
get italic() {
472+
return this.#fontData.italic;
473+
}
474+
475+
get missingFile() {
476+
return this.#fontData.missingFile;
477+
}
478+
479+
get remeasure() {
480+
return this.#fontData.remeasure;
481+
}
482+
483+
get vertical() {
484+
return this.#fontData.vertical;
485+
}
486+
487+
get ascent() {
488+
return this.#fontData.ascent;
489+
}
490+
491+
get defaultWidth() {
492+
return this.#fontData.defaultWidth;
493+
}
494+
495+
get descent() {
496+
return this.#fontData.descent;
497+
}
498+
499+
get bbox() {
500+
return this.#fontData.bbox;
501+
}
502+
503+
get fontMatrix() {
504+
return this.#fontData.fontMatrix;
505+
}
506+
507+
get fallbackName() {
508+
return this.#fontData.fallbackName;
509+
}
510+
511+
get loadedName() {
512+
return this.#fontData.loadedName;
513+
}
514+
515+
get mimetype() {
516+
return this.#fontData.mimetype;
517+
}
518+
519+
get name() {
520+
return this.#fontData.name;
521+
}
522+
523+
get data() {
524+
return this.#fontData.data;
525+
}
526+
527+
clearData() {
528+
this.#fontData.clearData();
529+
}
530+
531+
get cssFontInfo() {
532+
return this.#fontData.cssFontInfo;
533+
}
534+
535+
get systemFontInfo() {
536+
return this.#fontData.systemFontInfo;
537+
}
538+
539+
get defaultVMetrics() {
540+
return this.#fontData.defaultVMetrics;
541+
}
441542
}
442543

443544
export { FontFaceObject, FontLoader };

0 commit comments

Comments
 (0)