/*
 * Copyright 2018 Google Inc.
 *
 * Use of this source code is governed by a BSD-style license that can be
 * found in the LICENSE file.
 */

#include "src/pdf/SkPDFTag.h"

#include "include/core/SkPoint.h"
#include "include/core/SkScalar.h"
#include "include/private/base/SkAssert.h"
#include "include/private/base/SkDebug.h"
#include "include/private/base/SkTo.h"
#include "src/base/SkZip.h"
#include "src/pdf/SkPDFDocumentPriv.h"

#include <algorithm>
#include <memory>
#include <utility>
#include <vector>

using namespace skia_private;

// The /StructTreeRoot /ParentTree is a number tree which consists of one entry for each page
// (Page::StructParents -> StructElemRef[mcid]) and entries for individual content items
// (?::StructParent -> StructElemRef).
//
// This is implemented with page entries getting consecutive keys starting at 0. Since the total
// number of pages in the document is not known when content items are processed, start the key for
// content items with a large number, which effectively becomes the maximum number of pages in a
// PDF we can handle.
const int kFirstContentItemStructParentKey = 100000;

namespace {
struct Location {
    SkPoint fPoint{SK_ScalarNaN, SK_ScalarNaN};
    unsigned fPageIndex{0};

    void accumulate(Location const& child) {
        if (!child.fPoint.isFinite()) {
            return;
        }
        if (!fPoint.isFinite()) {
            *this = child;
            return;
        }
        if (child.fPageIndex < fPageIndex) {
            *this = child;
            return;
        }
        if (child.fPageIndex == fPageIndex) {
            fPoint.fX = std::min(child.fPoint.fX, fPoint.fX);
            fPoint.fY = std::max(child.fPoint.fY, fPoint.fY); // PDF y-up
            return;
        }
    }
};
} // namespace

struct SkPDFStructElem {
    // Structure elements (/StructElem) may have an element identifier (/ID) which is a byte string.
    // Element identifiers are used by attributes (/StructElem /A) to refer to structure elements.
    // The mapping from element identifier to structure element is emitted in the /IDTree.
    // Element identifiers are stored as an integer (elemId) and this method creates a byte string.
    // Since the /IDTree is a name tree the element identifier keys must be ordered;
    // the digits are zero-padded so that lexicographic order matches numeric order.
    static SkString StringFromElemId(int elemId) {
        SkString elemIdString;
        elemIdString.printf("node%08d", elemId);
        return elemIdString;
    }

    SkPDFStructElem* fParent = nullptr;
    SkSpan<SkPDFStructElem> fChildren;
    struct MarkedContentInfo {
        Location fLocation;
        int fMcid;
    };
    TArray<MarkedContentInfo> fMarkedContent;
    int fElemId = 0;
    bool fWantTitle = false;
    bool fUsed = false;
    bool fUsedInIDTree = false;
    SkString fStructType;
    SkString fTitle;
    SkString fAlt;
    SkString fLang;
    SkPDFIndirectReference fRef;
    std::unique_ptr<SkPDFArray> fAttributes;
    std::vector<int> fAttributeElemIds;
    struct ContentItemInfo {
        unsigned fPageIndex;
        SkPDFIndirectReference fContentItemRef;
    };
    std::vector<ContentItemInfo> fContentItems;

    void setUsed(const THashMap<int, SkPDFStructElem*>& structElemForElemId) {
        if (fUsed) {
            return;
        }
        // First to avoid possible cycles.
        fUsed = true;
        // Any StructElem referenced by an attribute is used.
        for (int elemId : fAttributeElemIds) {
            SkPDFStructElem** structElemPtr = structElemForElemId.find(elemId);
            if (!structElemPtr) {
                continue;
            }
            SkPDFStructElem* structElem = *structElemPtr;
            SkASSERT(structElem);
            structElem->setUsed(structElemForElemId);
            structElem->fUsedInIDTree = true;
        }
        // The parent StructElem is used.
        if (fParent) {
            fParent->setUsed(structElemForElemId);
        }
    }

    SkPDFIndirectReference emitStructElem(SkPDFIndirectReference parent,
                                          std::vector<SkPDFStructTree::IDTreeEntry>* idTree,
                                          SkPDFDocument* doc);
};

SkPDF::AttributeList::AttributeList() = default;

SkPDF::AttributeList::~AttributeList() = default;

void SkPDF::AttributeList::appendInt(const char* owner, const char* name, int value) {
    if (!fAttrs) {
        fAttrs = SkPDFMakeArray();
    }
    std::unique_ptr<SkPDFDict> attrDict = SkPDFMakeDict();
    attrDict->insertName("O", owner);
    attrDict->insertInt(name, value);
    fAttrs->appendObject(std::move(attrDict));
}

void SkPDF::AttributeList::appendFloat(const char* owner, const char* name, float value) {
    if (!fAttrs) {
        fAttrs = SkPDFMakeArray();
    }
    std::unique_ptr<SkPDFDict> attrDict = SkPDFMakeDict();
    attrDict->insertName("O", owner);
    attrDict->insertScalar(name, value);
    fAttrs->appendObject(std::move(attrDict));
}

void SkPDF::AttributeList::appendName(const char* owner, const char* name, const char* value) {
    if (!fAttrs) {
        fAttrs = SkPDFMakeArray();
    }
    std::unique_ptr<SkPDFDict> attrDict = SkPDFMakeDict();
    attrDict->insertName("O", owner);
    attrDict->insertName(name, value);
    fAttrs->appendObject(std::move(attrDict));
}

void SkPDF::AttributeList::appendFloatArray(const char* owner, const char* name,
                                            const std::vector<float>& value) {
    if (!fAttrs) {
        fAttrs = SkPDFMakeArray();
    }
    std::unique_ptr<SkPDFDict> attrDict = SkPDFMakeDict();
    attrDict->insertName("O", owner);
    std::unique_ptr<SkPDFArray> pdfArray = SkPDFMakeArray();
    for (float element : value) {
        pdfArray->appendScalar(element);
    }
    attrDict->insertObject(name, std::move(pdfArray));
    fAttrs->appendObject(std::move(attrDict));
}

void SkPDF::AttributeList::appendNodeIdArray(const char* owner, const char* name,
                                             const std::vector<int>& elemIds) {
    if (!fAttrs) {
        fAttrs = SkPDFMakeArray();
    }
    // Keep the element identifiers so we can mark their targets as used (and needing /ID) later.
    fElemIds.insert(fElemIds.end(), elemIds.begin(), elemIds.end());
    std::unique_ptr<SkPDFDict> attrDict = SkPDFMakeDict();
    attrDict->insertName("O", owner);
    std::unique_ptr<SkPDFArray> pdfArray = SkPDFMakeArray();
    for (int elemId : elemIds) {
        pdfArray->appendByteString(SkPDFStructElem::StringFromElemId(elemId));
    }
    attrDict->insertObject(name, std::move(pdfArray));
    fAttrs->appendObject(std::move(attrDict));
}

SkPDFStructTree::SkPDFStructTree(SkPDF::StructureElementNode* node,
                                 SkPDF::Metadata::Outline outline)
    : fArena(4 * sizeof(SkPDFStructElem))
{
    if (node) {
        fRoot = fArena.make<SkPDFStructElem>();
        fOutline = outline;
        this->move(*node, fRoot, false);
    }
}

SkPDFStructTree::~SkPDFStructTree() = default;

void SkPDFStructTree::move(SkPDF::StructureElementNode& node,
                           SkPDFStructElem* structElem,
                           bool wantTitle) {
    constexpr bool kDumpStructureTree = false;
    if constexpr (kDumpStructureTree) {
        int indent = 0;
        for (SkPDFStructElem* parent = structElem->fParent; parent; parent = parent->fParent) {
            ++indent;
        }
        SkDebugf("%.*s %d %s\n", indent, "            ", node.fNodeId, node.fTypeString.c_str());
    }

    structElem->fElemId = node.fNodeId;
    fStructElemForElemId.set(structElem->fElemId, structElem);

    // Accumulate title text, need to be in sync with create_outline_from_headers
    const SkString& type = node.fTypeString;
    wantTitle |= fOutline == SkPDF::Metadata::Outline::StructureElementHeaders &&
                 type.size() == 2 && type[0] == 'H' && '1' <= type[1] && type[1] <= '6';
    structElem->fWantTitle = wantTitle;

    static SkString nonStruct("NonStruct");
    structElem->fStructType = node.fTypeString.isEmpty() ? nonStruct : std::move(node.fTypeString);
    structElem->fAlt = std::move(node.fAlt);
    structElem->fLang = std::move(node.fLang);

    size_t childCount = node.fChildVector.size();
    structElem->fChildren = SkSpan(fArena.makeArray<SkPDFStructElem>(childCount), childCount);
    for (auto&& [nodeChild, elemChild] : SkMakeZip(node.fChildVector, structElem->fChildren)) {
        elemChild.fParent = structElem;
        this->move(*nodeChild, &elemChild, wantTitle);
    }

    structElem->fAttributes = std::move(node.fAttributes.fAttrs);
    structElem->fAttributeElemIds = std::move(node.fAttributes.fElemIds);
}

int SkPDFStructTree::Mark::elemId() const {
    return fStructElem ? fStructElem->fElemId : 0;
}

SkString SkPDFStructTree::Mark::structType() const {
    SkASSERT(bool(*this));
    return fStructElem->fStructType;
}

int SkPDFStructTree::Mark::mcid() const {
    return fStructElem ? fStructElem->fMarkedContent[fMarkIndex].fMcid : -1;
}

void SkPDFStructTree::Mark::accumulate(SkPoint point) {
    SkASSERT(bool(*this));
    Location& location = fStructElem->fMarkedContent[fMarkIndex].fLocation;
    return location.accumulate({{point}, location.fPageIndex});
}

auto SkPDFStructTree::createMarkForElemId(int elemId, unsigned pageIndex) -> Mark {
    if (!fRoot) {
        return Mark();
    }
    SkPDFStructElem** structElemPtr = fStructElemForElemId.find(elemId);
    if (!structElemPtr) {
        return Mark();
    }
    SkPDFStructElem* structElem = *structElemPtr;
    SkASSERT(structElem);

    structElem->setUsed(fStructElemForElemId);

    while (SkToUInt(fStructElemForMcidForPage.size()) < pageIndex + 1) {
        fStructElemForMcidForPage.push_back();
    }
    TArray<SkPDFStructElem*>& structElemForMcid = fStructElemForMcidForPage[pageIndex];
    int mcid = structElemForMcid.size();
    SkASSERT(structElem->fMarkedContent.empty() ||
             structElem->fMarkedContent.back().fLocation.fPageIndex <= pageIndex);
    structElem->fMarkedContent.push_back({{{SK_ScalarNaN, SK_ScalarNaN}, pageIndex}, mcid});
    structElemForMcid.push_back(structElem);
    return Mark(structElem, structElem->fMarkedContent.size() - 1);
}

int SkPDFStructTree::createStructParentKeyForElemId(int elemId,
                                                    SkPDFIndirectReference contentItemRef,
                                                    unsigned pageIndex) {
    if (!fRoot) {
        return -1;
    }
    SkPDFStructElem** structElemPtr = fStructElemForElemId.find(elemId);
    if (!structElemPtr) {
        return -1;
    }
    SkPDFStructElem* structElem = *structElemPtr;
    SkASSERT(structElem);

    structElem->setUsed(fStructElemForElemId);

    SkPDFStructElem::ContentItemInfo contentItemInfo = {pageIndex, contentItemRef};
    structElem->fContentItems.push_back(contentItemInfo);

    int structParentKey = kFirstContentItemStructParentKey + fStructElemForContentItem.size();
    fStructElemForContentItem.push_back(structElem);
    return structParentKey;
}

SkPDFIndirectReference SkPDFStructElem::emitStructElem(
        SkPDFIndirectReference parent,
        std::vector<SkPDFStructTree::IDTreeEntry>* idTree,
        SkPDFDocument* doc)
{
    fRef = doc->reserveRef();

    SkPDFDict dict("StructElem");
    dict.insertName("S", fStructType);

    if (!fAlt.isEmpty()) {
        dict.insertTextString("Alt", fAlt);
    }
    if (!fLang.isEmpty()) {
        dict.insertTextString("Lang", fLang);
    }
    dict.insertRef("P", parent);

    { // K
        std::unique_ptr<SkPDFArray> kids(new SkPDFOptionalArray());
        for (auto&& child : fChildren) {
            if (child.fUsed) {
                kids->appendRef(child.emitStructElem(fRef, idTree, doc));
            }
        }
        if (!fMarkedContent.empty()) {
            // Use the mode page as /Pg and use integer mcid for marks on that page.
            // SkPDFStructElem::fMarkedContent is already sorted by page, since it is append only in
            // createMarkForElemId where pageIndex is the monotonically increasing current page.
            size_t longestRun = 0;
            unsigned longestPage = 0;
            size_t currentRun = 0;
            unsigned currentPage = 0;
            for (const SkPDFStructElem::MarkedContentInfo& info : fMarkedContent) {
                unsigned thisPage = info.fLocation.fPageIndex;
                if (currentPage != thisPage) {
                    SkASSERT(currentPage < thisPage);
                    currentPage = thisPage;
                    currentRun = 0;
                }
                ++currentRun;
                if (longestRun < currentRun) {
                    longestRun = currentRun;
                    longestPage = currentPage;
                }
            }
            for (const SkPDFStructElem::MarkedContentInfo& info : fMarkedContent) {
                if (info.fLocation.fPageIndex == longestPage) {
                    kids->appendInt(info.fMcid);
                } else {
                    std::unique_ptr<SkPDFDict> mcr = SkPDFMakeDict("MCR");
                    mcr->insertRef("Pg", doc->getPage(info.fLocation.fPageIndex));
                    mcr->insertInt("MCID", info.fMcid);
                    kids->appendObject(std::move(mcr));
                }
            }
            dict.insertRef("Pg", doc->getPage(longestPage));
        }
        for (const SkPDFStructElem::ContentItemInfo& contentItemInfo : fContentItems) {
            std::unique_ptr<SkPDFDict> contentItemDict = SkPDFMakeDict("OBJR");
            contentItemDict->insertRef("Obj", contentItemInfo.fContentItemRef);
            contentItemDict->insertRef("Pg", doc->getPage(contentItemInfo.fPageIndex));
            kids->appendObject(std::move(contentItemDict));
        }
        dict.insertObject("K", std::move(kids));
    }

    if (fAttributes) {
        dict.insertObject("A", std::move(fAttributes));
    }

    // If this StructElem ID was referenced, add /ID and add it to the IDTree.
    if (fUsedInIDTree) {
        dict.insertByteString("ID", SkPDFStructElem::StringFromElemId(fElemId));
        idTree->push_back({fElemId, fRef});
    }

    return doc->emit(dict, fRef);
}

void SkPDFStructTree::addStructElemTitle(int elemId, SkSpan<const char> title) {
    if (!fRoot) {
        return;
    }
    SkPDFStructElem** structElemPtr = fStructElemForElemId.find(elemId);
    if (!structElemPtr) {
        return;
    }
    SkPDFStructElem* structElem = *structElemPtr;
    SkASSERT(structElem);

    if (structElem->fWantTitle) {
        structElem->fTitle.append(title.data(), title.size());
        // Arbitrary cutoff for size.
        if (structElem->fTitle.size() > 1023) {
            structElem->fWantTitle = false;
        }
    }
}

SkPDFIndirectReference SkPDFStructTree::emitStructTreeRoot(SkPDFDocument* doc) const {
    if (!fRoot || !fRoot->fUsed) {
        return SkPDFIndirectReference();
    }

    SkPDFIndirectReference structTreeRootRef = doc->reserveRef();

    unsigned pageCount = SkToUInt(doc->pageCount());

    // Build the StructTreeRoot.
    SkPDFDict structTreeRoot("StructTreeRoot");
    std::vector<IDTreeEntry> idTree;
    structTreeRoot.insertRef("K", fRoot->emitStructElem(structTreeRootRef, &idTree, doc));
    structTreeRoot.insertInt("ParentTreeNextKey", SkToInt(pageCount));

    // Build the parent tree, a number tree which consists of two things:
    // For each page:
    //   key: Page::StructParents
    //   value: array of structure element ref indexed by the page's marked-content identifiers
    // For each content item (usually an annotation)
    //   key: ?::StructParent
    //   value: structure element ref
    SkPDFDict parentTree("ParentTree");
    auto parentTreeNums = SkPDFMakeArray();

    // First, one entry per page.
    SkASSERT(SkToUInt(fStructElemForMcidForPage.size()) <= pageCount);
    for (int j = 0; j < fStructElemForMcidForPage.size(); ++j) {
        const TArray<SkPDFStructElem*>& structElemForMcid = fStructElemForMcidForPage[j];
        SkPDFArray structElemForMcidArray;
        for (SkPDFStructElem* structElem : structElemForMcid) {
            SkASSERT(structElem->fRef);
            structElemForMcidArray.appendRef(structElem->fRef);
        }
        parentTreeNums->appendInt(j); // /Page /StructParents
        parentTreeNums->appendRef(doc->emit(structElemForMcidArray));
    }

    // Then, one entry per content item.
    for (int j = 0; j < fStructElemForContentItem.size(); ++j) {
        int structParentKey = kFirstContentItemStructParentKey + j;
        SkPDFStructElem* structElem = fStructElemForContentItem[j];
        parentTreeNums->appendInt(structParentKey); // /<content-item> /StructParent
        parentTreeNums->appendRef(structElem->fRef);
    }

    parentTree.insertObject("Nums", std::move(parentTreeNums));
    structTreeRoot.insertRef("ParentTree", doc->emit(parentTree));

    // Build the IDTree, a mapping from every unique element identifier byte string to
    // a reference to its corresponding structure element.
    if (!idTree.empty()) {
        std::sort(idTree.begin(), idTree.end(),
                  [](const IDTreeEntry& a, const IDTreeEntry& b) {
                    return a.elemId < b.elemId;
                  });

        SkPDFDict idTreeLeaf;
        auto limits = SkPDFMakeArray();
        SkString lowestElemIdString = SkPDFStructElem::StringFromElemId(idTree.begin()->elemId);
        limits->appendByteString(lowestElemIdString);
        SkString highestElemIdString = SkPDFStructElem::StringFromElemId(idTree.rbegin()->elemId);
        limits->appendByteString(highestElemIdString);
        idTreeLeaf.insertObject("Limits", std::move(limits));
        auto names = SkPDFMakeArray();
        for (const IDTreeEntry& entry : idTree) {
            names->appendByteString(SkPDFStructElem::StringFromElemId(entry.elemId));
            names->appendRef(entry.structElemRef);
        }
        idTreeLeaf.insertObject("Names", std::move(names));
        auto idTreeKids = SkPDFMakeArray();
        idTreeKids->appendRef(doc->emit(idTreeLeaf));

        SkPDFDict idTreeRoot;
        idTreeRoot.insertObject("Kids", std::move(idTreeKids));
        structTreeRoot.insertRef("IDTree", doc->emit(idTreeRoot));
    }

    return doc->emit(structTreeRoot, structTreeRootRef);
}

namespace header_outline {
namespace {
struct Entry {
    struct Content {
        SkString fText;
        Location fLocation;
        void accumulate(Content const& child) {
            fText += child.fText;
            fLocation.accumulate(child.fLocation);
        }
    };

    Content fContent;
    int fHeaderLevel;
    SkPDFIndirectReference fStructureRef;
    SkPDFIndirectReference fRef = {};
    std::vector<Entry> fChildren = {};
    size_t fDescendentsEmitted = 0;

    void setAllRefs(SkPDFDocument* const doc, SkPDFIndirectReference ref) {
        fRef = ref;
        for (auto&& child : fChildren) {
            child.setAllRefs(doc, doc->reserveRef());
        }
    }

    void emitDescendents(SkPDFDocument* const doc) {
        fDescendentsEmitted = fChildren.size();
        for (size_t i = 0; i < fChildren.size(); ++i) {
            auto&& child = fChildren[i];
            child.emitDescendents(doc);
            fDescendentsEmitted += child.fDescendentsEmitted;

            SkPDFDict entry;
            entry.insertTextString("Title", child.fContent.fText);

            auto destination = SkPDFMakeArray();
            destination->appendRef(doc->getPage(child.fContent.fLocation.fPageIndex));
            destination->appendName("XYZ");
            destination->appendScalar(child.fContent.fLocation.fPoint.x());
            destination->appendScalar(child.fContent.fLocation.fPoint.y());
            destination->appendInt(0);
            entry.insertObject("Dest", std::move(destination));

            entry.insertRef("Parent", fRef);
            if (child.fStructureRef) {
                entry.insertRef("SE", child.fStructureRef);
            }
            if (0 < i) {
                entry.insertRef("Prev", fChildren[i-1].fRef);
            }
            if (i < fChildren.size()-1) {
                entry.insertRef("Next", fChildren[i+1].fRef);
            }
            if (!child.fChildren.empty()) {
                entry.insertRef("First", child.fChildren.front().fRef);
                entry.insertRef("Last", child.fChildren.back().fRef);
                entry.insertInt("Count", child.fDescendentsEmitted);
            }
            doc->emit(entry, child.fRef);
        }
    }
};

Entry::Content create_header_content(SkPDFStructElem* const structElem) {
    SkString text;
    if (!structElem->fTitle.isEmpty()) {
        text = structElem->fTitle;
    } else if (!structElem->fAlt.isEmpty()) {
        text = structElem->fAlt;
    }

    // The uppermost/leftmost point on the earliest page of this StructElem's marks.
    Location structElemLocation;
    for (auto&& mark : structElem->fMarkedContent) {
        structElemLocation.accumulate(mark.fLocation);
    }

    Entry::Content content{std::move(text), std::move(structElemLocation)};

    // Accumulate children
    for (auto&& child : structElem->fChildren) {
        if (child.fUsed) {
            content.accumulate(create_header_content(&child));
        }
    }
    return content;
}

void make(SkPDFDocument* const doc, SkPDFStructElem* const structElem, STArray<7, Entry*>& stack) {
    const SkString& type = structElem->fStructType;
    if (type.size() == 2 && type[0] == 'H' && '1' <= type[1] && type[1] <= '6') {
        int level = type[1] - '0';
        while (level <= stack.back()->fHeaderLevel) {
            stack.pop_back();
        }
        Entry::Content content = create_header_content(structElem);
        if (!content.fText.isEmpty()) {
            Entry e{std::move(content), level, structElem->fRef};
            stack.push_back(&stack.back()->fChildren.emplace_back(std::move(e)));
            return;
        }
    }

    for (auto&& child : structElem->fChildren) {
        if (child.fUsed) {
            make(doc, &child, stack);
        }
    }
}
} // namespace
} // namespace header_outline

namespace structelem_outline {
namespace {
struct Entry {
    size_t fDescendantCount = 0;
    Location fLocation;
    void accumulate(Entry const& child) {
        fDescendantCount += child.fDescendantCount;
        fLocation.accumulate(child.fLocation);
    }
};
Entry emit(SkPDFDocument* const doc,
           SkPDFStructElem* const structElem,
           SkPDFIndirectReference const parentRef,
           SkPDFIndirectReference const prevSiblingRef,
           SkPDFIndirectReference const selfRef,
           SkPDFIndirectReference const nextSiblingRef) {
    Entry self;

    // Emit any child entries.
    STArray<20, SkPDFIndirectReference> childRefs;
    for (auto&& child : structElem->fChildren) {
        if (!child.fUsed) {
            continue;
        }
        childRefs.emplace_back(doc->reserveRef());
    }
    int childRefsIndex = 0;
    SkPDFIndirectReference prevChildRef; // Starts out as "none".
    childRefs.emplace_back(); // Put an extra "none" on the end for the last "next".
    for (auto&& child : structElem->fChildren) {
        if (!child.fUsed) {
            continue;
        }
        SkPDFIndirectReference currChildRef = childRefs[childRefsIndex];
        SkPDFIndirectReference nextChildRef = childRefs[childRefsIndex+1];
        self.accumulate(emit(doc, &child, selfRef, prevChildRef, currChildRef, nextChildRef));
        prevChildRef = currChildRef;
        ++childRefsIndex;
    }
    childRefs.pop_back(); // Remove the "none" on the end.

    // Emit self entry.
    SkPDFDict entry;
    if (!structElem->fTitle.isEmpty()) {
        entry.insertTextString("Title", structElem->fTitle);
    } else if (!structElem->fAlt.isEmpty()) {
        entry.insertTextString("Title", structElem->fAlt);
    } else {
        entry.insertTextString("Title", structElem->fStructType);
    }

    // The uppermost/leftmost point on the earliest page of this structure element's marks.
    Location structElemLocation;
    for (auto&& mark : structElem->fMarkedContent) {
        structElemLocation.accumulate(mark.fLocation);
    }
    if (structElemLocation.fPoint.isFinite()) {
        auto destination = SkPDFMakeArray();
        destination->appendRef(doc->getPage(structElemLocation.fPageIndex));
        destination->appendName("XYZ");
        destination->appendScalar(structElemLocation.fPoint.x());
        destination->appendScalar(structElemLocation.fPoint.y());
        destination->appendInt(0);
        entry.insertObject("Dest", std::move(destination));

        self.fLocation.accumulate(structElemLocation);
    } else if (self.fLocation.fPoint.isFinite()) {
        // The uppermost/leftmost point on the earliest page of any child.
        auto destination = SkPDFMakeArray();
        destination->appendRef(doc->getPage(self.fLocation.fPageIndex));
        destination->appendName("XYZ");
        destination->appendScalar(self.fLocation.fPoint.x());
        destination->appendScalar(self.fLocation.fPoint.y());
        destination->appendInt(0);
        entry.insertObject("Dest", std::move(destination));
    }
    if (structElem->fRef) {
        entry.insertRef("SE", structElem->fRef);
    }
    entry.insertRef("Parent", parentRef);
    if (prevSiblingRef) {
        entry.insertRef("Prev", prevSiblingRef);
    }
    if (nextSiblingRef) {
        entry.insertRef("Next", nextSiblingRef);
    }
    if (!childRefs.empty()) {
        entry.insertRef("First", childRefs.front());
        entry.insertRef("Last", childRefs.back());
        entry.insertInt("Count", self.fDescendantCount);
    }
    doc->emit(entry, selfRef);
    ++self.fDescendantCount;
    return self;
}
} // namespace
} // namespace structelem_outline

SkPDFIndirectReference SkPDFStructTree::makeOutline(SkPDFDocument* doc) const {
    if (!fRoot || !fRoot->fUsed || fOutline == SkPDF::Metadata::Outline::None) {
        return SkPDFIndirectReference();
    }

    SkPDFIndirectReference outlineRef;
    SkPDFDict outline("Outlines");
    if (fOutline == SkPDF::Metadata::Outline::StructureElements) {
        outlineRef = doc->reserveRef();
        SkPDFIndirectReference entryRef = doc->reserveRef();
        SkPDFIndirectReference none;
        structelem_outline::Entry entry = structelem_outline::emit(doc, fRoot, outlineRef,
                                                                   none, entryRef, none);
        outline.insertRef("First", entryRef);
        outline.insertRef("Last", entryRef);
        outline.insertInt("Count", entry.fDescendantCount);
    } else {
        STArray<7, header_outline::Entry*> stack;
        header_outline::Entry top{{SkString(), Location()}, 0, {}};
        stack.push_back(&top);
        header_outline::make(doc, fRoot, stack);
        if (top.fChildren.empty()) {
            return SkPDFIndirectReference();
        }
        outlineRef = doc->reserveRef();
        top.setAllRefs(doc, outlineRef);
        top.emitDescendents(doc);
        outline.insertRef("First", top.fChildren.front().fRef);
        outline.insertRef("Last", top.fChildren.back().fRef);
        outline.insertInt("Count", top.fDescendentsEmitted);
    }

    return doc->emit(outline, outlineRef);
}

SkString SkPDFStructTree::getRootLanguage() {
    return fRoot ? fRoot->fLang : SkString();
}
