[api-minor] Add partial support for the "GoToE" action (issue 8844)
*Please note:* The referenced issue is the only mention that I can find, in either GitHub or Bugzilla, of "GoToE" actions. Hence why I've purposely settled for a very simple, and partial, "GoToE" implementation to avoid complicating things initially.[1] In particular, this patch only supports "GoToE" actions that references the /EmbeddedFiles-dict in the PDF document. See https://web.archive.org/web/20220309040754if_/https://www.adobe.com/content/dam/acom/en/devnet/pdf/pdfs/PDF32000_2008.pdf#G11.2048909 --- [1] Usually I always prefer having *real-world* test-cases to work with, whenever I'm implementing new features.
This commit is contained in:
parent
8c59cc72a3
commit
ce66fefbff
@ -80,15 +80,19 @@ class AnnotationFactory {
|
||||
// Only necessary to prevent the `pdfManager.docBaseUrl`-getter, used
|
||||
// with certain Annotations, from throwing and thus breaking parsing:
|
||||
pdfManager.ensureCatalog("baseUrl"),
|
||||
// Only necessary in the `Catalog.parseDestDictionary`-method,
|
||||
// when parsing "GoToE" actions:
|
||||
pdfManager.ensureCatalog("attachments"),
|
||||
pdfManager.ensureDoc("xfaDatasets"),
|
||||
collectFields ? this._getPageIndex(xref, ref, pdfManager) : -1,
|
||||
]).then(([acroForm, baseUrl, xfaDatasets, pageIndex]) =>
|
||||
]).then(([acroForm, baseUrl, attachments, xfaDatasets, pageIndex]) =>
|
||||
pdfManager.ensure(this, "_create", [
|
||||
xref,
|
||||
ref,
|
||||
pdfManager,
|
||||
idFactory,
|
||||
acroForm,
|
||||
attachments,
|
||||
xfaDatasets,
|
||||
collectFields,
|
||||
pageIndex,
|
||||
@ -105,6 +109,7 @@ class AnnotationFactory {
|
||||
pdfManager,
|
||||
idFactory,
|
||||
acroForm,
|
||||
attachments = null,
|
||||
xfaDatasets,
|
||||
collectFields,
|
||||
pageIndex = -1
|
||||
@ -130,6 +135,7 @@ class AnnotationFactory {
|
||||
id,
|
||||
pdfManager,
|
||||
acroForm: acroForm instanceof Dict ? acroForm : Dict.empty,
|
||||
attachments,
|
||||
xfaDatasets,
|
||||
collectFields,
|
||||
pageIndex,
|
||||
@ -2893,6 +2899,7 @@ class ButtonWidgetAnnotation extends WidgetAnnotation {
|
||||
destDict: params.dict,
|
||||
resultObj: this.data,
|
||||
docBaseUrl: params.pdfManager.docBaseUrl,
|
||||
docAttachments: params.attachments,
|
||||
});
|
||||
}
|
||||
|
||||
@ -3221,6 +3228,7 @@ class LinkAnnotation extends Annotation {
|
||||
destDict: params.dict,
|
||||
resultObj: this.data,
|
||||
docBaseUrl: params.pdfManager.docBaseUrl,
|
||||
docAttachments: params.attachments,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -307,6 +307,7 @@ class Catalog {
|
||||
destDict: outlineDict,
|
||||
resultObj: data,
|
||||
docBaseUrl: this.pdfManager.docBaseUrl,
|
||||
docAttachments: this.attachments,
|
||||
});
|
||||
const title = outlineDict.get("Title");
|
||||
const flags = outlineDict.get("F") || 0;
|
||||
@ -325,6 +326,7 @@ class Catalog {
|
||||
|
||||
const outlineItem = {
|
||||
action: data.action,
|
||||
attachment: data.attachment,
|
||||
dest: data.dest,
|
||||
url: data.url,
|
||||
unsafeUrl: data.unsafeUrl,
|
||||
@ -1412,6 +1414,8 @@ class Catalog {
|
||||
* properties will be placed.
|
||||
* @property {string} [docBaseUrl] - The document base URL that is used when
|
||||
* attempting to recover valid absolute URLs from relative ones.
|
||||
* @property {Object} [docAttachments] - The document attachments (may not
|
||||
* exist in most PDF documents).
|
||||
*/
|
||||
|
||||
/**
|
||||
@ -1430,6 +1434,7 @@ class Catalog {
|
||||
return;
|
||||
}
|
||||
const docBaseUrl = params.docBaseUrl || null;
|
||||
const docAttachments = params.docAttachments || null;
|
||||
|
||||
let action = destDict.get("A"),
|
||||
url,
|
||||
@ -1526,6 +1531,26 @@ class Catalog {
|
||||
}
|
||||
break;
|
||||
|
||||
case "GoToE":
|
||||
const target = action.get("T");
|
||||
let attachment;
|
||||
|
||||
if (docAttachments && target instanceof Dict) {
|
||||
const relationship = target.get("R");
|
||||
const name = target.get("N");
|
||||
|
||||
if (isName(relationship, "C") && typeof name === "string") {
|
||||
attachment = docAttachments[stringToPDFString(name)];
|
||||
}
|
||||
}
|
||||
|
||||
if (attachment) {
|
||||
resultObj.attachment = attachment;
|
||||
} else {
|
||||
warn(`parseDestDictionary - unimplemented "GoToE" action.`);
|
||||
}
|
||||
break;
|
||||
|
||||
case "Named":
|
||||
const namedAction = action.get("N");
|
||||
if (namedAction instanceof Name) {
|
||||
|
@ -595,6 +595,9 @@ class LinkAnnotationElement extends AnnotationElement {
|
||||
} else if (data.action) {
|
||||
this._bindNamedAction(link, data.action);
|
||||
isBound = true;
|
||||
} else if (data.attachment) {
|
||||
this._bindAttachment(link, data.attachment);
|
||||
isBound = true;
|
||||
} else if (data.setOCGState) {
|
||||
this.#bindSetOCGState(link, data.setOCGState);
|
||||
isBound = true;
|
||||
@ -679,6 +682,24 @@ class LinkAnnotationElement extends AnnotationElement {
|
||||
link.className = "internalLink";
|
||||
}
|
||||
|
||||
/**
|
||||
* Bind attachments to the link element.
|
||||
* @param {Object} link
|
||||
* @param {Object} attachment
|
||||
*/
|
||||
_bindAttachment(link, attachment) {
|
||||
link.href = this.linkService.getAnchorUrl("");
|
||||
link.onclick = () => {
|
||||
this.downloadManager?.openOrDownloadData(
|
||||
this.container,
|
||||
attachment.content,
|
||||
attachment.filename
|
||||
);
|
||||
return false;
|
||||
};
|
||||
link.className = "internalLink";
|
||||
}
|
||||
|
||||
/**
|
||||
* Bind SetOCGState actions to the link element.
|
||||
* @param {Object} link
|
||||
|
1
test/pdfs/.gitignore
vendored
1
test/pdfs/.gitignore
vendored
@ -49,6 +49,7 @@
|
||||
!issue7426.pdf
|
||||
!issue7439.pdf
|
||||
!issue7847_radial.pdf
|
||||
!issue8844.pdf
|
||||
!issue14953.pdf
|
||||
!issue15367.pdf
|
||||
!issue15372.pdf
|
||||
|
BIN
test/pdfs/issue8844.pdf
Normal file
BIN
test/pdfs/issue8844.pdf
Normal file
Binary file not shown.
@ -15,6 +15,7 @@
|
||||
|
||||
import {
|
||||
AnnotationMode,
|
||||
AnnotationType,
|
||||
createPromiseCapability,
|
||||
FontType,
|
||||
ImageKind,
|
||||
@ -1536,6 +1537,7 @@ describe("api", function () {
|
||||
|
||||
expect(outline[4]).toEqual({
|
||||
action: null,
|
||||
attachment: undefined,
|
||||
dest: "Händel -- Halle🎆lujah",
|
||||
url: null,
|
||||
unsafeUrl: undefined,
|
||||
@ -1562,6 +1564,7 @@ describe("api", function () {
|
||||
|
||||
expect(outline[1]).toEqual({
|
||||
action: "PrevPage",
|
||||
attachment: undefined,
|
||||
dest: null,
|
||||
url: null,
|
||||
unsafeUrl: undefined,
|
||||
@ -1588,6 +1591,7 @@ describe("api", function () {
|
||||
|
||||
expect(outline[0]).toEqual({
|
||||
action: null,
|
||||
attachment: undefined,
|
||||
dest: null,
|
||||
url: null,
|
||||
unsafeUrl: undefined,
|
||||
@ -2155,6 +2159,23 @@ describe("api", function () {
|
||||
]);
|
||||
});
|
||||
|
||||
it("gets annotations containing GoToE action (issue 8844)", async function () {
|
||||
const loadingTask = getDocument(buildGetDocumentParams("issue8844.pdf"));
|
||||
const pdfDoc = await loadingTask.promise;
|
||||
const pdfPage = await pdfDoc.getPage(1);
|
||||
const annotations = await pdfPage.getAnnotations();
|
||||
|
||||
expect(annotations.length).toEqual(1);
|
||||
expect(annotations[0].annotationType).toEqual(AnnotationType.LINK);
|
||||
|
||||
const { filename, content } = annotations[0].attachment;
|
||||
expect(filename).toEqual("man.pdf");
|
||||
expect(content instanceof Uint8Array).toEqual(true);
|
||||
expect(content.length).toEqual(4508);
|
||||
|
||||
await loadingTask.destroy();
|
||||
});
|
||||
|
||||
it("gets text content", async function () {
|
||||
const defaultPromise = page.getTextContent();
|
||||
const parametersPromise = page.getTextContent({
|
||||
|
@ -636,6 +636,7 @@ const PDFViewerApplication = {
|
||||
container: appConfig.sidebar.outlineView,
|
||||
eventBus,
|
||||
linkService: pdfLinkService,
|
||||
downloadManager,
|
||||
});
|
||||
|
||||
this.pdfAttachmentViewer = new PDFAttachmentViewer({
|
||||
|
@ -20,8 +20,9 @@ import { SidebarView } from "./ui_utils.js";
|
||||
/**
|
||||
* @typedef {Object} PDFOutlineViewerOptions
|
||||
* @property {HTMLDivElement} container - The viewer element.
|
||||
* @property {IPDFLinkService} linkService - The navigation/linking service.
|
||||
* @property {EventBus} eventBus - The application event bus.
|
||||
* @property {IPDFLinkService} linkService - The navigation/linking service.
|
||||
* @property {DownloadManager} downloadManager - The download manager.
|
||||
*/
|
||||
|
||||
/**
|
||||
@ -37,6 +38,7 @@ class PDFOutlineViewer extends BaseTreeViewer {
|
||||
constructor(options) {
|
||||
super(options);
|
||||
this.linkService = options.linkService;
|
||||
this.downloadManager = options.downloadManager;
|
||||
|
||||
this.eventBus._on("toggleoutlinetree", this._toggleAllTreeItems.bind(this));
|
||||
this.eventBus._on(
|
||||
@ -109,7 +111,10 @@ class PDFOutlineViewer extends BaseTreeViewer {
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
_bindLink(element, { url, newWindow, action, dest, setOCGState }) {
|
||||
_bindLink(
|
||||
element,
|
||||
{ url, newWindow, action, attachment, dest, setOCGState }
|
||||
) {
|
||||
const { linkService } = this;
|
||||
|
||||
if (url) {
|
||||
@ -124,6 +129,18 @@ class PDFOutlineViewer extends BaseTreeViewer {
|
||||
};
|
||||
return;
|
||||
}
|
||||
if (attachment) {
|
||||
element.href = linkService.getAnchorUrl("");
|
||||
element.onclick = () => {
|
||||
this.downloadManager.openOrDownloadData(
|
||||
element,
|
||||
attachment.content,
|
||||
attachment.filename
|
||||
);
|
||||
return false;
|
||||
};
|
||||
return;
|
||||
}
|
||||
if (setOCGState) {
|
||||
element.href = linkService.getAnchorUrl("");
|
||||
element.onclick = () => {
|
||||
|
Loading…
Reference in New Issue
Block a user