in src/core/catalog.js [1538:1767]
static parseDestDictionary({
destDict,
resultObj,
docBaseUrl = null,
docAttachments = null,
}) {
if (!(destDict instanceof Dict)) {
warn("parseDestDictionary: `destDict` must be a dictionary.");
return;
}
let action = destDict.get("A"),
url,
dest;
if (!(action instanceof Dict)) {
if (destDict.has("Dest")) {
// A /Dest entry should *only* contain a Name or an Array, but some bad
// PDF generators ignore that and treat it as an /A entry.
action = destDict.get("Dest");
} else {
action = destDict.get("AA");
if (action instanceof Dict) {
if (action.has("D")) {
// MouseDown
action = action.get("D");
} else if (action.has("U")) {
// MouseUp
action = action.get("U");
}
}
}
}
if (action instanceof Dict) {
const actionType = action.get("S");
if (!(actionType instanceof Name)) {
warn("parseDestDictionary: Invalid type in Action dictionary.");
return;
}
const actionName = actionType.name;
switch (actionName) {
case "ResetForm":
const flags = action.get("Flags");
const include = ((typeof flags === "number" ? flags : 0) & 1) === 0;
const fields = [];
const refs = [];
for (const obj of action.get("Fields") || []) {
if (obj instanceof Ref) {
refs.push(obj.toString());
} else if (typeof obj === "string") {
fields.push(stringToPDFString(obj));
}
}
resultObj.resetForm = { fields, refs, include };
break;
case "URI":
url = action.get("URI");
if (url instanceof Name) {
// Some bad PDFs do not put parentheses around relative URLs.
url = "/" + url.name;
}
break;
case "GoTo":
dest = action.get("D");
break;
case "Launch":
// We neither want, nor can, support arbitrary 'Launch' actions.
// However, in practice they are mostly used for linking to other PDF
// files, which we thus attempt to support (utilizing `docBaseUrl`).
/* falls through */
case "GoToR":
const urlDict = action.get("F");
if (urlDict instanceof Dict) {
const fs = new FileSpec(
urlDict,
/* xref = */ null,
/* skipContent = */ true
);
const { rawFilename } = fs.serializable;
url = rawFilename;
} else if (typeof urlDict === "string") {
url = urlDict;
}
// NOTE: the destination is relative to the *remote* document.
const remoteDest = fetchRemoteDest(action);
if (remoteDest && typeof url === "string") {
// NOTE: We don't use the `updateUrlHash` function here, since
// the `createValidAbsoluteUrl` function (see below) already
// handles parsing and validation of the final URL.
url = /* baseUrl = */ url.split("#", 1)[0] + "#" + remoteDest;
}
// The 'NewWindow' property, equal to `LinkTarget.BLANK`.
const newWindow = action.get("NewWindow");
if (typeof newWindow === "boolean") {
resultObj.newWindow = newWindow;
}
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, /* keepEscapeSequence = */ true)
];
}
}
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.`);
}
break;
case "Named":
const namedAction = action.get("N");
if (namedAction instanceof Name) {
resultObj.action = namedAction.name;
}
break;
case "SetOCGState":
const state = action.get("State");
const preserveRB = action.get("PreserveRB");
if (!Array.isArray(state) || state.length === 0) {
break;
}
const stateArr = [];
for (const elem of state) {
if (elem instanceof Name) {
switch (elem.name) {
case "ON":
case "OFF":
case "Toggle":
stateArr.push(elem.name);
break;
}
} else if (elem instanceof Ref) {
stateArr.push(elem.toString());
}
}
if (stateArr.length !== state.length) {
break; // Some of the original entries are not valid.
}
resultObj.setOCGState = {
state: stateArr,
preserveRB: typeof preserveRB === "boolean" ? preserveRB : true,
};
break;
case "JavaScript":
const jsAction = action.get("JS");
let js;
if (jsAction instanceof BaseStream) {
js = jsAction.getString();
} else if (typeof jsAction === "string") {
js = jsAction;
}
const jsURL =
js &&
recoverJsURL(
stringToPDFString(js, /* keepEscapeSequence = */ true)
);
if (jsURL) {
url = jsURL.url;
resultObj.newWindow = jsURL.newWindow;
break;
}
/* falls through */
default:
if (actionName === "JavaScript" || actionName === "SubmitForm") {
// Don't bother the user with a warning for actions that require
// scripting support, since those will be handled separately.
break;
}
warn(`parseDestDictionary - unsupported action: "${actionName}".`);
break;
}
} else if (destDict.has("Dest")) {
// Simple destination.
dest = destDict.get("Dest");
}
if (typeof url === "string") {
const absoluteUrl = createValidAbsoluteUrl(url, docBaseUrl, {
addDefaultProtocol: true,
tryConvertEncoding: true,
});
if (absoluteUrl) {
resultObj.url = absoluteUrl.href;
}
resultObj.unsafeUrl = url;
}
if (dest) {
if (dest instanceof Name) {
dest = dest.name;
}
if (typeof dest === "string") {
resultObj.dest = stringToPDFString(
dest,
/* keepEscapeSequence = */ true
);
} else if (isValidExplicitDest(dest)) {
resultObj.dest = dest;
}
}
}