static parseDestDictionary()

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;
      }
    }
  }