diff --git a/src/core/catalog.js b/src/core/catalog.js index 8f354775c..856e384fe 100644 --- a/src/core/catalog.js +++ b/src/core/catalog.js @@ -59,6 +59,21 @@ function fetchDestination(dest) { return Array.isArray(dest) ? dest : null; } +function fetchRemoteDest(action) { + let dest = action.get("D"); + if (dest) { + if (dest instanceof Name) { + dest = dest.name; + } + if (typeof dest === "string") { + return stringToPDFString(dest); + } else if (Array.isArray(dest)) { + return JSON.stringify(dest); + } + } + return null; +} + class Catalog { constructor(pdfManager, xref) { this.pdfManager = pdfManager; @@ -1514,19 +1529,9 @@ class Catalog { } // NOTE: the destination is relative to the *remote* document. - let remoteDest = action.get("D"); - if (remoteDest) { - if (remoteDest instanceof Name) { - remoteDest = remoteDest.name; - } - if (typeof url === "string") { - const baseUrl = url.split("#")[0]; - if (typeof remoteDest === "string") { - url = baseUrl + "#" + remoteDest; - } else if (Array.isArray(remoteDest)) { - url = baseUrl + "#" + JSON.stringify(remoteDest); - } - } + const remoteDest = fetchRemoteDest(action); + if (remoteDest && typeof url === "string") { + url = /* baseUrl = */ url.split("#", 1)[0] + "#" + remoteDest; } // The 'NewWindow' property, equal to `LinkTarget.BLANK`. const newWindow = action.get("NewWindow"); @@ -1550,6 +1555,12 @@ class Catalog { if (attachment) { resultObj.attachment = attachment; + + // NOTE: the destination is relative to the *attachment*. + const attachmentDest = fetchRemoteDest(action); + if (attachmentDest) { + resultObj.attachmentDest = attachmentDest; + } } else { warn(`parseDestDictionary - unimplemented "GoToE" action.`); } diff --git a/src/display/annotation_layer.js b/src/display/annotation_layer.js index 3c17538e2..32287337f 100644 --- a/src/display/annotation_layer.js +++ b/src/display/annotation_layer.js @@ -709,7 +709,7 @@ class LinkAnnotationElement extends AnnotationElement { this._bindNamedAction(link, data.action); isBound = true; } else if (data.attachment) { - this._bindAttachment(link, data.attachment); + this.#bindAttachment(link, data.attachment, data.attachmentDest); isBound = true; } else if (data.setOCGState) { this.#bindSetOCGState(link, data.setOCGState); @@ -793,14 +793,16 @@ class LinkAnnotationElement extends AnnotationElement { * Bind attachments to the link element. * @param {Object} link * @param {Object} attachment + * @param {str} [dest] */ - _bindAttachment(link, attachment) { + #bindAttachment(link, attachment, dest = null) { link.href = this.linkService.getAnchorUrl(""); link.onclick = () => { this.downloadManager?.openOrDownloadData( this.container, attachment.content, - attachment.filename + attachment.filename, + dest ); return false; }; diff --git a/test/pdfs/.gitignore b/test/pdfs/.gitignore index 12d64ee27..bde07e5b3 100644 --- a/test/pdfs/.gitignore +++ b/test/pdfs/.gitignore @@ -51,6 +51,7 @@ !issue7439.pdf !issue7847_radial.pdf !issue8844.pdf +!issue17056.pdf !issue14953.pdf !issue15367.pdf !issue15372.pdf diff --git a/test/pdfs/issue17056.pdf b/test/pdfs/issue17056.pdf new file mode 100644 index 000000000..a05dd21b1 Binary files /dev/null and b/test/pdfs/issue17056.pdf differ diff --git a/test/unit/api_spec.js b/test/unit/api_spec.js index 3e7eea679..f569a6dd2 100644 --- a/test/unit/api_spec.js +++ b/test/unit/api_spec.js @@ -2855,6 +2855,29 @@ describe("api", function () { expect(content instanceof Uint8Array).toEqual(true); expect(content.length).toEqual(4508); + expect(annotations[0].attachmentDest).toEqual('[-1,{"name":"Fit"}]'); + + await loadingTask.destroy(); + }); + + it("gets annotations containing GoToE action with destination (issue 17056)", async function () { + const loadingTask = getDocument(buildGetDocumentParams("issue17056.pdf")); + const pdfDoc = await loadingTask.promise; + const pdfPage = await pdfDoc.getPage(1); + + const annotations = await pdfPage.getAnnotations(); + expect(annotations.length).toEqual(30); + + const { annotationType, attachment, attachmentDest } = annotations[0]; + expect(annotationType).toEqual(AnnotationType.LINK); + + const { filename, content } = attachment; + expect(filename).toEqual("destination-doc.pdf"); + expect(content instanceof Uint8Array).toEqual(true); + expect(content.length).toEqual(10305); + + expect(attachmentDest).toEqual('[0,{"name":"Fit"}]'); + await loadingTask.destroy(); }); diff --git a/web/download_manager.js b/web/download_manager.js index 6117297a8..26d655bc1 100644 --- a/web/download_manager.js +++ b/web/download_manager.js @@ -67,7 +67,7 @@ class DownloadManager { /** * @returns {boolean} Indicating if the data was opened. */ - openOrDownloadData(element, data, filename) { + openOrDownloadData(element, data, filename, dest = null) { const isPdfData = isPdfFile(filename); const contentType = isPdfData ? "application/pdf" : ""; @@ -93,6 +93,9 @@ class DownloadManager { "?file=" + encodeURIComponent(blobUrl + "#" + filename); } + if (dest) { + viewerUrl += `#${escape(dest)}`; + } try { window.open(viewerUrl); diff --git a/web/firefoxcom.js b/web/firefoxcom.js index 7c886fb7a..438fbf9fd 100644 --- a/web/firefoxcom.js +++ b/web/firefoxcom.js @@ -132,7 +132,7 @@ class DownloadManager { /** * @returns {boolean} Indicating if the data was opened. */ - openOrDownloadData(element, data, filename) { + openOrDownloadData(element, data, filename, dest = null) { const isPdfData = isPdfFile(filename); const contentType = isPdfData ? "application/pdf" : ""; @@ -143,7 +143,10 @@ class DownloadManager { this.#openBlobUrls.set(element, blobUrl); } // Let Firefox's content handler catch the URL and display the PDF. - const viewerUrl = blobUrl + "#filename=" + encodeURIComponent(filename); + let viewerUrl = blobUrl + "?filename=" + encodeURIComponent(filename); + if (dest) { + viewerUrl += `#${escape(dest)}`; + } try { window.open(viewerUrl);