From e88a5652de17d53819117086b097b588a9f84e67 Mon Sep 17 00:00:00 2001 From: Jonas Jenwald Date: Thu, 12 Mar 2026 16:58:32 +0100 Subject: [PATCH] Fix the `FontInfo.prototype.clearData` method to actually remove the data as intended (PR 20197 follow-up) The purpose of PR 11844 was to reduce memory usage once fonts have been attached to the DOM, since the font-data can be quite large in many cases. Unfortunately the new `clearData` method added in PR 20197 doesn't actually remove *anything*, it just replaces the font-data with zeros which doesn't help when the underlying `ArrayBuffer` itself isn't modified. The method does include a commented-out `resize` call[1], but uncommenting that just breaks rendering completely. To address this regression, without having to make large or possibly complex changes, this patch simply changes the `clearData` method to replace the internal buffer/view with its contents *before* the font-data. While this does lead to a data copy, the size of this data is usually orders of magnitude smaller than the font-data that we're removing. --- [1] Slightly off-topic, but I don't think that patches should include commented-out code since there's a very real risk that those things never get found/fixed. At the very least such cases should be clearly marked with `// TODO: ...` comments, and should possibly also have an issue filed about fixing the TODO. --- src/display/api.js | 2 +- src/shared/obj-bin-transform.js | 55 ++++++++++++++++----------------- 2 files changed, 28 insertions(+), 29 deletions(-) diff --git a/src/display/api.js b/src/display/api.js index 2cea1c1e2..9cd87f489 100644 --- a/src/display/api.js +++ b/src/display/api.js @@ -2903,7 +2903,7 @@ class WorkerTransport { .bind(font) .catch(() => messageHandler.sendWithPromise("FontFallback", { id })) .finally(() => { - if (!font.fontExtraProperties && font.data) { + if (!font.fontExtraProperties) { // Immediately release the `font.data` property once the font // has been attached to the DOM, since it's no longer needed, // rather than waiting for a `PDFDocumentProxy.cleanup` call. diff --git a/src/shared/obj-bin-transform.js b/src/shared/obj-bin-transform.js index 7e0f4b141..7c549b470 100644 --- a/src/shared/obj-bin-transform.js +++ b/src/shared/obj-bin-transform.js @@ -18,9 +18,9 @@ import { assert, FeatureTest, MeshFigureType } from "./util.js"; class CssFontInfo { #buffer; - #view; + #decoder = new TextDecoder(); - #decoder; + #view; static strings = ["fontFamily", "fontWeight", "italicAngle"]; @@ -53,7 +53,6 @@ class CssFontInfo { constructor(buffer) { this.#buffer = buffer; this.#view = new DataView(this.#buffer); - this.#decoder = new TextDecoder(); } #readString(index) { @@ -84,9 +83,9 @@ class CssFontInfo { class SystemFontInfo { #buffer; - #view; + #decoder = new TextDecoder(); - #decoder; + #view; static strings = ["css", "loadedName", "baseFontName", "src"]; @@ -147,7 +146,6 @@ class SystemFontInfo { constructor(buffer) { this.#buffer = buffer; this.#view = new DataView(this.#buffer); - this.#decoder = new TextDecoder(); } get guessFallback() { @@ -228,13 +226,12 @@ class FontInfo { #buffer; - #decoder; + #decoder = new TextDecoder(); #view; constructor({ data, extra }) { this.#buffer = data; - this.#decoder = new TextDecoder(); this.#view = new DataView(this.#buffer); if (extra) { Object.assign(this, extra); @@ -379,7 +376,7 @@ class FontInfo { return this.#readString(3); } - get data() { + #getDataOffsets() { let offset = FontInfo.#OFFSET_STRINGS; const stringsLength = this.#view.getUint32(offset); offset += 4 + stringsLength; @@ -388,25 +385,27 @@ class FontInfo { const cssFontInfoLength = this.#view.getUint32(offset); offset += 4 + cssFontInfoLength; const length = this.#view.getUint32(offset); - if (length === 0) { - return undefined; - } - return new Uint8Array(this.#buffer, offset + 4, length); + + return { offset, length }; + } + + get data() { + const { offset, length } = this.#getDataOffsets(); + return length === 0 + ? undefined + : new Uint8Array(this.#buffer, offset + 4, length); } clearData() { - let offset = FontInfo.#OFFSET_STRINGS; - const stringsLength = this.#view.getUint32(offset); - offset += 4 + stringsLength; - const systemFontInfoLength = this.#view.getUint32(offset); - offset += 4 + systemFontInfoLength; - const cssFontInfoLength = this.#view.getUint32(offset); - offset += 4 + cssFontInfoLength; - const length = this.#view.getUint32(offset); - const data = new Uint8Array(this.#buffer, offset + 4, length); - data.fill(0); - this.#view.setUint32(offset, 0); - // this.#buffer.resize(offset); + const { offset, length } = this.#getDataOffsets(); + if (length === 0) { + return; // The data is either not present, or it was previously cleared. + } + this.#view.setUint32(offset, 0); // Zero the data-length. + + // Replace the buffer/view with only its contents *before* the data-block. + this.#buffer = new Uint8Array(this.#buffer, 0, offset + 4).slice().buffer; + this.#view = new DataView(this.#buffer); } get cssFontInfo() { @@ -462,11 +461,11 @@ class FontInfo { 4 + stringsLength + 4 + - (systemFontInfoBuffer ? systemFontInfoBuffer.byteLength : 0) + + (systemFontInfoBuffer?.byteLength ?? 0) + 4 + - (cssFontInfoBuffer ? cssFontInfoBuffer.byteLength : 0) + + (cssFontInfoBuffer?.byteLength ?? 0) + 4 + - (font.data ? font.data.length : 0); + (font.data?.length ?? 0); const buffer = new ArrayBuffer(lengthEstimate); const data = new Uint8Array(buffer);