diff --git a/src/core/file_spec.js b/src/core/file_spec.js index 8ce91c35275e6..11ddb38e452b6 100644 --- a/src/core/file_spec.js +++ b/src/core/file_spec.js @@ -13,7 +13,7 @@ * limitations under the License. */ -import { stringToPDFString, warn } from "../shared/util.js"; +import { shadow, stringToPDFString, warn } from "../shared/util.js"; import { BaseStream } from "./base_stream.js"; import { Dict } from "./primitives.js"; @@ -53,9 +53,6 @@ class FileSpec { if (root.has("FS")) { this.fs = root.get("FS"); } - this.description = root.has("Desc") - ? stringToPDFString(root.get("Desc")) - : ""; if (root.has("RF")) { warn("Related file specifications are not supported"); } @@ -102,10 +99,21 @@ class FileSpec { return content; } + get description() { + let description = ""; + + const desc = this.root?.get("Desc"); + if (desc && typeof desc === "string") { + description = stringToPDFString(desc); + } + return shadow(this, "description", description); + } + get serializable() { return { filename: this.filename, content: this.content, + description: this.description, }; } } diff --git a/src/display/annotation_layer.js b/src/display/annotation_layer.js index e6555745c15bd..6be8d71e9da90 100644 --- a/src/display/annotation_layer.js +++ b/src/display/annotation_layer.js @@ -857,6 +857,9 @@ class LinkAnnotationElement extends AnnotationElement { */ #bindAttachment(link, attachment, dest = null) { link.href = this.linkService.getAnchorUrl(""); + if (attachment.description) { + link.title = attachment.description; + } link.onclick = () => { this.downloadManager?.openOrDownloadData( attachment.content, @@ -2856,7 +2859,7 @@ class FileAttachmentAnnotationElement extends AnnotationElement { constructor(parameters) { super(parameters, { isRenderable: true }); - const { filename, content } = this.data.file; + const { filename, content, description } = this.data.file; this.filename = getFilenameFromUrl(filename, /* onlyStripPath = */ true); this.content = content; @@ -2864,6 +2867,7 @@ class FileAttachmentAnnotationElement extends AnnotationElement { source: this, filename, content, + description, }); } diff --git a/test/pdfs/.gitignore b/test/pdfs/.gitignore index 0f516162583a0..1f1198aa80d8c 100644 --- a/test/pdfs/.gitignore +++ b/test/pdfs/.gitignore @@ -54,6 +54,7 @@ !issue17056.pdf !issue17679.pdf !issue17679_2.pdf +!issue18030.pdf !issue14953.pdf !issue15367.pdf !issue15372.pdf diff --git a/test/pdfs/issue18030.pdf b/test/pdfs/issue18030.pdf new file mode 100644 index 0000000000000..a30b7ff2ca1c1 Binary files /dev/null and b/test/pdfs/issue18030.pdf differ diff --git a/test/unit/annotation_spec.js b/test/unit/annotation_spec.js index 5432a4ab63511..f118eae3d5e5e 100644 --- a/test/unit/annotation_spec.js +++ b/test/unit/annotation_spec.js @@ -4005,7 +4005,7 @@ describe("annotation", function () { const fileSpecRef = Ref.get(19, 0); const fileSpecDict = new Dict(); fileSpecDict.set("Type", Name.get("Filespec")); - fileSpecDict.set("Desc", ""); + fileSpecDict.set("Desc", "abc"); fileSpecDict.set("EF", embeddedFileDict); fileSpecDict.set("UF", "Test.txt"); @@ -4035,6 +4035,7 @@ describe("annotation", function () { expect(data.annotationType).toEqual(AnnotationType.FILEATTACHMENT); expect(data.file.filename).toEqual("Test.txt"); expect(data.file.content).toEqual(stringToBytes("Test attachment")); + expect(data.file.description).toEqual("abc"); }); }); diff --git a/test/unit/api_spec.js b/test/unit/api_spec.js index 08db9bfb80bde..6a0c034a204b3 100644 --- a/test/unit/api_spec.js +++ b/test/unit/api_spec.js @@ -1475,11 +1475,28 @@ describe("api", function () { const pdfDoc = await loadingTask.promise; const attachments = await pdfDoc.getAttachments(); - const attachment = attachments["foo.txt"]; - expect(attachment.filename).toEqual("foo.txt"); - expect(attachment.content).toEqual( + const { filename, content, description } = attachments["foo.txt"]; + expect(filename).toEqual("foo.txt"); + expect(content).toEqual( new Uint8Array([98, 97, 114, 32, 98, 97, 122, 32, 10]) ); + expect(description).toEqual(""); + + await loadingTask.destroy(); + }); + + it("gets attachments, with /Desc", async function () { + const loadingTask = getDocument(buildGetDocumentParams("issue18030.pdf")); + const pdfDoc = await loadingTask.promise; + const attachments = await pdfDoc.getAttachments(); + + const { filename, content, description } = attachments["empty.pdf"]; + expect(filename).toEqual("Empty page.pdf"); + expect(content instanceof Uint8Array).toEqual(true); + expect(content.length).toEqual(2357); + expect(description).toEqual( + "SHA512: 06bec56808f93846f1d41ff0be4e54079c1291b860378c801c0f35f1d127a8680923ff6de59bd5a9692f01f0d97ca4f26da178ed03635fa4813d86c58a6c981a" + ); await loadingTask.destroy(); }); diff --git a/web/pdf_attachment_viewer.js b/web/pdf_attachment_viewer.js index 53aef16db1d64..eb0cfd9e612f4 100644 --- a/web/pdf_attachment_viewer.js +++ b/web/pdf_attachment_viewer.js @@ -94,7 +94,10 @@ class PDFAttachmentViewer extends BaseTreeViewer { /** * @protected */ - _bindLink(element, { content, filename }) { + _bindLink(element, { content, description, filename }) { + if (description) { + element.title = description; + } element.onclick = () => { this.downloadManager.openOrDownloadData(content, filename); return false; @@ -120,6 +123,7 @@ class PDFAttachmentViewer extends BaseTreeViewer { for (const name in attachments) { const item = attachments[name]; const content = item.content, + description = item.description, filename = getFilenameFromUrl( item.filename, /* onlyStripPath = */ true @@ -129,7 +133,7 @@ class PDFAttachmentViewer extends BaseTreeViewer { div.className = "treeItem"; const element = document.createElement("a"); - this._bindLink(element, { content, filename }); + this._bindLink(element, { content, description, filename }); element.textContent = this._normalizeTextContent(filename); div.append(element); @@ -144,7 +148,7 @@ class PDFAttachmentViewer extends BaseTreeViewer { /** * Used to append FileAttachment annotations to the sidebar. */ - #appendAttachment({ filename, content }) { + #appendAttachment({ filename, content, description }) { const renderedPromise = this._renderedCapability.promise; renderedPromise.then(() => { @@ -161,6 +165,7 @@ class PDFAttachmentViewer extends BaseTreeViewer { attachments[filename] = { filename, content, + description, }; this.render({ attachments,