Shadow the getters in FontInfo, CssFontInfo, and SystemFontInfo (PR 20197 follow-up)

Prior to PR 20197 each font-instance had just a single copy of all its relevant font-properties on the main-thread, however that's unfortunately no longer the case.
Given how the compilation was implemented, every single time that a font-property is accessed on the main-thread it'll now be re-parsed. Not only does this seem inefficient, especially for properties needed e.g. during text-rendering, but it'll lead to (potentially) a lot of duplicated object creation.

Consider what currently happens when rendering all pages of the following PDFs:
 - `tracemonkey.pdf` contains `24` separate fonts, but the `fontMatrix`-property is re-parsed a whopping `1009` times (once per `showText` operator).

 - `standard_fonts.pdf` contains `14` separate fonts, however we create no less than `186` separate `StandardFontInfo` instances.

 - `xfa_bug1716816.pdf` contains `4` separate fonts, however we create no less than `45` separate `CssFontInfo` instances.

This is obviously not limited to just the font-properties listed above, but those are mere examples to illustrate the problem.
By shadowing the font-property getters on the main-thread, obviously with the exception of `FontInfo.prototype.data`, we only need to parse each font-property *once* per font-instance.

*Note:* Unfortunately this *increases* the size of the `gulp mozcentral` bundle by `792` bytes, but that cannot really be helped since this seems like the correct thing to do regardless.
This commit is contained in:
Jonas Jenwald
2026-10-02 15:25:24 +02:00
parent a00ad131fd
commit db561fecc6
2 changed files with 69 additions and 51 deletions
+7 -5
View File
@@ -423,12 +423,13 @@ class FontFaceObject {
}
createNativeFontFace() {
if (!this.data || this.disableFontFace) {
const { data } = this; // Get `data` just once, since it's not shadowed.
if (!data || this.disableFontFace) {
return null;
}
let nativeFontFace;
if (!this.cssFontInfo) {
nativeFontFace = new FontFace(this.loadedName, this.data, {});
nativeFontFace = new FontFace(this.loadedName, data, {});
} else {
const css = {
weight: this.cssFontInfo.fontWeight,
@@ -438,7 +439,7 @@ class FontFaceObject {
}
nativeFontFace = new FontFace(
serializeFontFamily(this.cssFontInfo.fontFamily),
this.data,
data,
css
);
}
@@ -452,11 +453,12 @@ class FontFaceObject {
throw new Error("Not implemented: createFontFaceRule");
}
if (!this.data || this.disableFontFace) {
const { data } = this; // Get `data` just once, since it's not shadowed.
if (!data || this.disableFontFace) {
return null;
}
// Add the @font-face rule to the document.
const url = `url(data:${this.mimetype};base64,${this.data.toBase64()});`;
const url = `url(data:${this.mimetype};base64,${data.toBase64()});`;
let rule;
if (!this.cssFontInfo) {
rule = `@font-face {font-family:"${this.loadedName}";src:${url}}`;
+62 -46
View File
@@ -13,7 +13,13 @@
* limitations under the License.
*/
import { assert, BBOX_INIT, FeatureTest, Util } from "../shared/util.js";
import {
assert,
BBOX_INIT,
FeatureTest,
shadow,
Util,
} from "../shared/util.js";
import {
CSS_FONT_INFO,
FONT_INFO,
@@ -47,15 +53,15 @@ class CssFontInfo {
}
get fontFamily() {
return this.#readString(0);
return shadow(this, "fontFamily", this.#readString(0));
}
get fontWeight() {
return this.#readString(1);
return shadow(this, "fontWeight", this.#readString(1));
}
get italicAngle() {
return this.#readString(2);
return shadow(this, "italicAngle", this.#readString(2));
}
}
@@ -75,19 +81,19 @@ class SystemFontInfo {
}
get css() {
return this.#readString(0);
return shadow(this, "css", this.#readString(0));
}
get loadedName() {
return this.#readString(1);
return shadow(this, "loadedName", this.#readString(1));
}
get baseFontName() {
return this.#readString(2);
return shadow(this, "baseFontName", this.#readString(2));
}
get src() {
return this.#readString(3);
return shadow(this, "src", this.#readString(3));
}
get style() {
@@ -95,7 +101,7 @@ class SystemFontInfo {
offset += 4 + this.#view.getUint32(offset);
const style = readString(this.#buffer, this.#view, /* index = */ 0, offset),
weight = readString(this.#buffer, this.#view, /* index = */ 1, offset);
return { style, weight };
return shadow(this, "style", { style, weight });
}
}
@@ -121,43 +127,43 @@ class FontInfo {
}
get black() {
return this.#readBoolean(0);
return shadow(this, "black", this.#readBoolean(0));
}
get bold() {
return this.#readBoolean(1);
return shadow(this, "bold", this.#readBoolean(1));
}
get disableFontFace() {
return this.#readBoolean(2);
return shadow(this, "disableFontFace", this.#readBoolean(2));
}
get fontExtraProperties() {
return this.#readBoolean(3);
return shadow(this, "fontExtraProperties", this.#readBoolean(3));
}
get isInvalidPDFjsFont() {
return this.#readBoolean(4);
return shadow(this, "isInvalidPDFjsFont", this.#readBoolean(4));
}
get isType3Font() {
return this.#readBoolean(5);
return shadow(this, "isType3Font", this.#readBoolean(5));
}
get italic() {
return this.#readBoolean(6);
return shadow(this, "italic", this.#readBoolean(6));
}
get missingFile() {
return this.#readBoolean(7);
return shadow(this, "missingFile", this.#readBoolean(7));
}
get remeasure() {
return this.#readBoolean(8);
return shadow(this, "remeasure", this.#readBoolean(8));
}
get vertical() {
return this.#readBoolean(9);
return shadow(this, "vertical", this.#readBoolean(9));
}
#readNumber(index) {
@@ -166,20 +172,19 @@ class FontInfo {
}
get ascent() {
return this.#readNumber(0);
return shadow(this, "ascent", this.#readNumber(0));
}
get descent() {
return this.#readNumber(1);
return shadow(this, "descent", this.#readNumber(1));
}
#readArray(offset, arrLen, lookupName, increment) {
const len = this.#view.getUint8(offset);
const len = this.#view.getUint8(offset++);
if (len === 0) {
return undefined;
}
assert(len === arrLen, "Invalid array length.");
offset += 1;
const arr = new Array(len);
for (let i = 0; i < len; i++) {
arr[i] = this.#view[lookupName](offset, true);
@@ -189,20 +194,28 @@ class FontInfo {
}
get bbox() {
return this.#readArray(
/* offset = */ FONT_INFO.OFFSET_BBOX,
/* arrLen = */ 4,
/* lookup = */ "getInt16",
/* increment = */ 2
return shadow(
this,
"bbox",
this.#readArray(
/* offset = */ FONT_INFO.OFFSET_BBOX,
/* arrLen = */ 4,
/* lookup = */ "getInt16",
/* increment = */ 2
)
);
}
get fontMatrix() {
return this.#readArray(
/* offset = */ FONT_INFO.OFFSET_FONT_MATRIX,
/* arrLen = */ 6,
/* lookup = */ "getFloat64",
/* increment = */ 8
return shadow(
this,
"fontMatrix",
this.#readArray(
/* offset = */ FONT_INFO.OFFSET_FONT_MATRIX,
/* arrLen = */ 6,
/* lookup = */ "getFloat64",
/* increment = */ 8
)
);
}
@@ -217,15 +230,15 @@ class FontInfo {
}
get fallbackName() {
return this.#readString(0);
return shadow(this, "fallbackName", this.#readString(0));
}
get loadedName() {
return this.#readString(1);
return shadow(this, "loadedName", this.#readString(1));
}
get name() {
return this.#readString(2);
return shadow(this, "name", this.#readString(2));
}
#getBufferOffset(index) {
@@ -239,16 +252,17 @@ class FontInfo {
return { offset, length };
}
// NOTE: This cannot be shadowed, since it may be cleared.
get data() {
const { offset, length } = this.#getBufferOffset(2);
return length === 0
return !length
? undefined
: new Uint8Array(this.#buffer, offset + 4, length);
}
clearData() {
const { offset, length } = this.#getBufferOffset(2);
if (length === 0) {
if (!length) {
return; // The data is either not present, or it was previously cleared.
}
this.#view.setUint32(offset, 0); // Zero the data-length.
@@ -260,20 +274,22 @@ class FontInfo {
get cssFontInfo() {
const { offset, length } = this.#getBufferOffset(1);
if (length === 0) {
return null;
let info = null;
if (length) {
const data = new Uint8Array(this.#buffer, offset + 4, length).slice();
info = new CssFontInfo(data.buffer);
}
const data = new Uint8Array(this.#buffer, offset + 4, length).slice();
return new CssFontInfo(data.buffer);
return shadow(this, "cssFontInfo", info);
}
get systemFontInfo() {
const { offset, length } = this.#getBufferOffset(0);
if (length === 0) {
return null;
let info = null;
if (length) {
const data = new Uint8Array(this.#buffer, offset + 4, length).slice();
info = new SystemFontInfo(data.buffer);
}
const data = new Uint8Array(this.#buffer, offset + 4, length).slice();
return new SystemFontInfo(data.buffer);
return shadow(this, "systemFontInfo", info);
}
}