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.)
This commit is contained in:
Jonas Jenwald 2026-01-25 13:16:29 +01:00
parent 84b5866853
commit 640a3106d5
3 changed files with 21 additions and 37 deletions

View File

@ -5290,8 +5290,8 @@ class FileAttachmentAnnotation extends MarkupAnnotation {
constructor(params) { constructor(params) {
super(params); super(params);
const { dict, xref } = params; const { dict } = params;
const file = new FileSpec(dict.get("FS"), xref); const file = new FileSpec(dict.get("FS"));
this.data.annotationType = AnnotationType.FILEATTACHMENT; this.data.annotationType = AnnotationType.FILEATTACHMENT;
this.data.hasOwnCanvas = this.data.noRotate; this.data.hasOwnCanvas = this.data.noRotate;

View File

@ -1057,7 +1057,7 @@ class Catalog {
if (obj instanceof Dict && obj.has("EmbeddedFiles")) { if (obj instanceof Dict && obj.has("EmbeddedFiles")) {
const nameTree = new NameTree(obj.getRaw("EmbeddedFiles"), this.xref); const nameTree = new NameTree(obj.getRaw("EmbeddedFiles"), this.xref);
for (const [key, value] of nameTree.getAll()) { for (const [key, value] of nameTree.getAll()) {
const fs = new FileSpec(value, this.xref); const fs = new FileSpec(value);
attachments ??= Object.create(null); attachments ??= Object.create(null);
attachments[stringToPDFString(key, /* keepEscapeSequence = */ true)] = attachments[stringToPDFString(key, /* keepEscapeSequence = */ true)] =
fs.serializable; fs.serializable;
@ -1623,11 +1623,7 @@ class Catalog {
case "GoToR": case "GoToR":
const urlDict = action.get("F"); const urlDict = action.get("F");
if (urlDict instanceof Dict) { if (urlDict instanceof Dict) {
const fs = new FileSpec( const fs = new FileSpec(urlDict, /* skipContent = */ true);
urlDict,
/* xref = */ null,
/* skipContent = */ true
);
({ rawFilename: url } = fs.serializable); ({ rawFilename: url } = fs.serializable);
} else if (typeof urlDict === "string") { } else if (typeof urlDict === "string") {
url = urlDict; url = urlDict;

View File

@ -13,7 +13,7 @@
* limitations under the License. * 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 { BaseStream } from "./base_stream.js";
import { Dict } from "./primitives.js"; import { Dict } from "./primitives.js";
@ -43,11 +43,10 @@ function stripPath(str) {
class FileSpec { class FileSpec {
#contentAvailable = false; #contentAvailable = false;
constructor(root, xref, skipContent = false) { constructor(root, skipContent = false) {
if (!(root instanceof Dict)) { if (!(root instanceof Dict)) {
return; return;
} }
this.xref = xref;
this.root = root; this.root = root;
if (root.has("FS")) { if (root.has("FS")) {
this.fs = root.get("FS"); this.fs = root.get("FS");
@ -65,56 +64,45 @@ class FileSpec {
} }
get filename() { get filename() {
let filename = "";
const item = pickPlatformItem(this.root); const item = pickPlatformItem(this.root);
let name;
if (item && typeof item === "string") { if (item && typeof item === "string") {
filename = stringToPDFString(item, /* keepEscapeSequence = */ true) name = stringToPDFString(item, /* keepEscapeSequence = */ true)
.replaceAll("\\\\", "\\") .replaceAll("\\\\", "\\")
.replaceAll("\\/", "/") .replaceAll("\\/", "/")
.replaceAll("\\", "/"); .replaceAll("\\", "/");
} }
return shadow(this, "filename", filename || "unnamed"); return name || "unnamed";
} }
get content() { get content() {
if (!this.#contentAvailable) { if (!this.#contentAvailable) {
return null; return null;
} }
this._contentRef ||= pickPlatformItem(this.root?.get("EF")); const ef = pickPlatformItem(this.root?.get("EF"));
let content = null; if (ef instanceof BaseStream) {
if (this._contentRef) { return ef.getBytes();
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 points to non-existing/invalid content");
warn("Embedded file specification does not have any content"); return null;
}
return content;
} }
get description() { get description() {
let description = "";
const desc = this.root?.get("Desc"); const desc = this.root?.get("Desc");
if (desc && typeof desc === "string") { if (desc && typeof desc === "string") {
description = stringToPDFString(desc); return stringToPDFString(desc);
} }
return shadow(this, "description", description); return "";
} }
get serializable() { get serializable() {
const { filename, content, description } = this;
return { return {
rawFilename: this.filename, rawFilename: filename,
filename: stripPath(this.filename), filename: stripPath(filename),
content: this.content, content,
description: this.description, description,
}; };
} }
} }