From ea5ecb012e4816ef14ca164e0960166a92eae5db Mon Sep 17 00:00:00 2001 From: Jonas Jenwald Date: Thu, 20 Aug 2026 14:19:23 +0200 Subject: [PATCH] Cache the `TextDecoder`/`TextEncoder` instances used when compiling/decompiling font-data This avoids creating "duplicate" `TextDecoder`/`TextEncoder` instances. For example, when rendering all 1310 pages of the `pdf.pdf` document (from the test-suite) this patch reduces the number of `TextDecoder`/`TextEncoder` instances from `73` *each* to just a single one each. *Note:* Compared to PR 21801 the reduction is obviously much smaller, however it still seems worthwhile to avoid unnecessary object creation. --- src/core/obj_bin_transform_core.js | 7 +++--- src/display/obj_bin_transform_display.js | 27 +++++++++--------------- src/shared/obj_bin_transform_utils.js | 14 +++++++++++- test/unit/obj_bin_transform_spec.js | 7 +++--- 4 files changed, 31 insertions(+), 24 deletions(-) diff --git a/src/core/obj_bin_transform_core.js b/src/core/obj_bin_transform_core.js index 8704bc062b2ca..25dbbf63bce56 100644 --- a/src/core/obj_bin_transform_core.js +++ b/src/core/obj_bin_transform_core.js @@ -17,12 +17,13 @@ import { assert, FeatureTest } from "../shared/util.js"; import { CSS_FONT_INFO, FONT_INFO, + InfoUtils, PATTERN_INFO, SYSTEM_FONT_INFO, } from "../shared/obj_bin_transform_utils.js"; function compileCssFontInfo(info) { - const encoder = new TextEncoder(); + const { encoder } = InfoUtils; const encodedStrings = {}; let stringsLength = 0; for (const prop of CSS_FONT_INFO.strings) { @@ -48,7 +49,7 @@ function compileCssFontInfo(info) { } function compileSystemFontInfo(info) { - const encoder = new TextEncoder(); + const { encoder } = InfoUtils; const encodedStrings = {}; let stringsLength = 0; for (const prop of SYSTEM_FONT_INFO.strings) { @@ -106,7 +107,7 @@ function compileFontInfo(font) { ? compileCssFontInfo(font.cssFontInfo) : null; - const encoder = new TextEncoder(); + const { encoder } = InfoUtils; const encodedStrings = {}; let stringsLength = 0; for (const prop of FONT_INFO.strings) { diff --git a/src/display/obj_bin_transform_display.js b/src/display/obj_bin_transform_display.js index 9e21c7bb02a72..a584198f2eee0 100644 --- a/src/display/obj_bin_transform_display.js +++ b/src/display/obj_bin_transform_display.js @@ -17,6 +17,7 @@ import { assert, BBOX_INIT, FeatureTest, Util } from "../shared/util.js"; import { CSS_FONT_INFO, FONT_INFO, + InfoUtils, PATTERN_INFO, SYSTEM_FONT_INFO, } from "../shared/obj_bin_transform_utils.js"; @@ -24,8 +25,6 @@ import { class CssFontInfo { #buffer; - #decoder = new TextDecoder(); - #view; constructor(buffer) { @@ -35,14 +34,13 @@ class CssFontInfo { #readString(index) { assert(index < CSS_FONT_INFO.strings.length, "Invalid string index"); + const { decoder } = InfoUtils; let offset = 0; for (let i = 0; i < index; i++) { offset += this.#view.getUint32(offset) + 4; } const length = this.#view.getUint32(offset); - return this.#decoder.decode( - new Uint8Array(this.#buffer, offset + 4, length) - ); + return decoder.decode(new Uint8Array(this.#buffer, offset + 4, length)); } get fontFamily() { @@ -61,8 +59,6 @@ class CssFontInfo { class SystemFontInfo { #buffer; - #decoder = new TextDecoder(); - #view; constructor(buffer) { @@ -76,14 +72,13 @@ class SystemFontInfo { #readString(index) { assert(index < SYSTEM_FONT_INFO.strings.length, "Invalid string index"); + const { decoder } = InfoUtils; let offset = 5; for (let i = 0; i < index; i++) { offset += this.#view.getUint32(offset) + 4; } const length = this.#view.getUint32(offset); - return this.#decoder.decode( - new Uint8Array(this.#buffer, offset + 4, length) - ); + return decoder.decode(new Uint8Array(this.#buffer, offset + 4, length)); } get css() { @@ -103,15 +98,16 @@ class SystemFontInfo { } get style() { + const { decoder } = InfoUtils; let offset = 1; offset += 4 + this.#view.getUint32(offset); const styleLength = this.#view.getUint32(offset); - const style = this.#decoder.decode( + const style = decoder.decode( new Uint8Array(this.#buffer, offset + 4, styleLength) ); offset += 4 + styleLength; const weightLength = this.#view.getUint32(offset); - const weight = this.#decoder.decode( + const weight = decoder.decode( new Uint8Array(this.#buffer, offset + 4, weightLength) ); return { style, weight }; @@ -121,8 +117,6 @@ class SystemFontInfo { class FontInfo { #buffer; - #decoder = new TextDecoder(); - #view; constructor({ buffer, extra }) { @@ -242,14 +236,13 @@ class FontInfo { #readString(index) { assert(index < FONT_INFO.strings.length, "Invalid string index"); + const { decoder } = InfoUtils; let offset = FONT_INFO.OFFSET_STRINGS + 4; for (let i = 0; i < index; i++) { offset += this.#view.getUint32(offset) + 4; } const length = this.#view.getUint32(offset); - return this.#decoder.decode( - new Uint8Array(this.#buffer, offset + 4, length) - ); + return decoder.decode(new Uint8Array(this.#buffer, offset + 4, length)); } get fallbackName() { diff --git a/src/shared/obj_bin_transform_utils.js b/src/shared/obj_bin_transform_utils.js index 9682485efbcb0..1dd39a9d26416 100644 --- a/src/shared/obj_bin_transform_utils.js +++ b/src/shared/obj_bin_transform_utils.js @@ -13,6 +13,8 @@ * limitations under the License. */ +import { shadow } from "./util.js"; + class CSS_FONT_INFO { static strings = ["fontFamily", "fontWeight", "italicAngle"]; } @@ -68,4 +70,14 @@ class PATTERN_INFO { static N_FIGURES = 16; // number of figures } -export { CSS_FONT_INFO, FONT_INFO, PATTERN_INFO, SYSTEM_FONT_INFO }; +class InfoUtils { + static get decoder() { + return shadow(this, "decoder", new TextDecoder()); + } + + static get encoder() { + return shadow(this, "encoder", new TextEncoder()); + } +} + +export { CSS_FONT_INFO, FONT_INFO, InfoUtils, PATTERN_INFO, SYSTEM_FONT_INFO }; diff --git a/test/unit/obj_bin_transform_spec.js b/test/unit/obj_bin_transform_spec.js index a96c08c8e0c31..7094157d6db32 100644 --- a/test/unit/obj_bin_transform_spec.js +++ b/test/unit/obj_bin_transform_spec.js @@ -28,6 +28,7 @@ import { SystemFontInfo, } from "../../src/display/obj_bin_transform_display.js"; import { FeatureTest } from "../../src/shared/util.js"; +import { InfoUtils } from "../../src/shared/obj_bin_transform_utils.js"; describe("obj_bin_transform", function () { describe("Font data", function () { @@ -80,7 +81,7 @@ describe("obj_bin_transform", function () { describe("font data serialization and deserialization", function () { describe("CssFontInfo", function () { it("must roundtrip correctly for CssFontInfo", function () { - const encoder = new TextEncoder(); + const { encoder } = InfoUtils; let sizeEstimate = 0; for (const string of ["Sample Family", "not a number", "angle"]) { sizeEstimate += 4 + encoder.encode(string).length; @@ -97,7 +98,7 @@ describe("obj_bin_transform", function () { describe("SystemFontInfo", function () { it("must roundtrip correctly for SystemFontInfo", function () { - const encoder = new TextEncoder(); + const { encoder } = InfoUtils; let sizeEstimate = 1 + 4; for (const string of [ "some string", @@ -127,7 +128,7 @@ describe("obj_bin_transform", function () { describe("FontInfo", function () { it("must roundtrip correctly for FontInfo", function () { let sizeEstimate = 92; // fixed offset until the strings - const encoder = new TextEncoder(); + const { encoder } = InfoUtils; sizeEstimate += 4 + 4 * (4 + encoder.encode("string").length); sizeEstimate += 4 + 4; // cssFontInfo and systemFontInfo sizeEstimate += 4 + fontInfo.data.length;