get serializable() {
    function nodeToSerializable(node, parent, level = 0) {
      if (level > MAX_DEPTH) {
        warn("StructTree too deep to be fully serialized.");
        return;
      }
      const obj = Object.create(null);
      obj.role = node.role;
      obj.children = [];
      parent.children.push(obj);
      let alt = node.dict.get("Alt");
      if (typeof alt !== "string") {
        alt = node.dict.get("ActualText");
      }
      if (typeof alt === "string") {
        obj.alt = stringToPDFString(alt);
      }

      const a = node.dict.get("A");
      if (a instanceof Dict) {
        const bbox = lookupNormalRect(a.getArray("BBox"), null);
        if (bbox) {
          obj.bbox = bbox;
        } else {
          const width = a.get("Width");
          const height = a.get("Height");
          if (
            typeof width === "number" &&
            width > 0 &&
            typeof height === "number" &&
            height > 0
          ) {
            obj.bbox = [0, 0, width, height];
          }
        }
        // TODO: If the bbox is not available, we should try to get it from
        // the content stream.
        // For example when rendering on the canvas the commands between the
        // beginning and the end of the marked-content sequence, we can
        // compute the overall bbox.
      }

      const lang = node.dict.get("Lang");
      if (typeof lang === "string") {
        obj.lang = stringToPDFString(lang);
      }

      for (const kid of node.kids) {
        const kidElement =
          kid.type === StructElementType.ELEMENT ? kid.parentNode : null;
        if (kidElement) {
          nodeToSerializable(kidElement, obj, level + 1);
          continue;
        } else if (
          kid.type === StructElementType.PAGE_CONTENT ||
          kid.type === StructElementType.STREAM_CONTENT
        ) {
          obj.children.push({
            type: "content",
            id: `p${kid.pageObjId}_mc${kid.mcid}`,
          });
        } else if (kid.type === StructElementType.OBJECT) {
          obj.children.push({
            type: "object",
            id: kid.refObjId,
          });
        } else if (kid.type === StructElementType.ANNOTATION) {
          obj.children.push({
            type: "annotation",
            id: `${AnnotationPrefix}${kid.refObjId}`,
          });
        }
      }
    }

    const root = Object.create(null);
    root.children = [];
    root.role = "Root";
    for (const child of this.nodes) {
      if (!child) {
        continue;
      }
      nodeToSerializable(child, root);
    }
    return root;
  }
Convert the tree structure into a simplified object literal that can
be sent to the main thread.
@returns {Object}