From 640a3106d5e254e7392eae0861c3f5e30cdf6276 Mon Sep 17 00:00:00 2001 From: Jonas Jenwald Date: Sun, 25 Jan 2026 13:16:29 +0100 Subject: [PATCH] Remove caching/shadowing from the `FileSpec` getters, and simplify the code Given that only the `FileSpec.prototype.serializable` getter is ever invoked from "outside" of the class, and only once per `FileSpec`-instance, the caching/shadowing isn't actually necessary. Furthermore the `_contentRef`-caching wasn't actually correct, since it ended up storing a `BaseStream`-instance and those should *generally* never be cached. (Since calling `BaseStream.prototype.getBytes()` more than once, without resetting the stream in between, will return an empty TypedArray after the first time.) --- src/core/annotation.js | 4 ++-- src/core/catalog.js | 8 ++------ src/core/file_spec.js | 46 ++++++++++++++++-------------------------- 3 files changed, 21 insertions(+), 37 deletions(-) diff --git a/src/core/annotation.js b/src/core/annotation.js index f2f642b50..a0da4ae46 100644 --- a/src/core/annotation.js +++ b/src/core/annotation.js @@ -5290,8 +5290,8 @@ class FileAttachmentAnnotation extends MarkupAnnotation { constructor(params) { super(params); - const { dict, xref } = params; - const file = new FileSpec(dict.get("FS"), xref); + const { dict } = params; + const file = new FileSpec(dict.get("FS")); this.data.annotationType = AnnotationType.FILEATTACHMENT; this.data.hasOwnCanvas = this.data.noRotate; diff --git a/src/core/catalog.js b/src/core/catalog.js index 6e183d964..9ab10f281 100644 --- a/src/core/catalog.js +++ b/src/core/catalog.js @@ -1057,7 +1057,7 @@ class Catalog { if (obj instanceof Dict && obj.has("EmbeddedFiles")) { const nameTree = new NameTree(obj.getRaw("EmbeddedFiles"), this.xref); for (const [key, value] of nameTree.getAll()) { - const fs = new FileSpec(value, this.xref); + const fs = new FileSpec(value); attachments ??= Object.create(null); attachments[stringToPDFString(key, /* keepEscapeSequence = */ true)] = fs.serializable; @@ -1623,11 +1623,7 @@ class Catalog { case "GoToR": const urlDict = action.get("F"); if (urlDict instanceof Dict) { - const fs = new FileSpec( - urlDict, - /* xref = */ null, - /* skipContent = */ true - ); + const fs = new FileSpec(urlDict, /* skipContent = */ true); ({ rawFilename: url } = fs.serializable); } else if (typeof urlDict === "string") { url = urlDict; diff --git a/src/core/file_spec.js b/src/core/file_spec.js index 6e544295d..198a663c9 100644 --- a/src/core/file_spec.js +++ b/src/core/file_spec.js @@ -13,7 +13,7 @@ * limitations under the License. */ -import { shadow, stringToPDFString, warn } from "../shared/util.js"; +import { stringToPDFString, warn } from "../shared/util.js"; import { BaseStream } from "./base_stream.js"; import { Dict } from "./primitives.js"; @@ -43,11 +43,10 @@ function stripPath(str) { class FileSpec { #contentAvailable = false; - constructor(root, xref, skipContent = false) { + constructor(root, skipContent = false) { if (!(root instanceof Dict)) { return; } - this.xref = xref; this.root = root; if (root.has("FS")) { this.fs = root.get("FS"); @@ -65,56 +64,45 @@ class FileSpec { } get filename() { - let filename = ""; - const item = pickPlatformItem(this.root); + let name; if (item && typeof item === "string") { - filename = stringToPDFString(item, /* keepEscapeSequence = */ true) + name = stringToPDFString(item, /* keepEscapeSequence = */ true) .replaceAll("\\\\", "\\") .replaceAll("\\/", "/") .replaceAll("\\", "/"); } - return shadow(this, "filename", filename || "unnamed"); + return name || "unnamed"; } get content() { if (!this.#contentAvailable) { return null; } - this._contentRef ||= pickPlatformItem(this.root?.get("EF")); + const ef = pickPlatformItem(this.root?.get("EF")); - let content = null; - if (this._contentRef) { - const fileObj = this.xref.fetchIfRef(this._contentRef); - if (fileObj instanceof BaseStream) { - content = fileObj.getBytes(); - } else { - warn( - "Embedded file specification points to non-existing/invalid content" - ); - } - } else { - warn("Embedded file specification does not have any content"); + if (ef instanceof BaseStream) { + return ef.getBytes(); } - return content; + warn("Embedded file specification points to non-existing/invalid content"); + return null; } get description() { - let description = ""; - const desc = this.root?.get("Desc"); if (desc && typeof desc === "string") { - description = stringToPDFString(desc); + return stringToPDFString(desc); } - return shadow(this, "description", description); + return ""; } get serializable() { + const { filename, content, description } = this; return { - rawFilename: this.filename, - filename: stripPath(this.filename), - content: this.content, - description: this.description, + rawFilename: filename, + filename: stripPath(filename), + content, + description, }; } }