[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
|
// Only necessary to prevent the `pdfManager.docBaseUrl`-getter, used
|
||||||
// with certain Annotations, from throwing and thus breaking parsing:
|
// with certain Annotations, from throwing and thus breaking parsing:
|
||||||
pdfManager.ensureCatalog("baseUrl"),
|
pdfManager.ensureCatalog("baseUrl"),
|
||||||
|
// Only necessary in the `Catalog.parseDestDictionary`-method,
|
||||||
|
// when parsing "GoToE" actions:
|
||||||
|
pdfManager.ensureCatalog("attachments"),
|
||||||
pdfManager.ensureDoc("xfaDatasets"),
|
pdfManager.ensureDoc("xfaDatasets"),
|
||||||
collectFields ? this._getPageIndex(xref, ref, pdfManager) : -1,
|
collectFields ? this._getPageIndex(xref, ref, pdfManager) : -1,
|
||||||
]).then(([acroForm, baseUrl, xfaDatasets, pageIndex]) =>
|
]).then(([acroForm, baseUrl, attachments, xfaDatasets, pageIndex]) =>
|
||||||
pdfManager.ensure(this, "_create", [
|
pdfManager.ensure(this, "_create", [
|
||||||
xref,
|
xref,
|
||||||
ref,
|
ref,
|
||||||
pdfManager,
|
pdfManager,
|
||||||
idFactory,
|
idFactory,
|
||||||
acroForm,
|
acroForm,
|
||||||
|
attachments,
|
||||||
xfaDatasets,
|
xfaDatasets,
|
||||||
collectFields,
|
collectFields,
|
||||||
pageIndex,
|
pageIndex,
|
||||||
@ -105,6 +109,7 @@ class AnnotationFactory {
|
|||||||
pdfManager,
|
pdfManager,
|
||||||
idFactory,
|
idFactory,
|
||||||
acroForm,
|
acroForm,
|
||||||
|
attachments = null,
|
||||||
xfaDatasets,
|
xfaDatasets,
|
||||||
collectFields,
|
collectFields,
|
||||||
pageIndex = -1
|
pageIndex = -1
|
||||||
@ -130,6 +135,7 @@ class AnnotationFactory {
|
|||||||
id,
|
id,
|
||||||
pdfManager,
|
pdfManager,
|
||||||
acroForm: acroForm instanceof Dict ? acroForm : Dict.empty,
|
acroForm: acroForm instanceof Dict ? acroForm : Dict.empty,
|
||||||
|
attachments,
|
||||||
xfaDatasets,
|
xfaDatasets,
|
||||||
collectFields,
|
collectFields,
|
||||||
pageIndex,
|
pageIndex,
|
||||||
@ -2893,6 +2899,7 @@ class ButtonWidgetAnnotation extends WidgetAnnotation {
|
|||||||
destDict: params.dict,
|
destDict: params.dict,
|
||||||
resultObj: this.data,
|
resultObj: this.data,
|
||||||
docBaseUrl: params.pdfManager.docBaseUrl,
|
docBaseUrl: params.pdfManager.docBaseUrl,
|
||||||
|
docAttachments: params.attachments,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -3221,6 +3228,7 @@ class LinkAnnotation extends Annotation {
|
|||||||
destDict: params.dict,
|
destDict: params.dict,
|
||||||
resultObj: this.data,
|
resultObj: this.data,
|
||||||
docBaseUrl: params.pdfManager.docBaseUrl,
|
docBaseUrl: params.pdfManager.docBaseUrl,
|
||||||
|
docAttachments: params.attachments,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -307,6 +307,7 @@ class Catalog {
|
|||||||
destDict: outlineDict,
|
destDict: outlineDict,
|
||||||
resultObj: data,
|
resultObj: data,
|
||||||
docBaseUrl: this.pdfManager.docBaseUrl,
|
docBaseUrl: this.pdfManager.docBaseUrl,
|
||||||
|
docAttachments: this.attachments,
|
||||||
});
|
});
|
||||||
const title = outlineDict.get("Title");
|
const title = outlineDict.get("Title");
|
||||||
const flags = outlineDict.get("F") || 0;
|
const flags = outlineDict.get("F") || 0;
|
||||||
@ -325,6 +326,7 @@ class Catalog {
|
|||||||
|
|
||||||
const outlineItem = {
|
const outlineItem = {
|
||||||
action: data.action,
|
action: data.action,
|
||||||
|
attachment: data.attachment,
|
||||||
dest: data.dest,
|
dest: data.dest,
|
||||||
url: data.url,
|
url: data.url,
|
||||||
unsafeUrl: data.unsafeUrl,
|
unsafeUrl: data.unsafeUrl,
|
||||||
@ -1412,6 +1414,8 @@ class Catalog {
|
|||||||
* properties will be placed.
|
* properties will be placed.
|
||||||
* @property {string} [docBaseUrl] - The document base URL that is used when
|
* @property {string} [docBaseUrl] - The document base URL that is used when
|
||||||
* attempting to recover valid absolute URLs from relative ones.
|
* 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;
|
return;
|
||||||
}
|
}
|
||||||
const docBaseUrl = params.docBaseUrl || null;
|
const docBaseUrl = params.docBaseUrl || null;
|
||||||
|
const docAttachments = params.docAttachments || null;
|
||||||
|
|
||||||
let action = destDict.get("A"),
|
let action = destDict.get("A"),
|
||||||
url,
|
url,
|
||||||
@ -1526,6 +1531,26 @@ class Catalog {
|
|||||||
}
|
}
|
||||||
break;
|
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":
|
case "Named":
|
||||||
const namedAction = action.get("N");
|
const namedAction = action.get("N");
|
||||||
if (namedAction instanceof Name) {
|
if (namedAction instanceof Name) {
|
||||||
|
@ -595,6 +595,9 @@ class LinkAnnotationElement extends AnnotationElement {
|
|||||||
} else if (data.action) {
|
} else if (data.action) {
|
||||||
this._bindNamedAction(link, data.action);
|
this._bindNamedAction(link, data.action);
|
||||||
isBound = true;
|
isBound = true;
|
||||||
|
} else if (data.attachment) {
|
||||||
|
this._bindAttachment(link, data.attachment);
|
||||||
|
isBound = true;
|
||||||
} else if (data.setOCGState) {
|
} else if (data.setOCGState) {
|
||||||
this.#bindSetOCGState(link, data.setOCGState);
|
this.#bindSetOCGState(link, data.setOCGState);
|
||||||
isBound = true;
|
isBound = true;
|
||||||
@ -679,6 +682,24 @@ class LinkAnnotationElement extends AnnotationElement {
|
|||||||
link.className = "internalLink";
|
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.
|
* Bind SetOCGState actions to the link element.
|
||||||
* @param {Object} link
|
* @param {Object} link
|
||||||
|
1
test/pdfs/.gitignore
vendored
1
test/pdfs/.gitignore
vendored
@ -49,6 +49,7 @@
|
|||||||
!issue7426.pdf
|
!issue7426.pdf
|
||||||
!issue7439.pdf
|
!issue7439.pdf
|
||||||
!issue7847_radial.pdf
|
!issue7847_radial.pdf
|
||||||
|
!issue8844.pdf
|
||||||
!issue14953.pdf
|
!issue14953.pdf
|
||||||
!issue15367.pdf
|
!issue15367.pdf
|
||||||
!issue15372.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 {
|
import {
|
||||||
AnnotationMode,
|
AnnotationMode,
|
||||||
|
AnnotationType,
|
||||||
createPromiseCapability,
|
createPromiseCapability,
|
||||||
FontType,
|
FontType,
|
||||||
ImageKind,
|
ImageKind,
|
||||||
@ -1536,6 +1537,7 @@ describe("api", function () {
|
|||||||
|
|
||||||
expect(outline[4]).toEqual({
|
expect(outline[4]).toEqual({
|
||||||
action: null,
|
action: null,
|
||||||
|
attachment: undefined,
|
||||||
dest: "Händel -- Halle🎆lujah",
|
dest: "Händel -- Halle🎆lujah",
|
||||||
url: null,
|
url: null,
|
||||||
unsafeUrl: undefined,
|
unsafeUrl: undefined,
|
||||||
@ -1562,6 +1564,7 @@ describe("api", function () {
|
|||||||
|
|
||||||
expect(outline[1]).toEqual({
|
expect(outline[1]).toEqual({
|
||||||
action: "PrevPage",
|
action: "PrevPage",
|
||||||
|
attachment: undefined,
|
||||||
dest: null,
|
dest: null,
|
||||||
url: null,
|
url: null,
|
||||||
unsafeUrl: undefined,
|
unsafeUrl: undefined,
|
||||||
@ -1588,6 +1591,7 @@ describe("api", function () {
|
|||||||
|
|
||||||
expect(outline[0]).toEqual({
|
expect(outline[0]).toEqual({
|
||||||
action: null,
|
action: null,
|
||||||
|
attachment: undefined,
|
||||||
dest: null,
|
dest: null,
|
||||||
url: null,
|
url: null,
|
||||||
unsafeUrl: undefined,
|
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 () {
|
it("gets text content", async function () {
|
||||||
const defaultPromise = page.getTextContent();
|
const defaultPromise = page.getTextContent();
|
||||||
const parametersPromise = page.getTextContent({
|
const parametersPromise = page.getTextContent({
|
||||||
|
@ -636,6 +636,7 @@ const PDFViewerApplication = {
|
|||||||
container: appConfig.sidebar.outlineView,
|
container: appConfig.sidebar.outlineView,
|
||||||
eventBus,
|
eventBus,
|
||||||
linkService: pdfLinkService,
|
linkService: pdfLinkService,
|
||||||
|
downloadManager,
|
||||||
});
|
});
|
||||||
|
|
||||||
this.pdfAttachmentViewer = new PDFAttachmentViewer({
|
this.pdfAttachmentViewer = new PDFAttachmentViewer({
|
||||||
|
@ -20,8 +20,9 @@ import { SidebarView } from "./ui_utils.js";
|
|||||||
/**
|
/**
|
||||||
* @typedef {Object} PDFOutlineViewerOptions
|
* @typedef {Object} PDFOutlineViewerOptions
|
||||||
* @property {HTMLDivElement} container - The viewer element.
|
* @property {HTMLDivElement} container - The viewer element.
|
||||||
* @property {IPDFLinkService} linkService - The navigation/linking service.
|
|
||||||
* @property {EventBus} eventBus - The application event bus.
|
* @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) {
|
constructor(options) {
|
||||||
super(options);
|
super(options);
|
||||||
this.linkService = options.linkService;
|
this.linkService = options.linkService;
|
||||||
|
this.downloadManager = options.downloadManager;
|
||||||
|
|
||||||
this.eventBus._on("toggleoutlinetree", this._toggleAllTreeItems.bind(this));
|
this.eventBus._on("toggleoutlinetree", this._toggleAllTreeItems.bind(this));
|
||||||
this.eventBus._on(
|
this.eventBus._on(
|
||||||
@ -109,7 +111,10 @@ class PDFOutlineViewer extends BaseTreeViewer {
|
|||||||
/**
|
/**
|
||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
_bindLink(element, { url, newWindow, action, dest, setOCGState }) {
|
_bindLink(
|
||||||
|
element,
|
||||||
|
{ url, newWindow, action, attachment, dest, setOCGState }
|
||||||
|
) {
|
||||||
const { linkService } = this;
|
const { linkService } = this;
|
||||||
|
|
||||||
if (url) {
|
if (url) {
|
||||||
@ -124,6 +129,18 @@ class PDFOutlineViewer extends BaseTreeViewer {
|
|||||||
};
|
};
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (attachment) {
|
||||||
|
element.href = linkService.getAnchorUrl("");
|
||||||
|
element.onclick = () => {
|
||||||
|
this.downloadManager.openOrDownloadData(
|
||||||
|
element,
|
||||||
|
attachment.content,
|
||||||
|
attachment.filename
|
||||||
|
);
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (setOCGState) {
|
if (setOCGState) {
|
||||||
element.href = linkService.getAnchorUrl("");
|
element.href = linkService.getAnchorUrl("");
|
||||||
element.onclick = () => {
|
element.onclick = () => {
|
||||||
|
Loading…
Reference in New Issue
Block a user