xref: /aosp_15_r20/external/skia/src/pdf/SkPDFTag.cpp (revision c8dee2aa9b3f27cf6c858bd81872bdeb2c07ed17)
1 /*
2  * Copyright 2018 Google Inc.
3  *
4  * Use of this source code is governed by a BSD-style license that can be
5  * found in the LICENSE file.
6  */
7 
8 #include "src/pdf/SkPDFTag.h"
9 
10 #include "include/core/SkPoint.h"
11 #include "include/core/SkScalar.h"
12 #include "include/private/base/SkAssert.h"
13 #include "include/private/base/SkTo.h"
14 #include "src/base/SkZip.h"
15 #include "src/pdf/SkPDFDocumentPriv.h"
16 
17 #include <algorithm>
18 #include <memory>
19 #include <utility>
20 #include <vector>
21 
22 using namespace skia_private;
23 
24 // The /StructTreeRoot /ParentTree is a number tree which consists of one entry for each page
25 // (Page::StructParents -> StructElemRef[mcid]) and entries for individual content items
26 // (?::StructParent -> StructElemRef).
27 //
28 // This is implemented with page entries getting consecutive keys starting at 0. Since the total
29 // number of pages in the document is not known when content items are processed, start the key for
30 // content items with a large number, which effectively becomes the maximum number of pages in a
31 // PDF we can handle.
32 const int kFirstContentItemStructParentKey = 100000;
33 
34 namespace {
35 struct Location {
36     SkPoint fPoint{SK_ScalarNaN, SK_ScalarNaN};
37     unsigned fPageIndex{0};
38 
accumulate__anonf09f2a830111::Location39     void accumulate(Location const& child) {
40         if (!child.fPoint.isFinite()) {
41             return;
42         }
43         if (!fPoint.isFinite()) {
44             *this = child;
45             return;
46         }
47         if (child.fPageIndex < fPageIndex) {
48             *this = child;
49             return;
50         }
51         if (child.fPageIndex == fPageIndex) {
52             fPoint.fX = std::min(child.fPoint.fX, fPoint.fX);
53             fPoint.fY = std::max(child.fPoint.fY, fPoint.fY); // PDF y-up
54             return;
55         }
56     }
57 };
58 } // namespace
59 
60 struct SkPDFStructElem {
61     // Structure elements (/StructElem) may have an element identifier (/ID) which is a byte string.
62     // Element identifiers are used by attributes (/StructElem /A) to refer to structure elements.
63     // The mapping from element identifier to structure element is emitted in the /IDTree.
64     // Element identifiers are stored as an integer (elemId) and this method creates a byte string.
65     // Since the /IDTree is a name tree the element identifier keys must be ordered;
66     // the digits are zero-padded so that lexicographic order matches numeric order.
StringFromElemIdSkPDFStructElem67     static SkString StringFromElemId(int elemId) {
68         SkString elemIdString;
69         elemIdString.printf("node%08d", elemId);
70         return elemIdString;
71     }
72 
73     SkPDFStructElem* fParent = nullptr;
74     SkSpan<SkPDFStructElem> fChildren;
75     struct MarkedContentInfo {
76         Location fLocation;
77         int fMcid;
78     };
79     TArray<MarkedContentInfo> fMarkedContent;
80     int fElemId = 0;
81     bool fWantTitle = false;
82     bool fUsed = false;
83     bool fUsedInIDTree = false;
84     SkString fStructType;
85     SkString fTitle;
86     SkString fAlt;
87     SkString fLang;
88     SkPDFIndirectReference fRef;
89     std::unique_ptr<SkPDFArray> fAttributes;
90     std::vector<int> fAttributeElemIds;
91     struct ContentItemInfo {
92         unsigned fPageIndex;
93         SkPDFIndirectReference fContentItemRef;
94     };
95     std::vector<ContentItemInfo> fContentItems;
96 
setUsedSkPDFStructElem97     void setUsed(const THashMap<int, SkPDFStructElem*>& structElemForElemId) {
98         if (fUsed) {
99             return;
100         }
101         // First to avoid possible cycles.
102         fUsed = true;
103         // Any StructElem referenced by an attribute is used.
104         for (int elemId : fAttributeElemIds) {
105             SkPDFStructElem** structElemPtr = structElemForElemId.find(elemId);
106             if (!structElemPtr) {
107                 continue;
108             }
109             SkPDFStructElem* structElem = *structElemPtr;
110             SkASSERT(structElem);
111             structElem->setUsed(structElemForElemId);
112             structElem->fUsedInIDTree = true;
113         }
114         // The parent StructElem is used.
115         if (fParent) {
116             fParent->setUsed(structElemForElemId);
117         }
118     }
119 
120     SkPDFIndirectReference emitStructElem(SkPDFIndirectReference parent,
121                                           std::vector<SkPDFStructTree::IDTreeEntry>* idTree,
122                                           SkPDFDocument* doc);
123 };
124 
125 SkPDF::AttributeList::AttributeList() = default;
126 
127 SkPDF::AttributeList::~AttributeList() = default;
128 
appendInt(const char * owner,const char * name,int value)129 void SkPDF::AttributeList::appendInt(const char* owner, const char* name, int value) {
130     if (!fAttrs) {
131         fAttrs = SkPDFMakeArray();
132     }
133     std::unique_ptr<SkPDFDict> attrDict = SkPDFMakeDict();
134     attrDict->insertName("O", owner);
135     attrDict->insertInt(name, value);
136     fAttrs->appendObject(std::move(attrDict));
137 }
138 
appendFloat(const char * owner,const char * name,float value)139 void SkPDF::AttributeList::appendFloat(const char* owner, const char* name, float value) {
140     if (!fAttrs) {
141         fAttrs = SkPDFMakeArray();
142     }
143     std::unique_ptr<SkPDFDict> attrDict = SkPDFMakeDict();
144     attrDict->insertName("O", owner);
145     attrDict->insertScalar(name, value);
146     fAttrs->appendObject(std::move(attrDict));
147 }
148 
appendName(const char * owner,const char * name,const char * value)149 void SkPDF::AttributeList::appendName(const char* owner, const char* name, const char* value) {
150     if (!fAttrs) {
151         fAttrs = SkPDFMakeArray();
152     }
153     std::unique_ptr<SkPDFDict> attrDict = SkPDFMakeDict();
154     attrDict->insertName("O", owner);
155     attrDict->insertName(name, value);
156     fAttrs->appendObject(std::move(attrDict));
157 }
158 
appendFloatArray(const char * owner,const char * name,const std::vector<float> & value)159 void SkPDF::AttributeList::appendFloatArray(const char* owner, const char* name,
160                                             const std::vector<float>& value) {
161     if (!fAttrs) {
162         fAttrs = SkPDFMakeArray();
163     }
164     std::unique_ptr<SkPDFDict> attrDict = SkPDFMakeDict();
165     attrDict->insertName("O", owner);
166     std::unique_ptr<SkPDFArray> pdfArray = SkPDFMakeArray();
167     for (float element : value) {
168         pdfArray->appendScalar(element);
169     }
170     attrDict->insertObject(name, std::move(pdfArray));
171     fAttrs->appendObject(std::move(attrDict));
172 }
173 
appendNodeIdArray(const char * owner,const char * name,const std::vector<int> & elemIds)174 void SkPDF::AttributeList::appendNodeIdArray(const char* owner, const char* name,
175                                              const std::vector<int>& elemIds) {
176     if (!fAttrs) {
177         fAttrs = SkPDFMakeArray();
178     }
179     // Keep the element identifiers so we can mark their targets as used (and needing /ID) later.
180     fElemIds.insert(fElemIds.end(), elemIds.begin(), elemIds.end());
181     std::unique_ptr<SkPDFDict> attrDict = SkPDFMakeDict();
182     attrDict->insertName("O", owner);
183     std::unique_ptr<SkPDFArray> pdfArray = SkPDFMakeArray();
184     for (int elemId : elemIds) {
185         pdfArray->appendByteString(SkPDFStructElem::StringFromElemId(elemId));
186     }
187     attrDict->insertObject(name, std::move(pdfArray));
188     fAttrs->appendObject(std::move(attrDict));
189 }
190 
SkPDFStructTree(SkPDF::StructureElementNode * node,SkPDF::Metadata::Outline outline)191 SkPDFStructTree::SkPDFStructTree(SkPDF::StructureElementNode* node,
192                                  SkPDF::Metadata::Outline outline)
193     : fArena(4 * sizeof(SkPDFStructElem))
194 {
195     if (node) {
196         fRoot = fArena.make<SkPDFStructElem>();
197         fOutline = outline;
198         this->move(*node, fRoot, false);
199     }
200 }
201 
202 SkPDFStructTree::~SkPDFStructTree() = default;
203 
move(SkPDF::StructureElementNode & node,SkPDFStructElem * structElem,bool wantTitle)204 void SkPDFStructTree::move(SkPDF::StructureElementNode& node,
205                            SkPDFStructElem* structElem,
206                            bool wantTitle) {
207     structElem->fElemId = node.fNodeId;
208     fStructElemForElemId.set(structElem->fElemId, structElem);
209 
210     // Accumulate title text, need to be in sync with create_outline_from_headers
211     const SkString& type = node.fTypeString;
212     wantTitle |= fOutline == SkPDF::Metadata::Outline::StructureElementHeaders &&
213                  type.size() == 2 && type[0] == 'H' && '1' <= type[1] && type[1] <= '6';
214     structElem->fWantTitle = wantTitle;
215 
216     static SkString nonStruct("NonStruct");
217     structElem->fStructType = node.fTypeString.isEmpty() ? nonStruct : std::move(node.fTypeString);
218     structElem->fAlt = std::move(node.fAlt);
219     structElem->fLang = std::move(node.fLang);
220 
221     size_t childCount = node.fChildVector.size();
222     structElem->fChildren = SkSpan(fArena.makeArray<SkPDFStructElem>(childCount), childCount);
223     for (auto&& [nodeChild, elemChild] : SkMakeZip(node.fChildVector, structElem->fChildren)) {
224         elemChild.fParent = structElem;
225         this->move(*nodeChild, &elemChild, wantTitle);
226     }
227 
228     structElem->fAttributes = std::move(node.fAttributes.fAttrs);
229     structElem->fAttributeElemIds = std::move(node.fAttributes.fElemIds);
230 }
231 
elemId() const232 int SkPDFStructTree::Mark::elemId() const {
233     return fStructElem ? fStructElem->fElemId : 0;
234 }
235 
structType() const236 SkString SkPDFStructTree::Mark::structType() const {
237     SkASSERT(bool(*this));
238     return fStructElem->fStructType;
239 }
240 
mcid() const241 int SkPDFStructTree::Mark::mcid() const {
242     return fStructElem ? fStructElem->fMarkedContent[fMarkIndex].fMcid : -1;
243 }
244 
accumulate(SkPoint point)245 void SkPDFStructTree::Mark::accumulate(SkPoint point) {
246     SkASSERT(bool(*this));
247     Location& location = fStructElem->fMarkedContent[fMarkIndex].fLocation;
248     return location.accumulate({{point}, location.fPageIndex});
249 }
250 
createMarkForElemId(int elemId,unsigned pageIndex)251 auto SkPDFStructTree::createMarkForElemId(int elemId, unsigned pageIndex) -> Mark {
252     if (!fRoot) {
253         return Mark();
254     }
255     SkPDFStructElem** structElemPtr = fStructElemForElemId.find(elemId);
256     if (!structElemPtr) {
257         return Mark();
258     }
259     SkPDFStructElem* structElem = *structElemPtr;
260     SkASSERT(structElem);
261 
262     structElem->setUsed(fStructElemForElemId);
263 
264     while (SkToUInt(fStructElemForMcidForPage.size()) < pageIndex + 1) {
265         fStructElemForMcidForPage.push_back();
266     }
267     TArray<SkPDFStructElem*>& structElemForMcid = fStructElemForMcidForPage[pageIndex];
268     int mcid = structElemForMcid.size();
269     SkASSERT(structElem->fMarkedContent.empty() ||
270              structElem->fMarkedContent.back().fLocation.fPageIndex <= pageIndex);
271     structElem->fMarkedContent.push_back({{{SK_ScalarNaN, SK_ScalarNaN}, pageIndex}, mcid});
272     structElemForMcid.push_back(structElem);
273     return Mark(structElem, structElem->fMarkedContent.size() - 1);
274 }
275 
createStructParentKeyForElemId(int elemId,SkPDFIndirectReference contentItemRef,unsigned pageIndex)276 int SkPDFStructTree::createStructParentKeyForElemId(int elemId,
277                                                     SkPDFIndirectReference contentItemRef,
278                                                     unsigned pageIndex) {
279     if (!fRoot) {
280         return -1;
281     }
282     SkPDFStructElem** structElemPtr = fStructElemForElemId.find(elemId);
283     if (!structElemPtr) {
284         return -1;
285     }
286     SkPDFStructElem* structElem = *structElemPtr;
287     SkASSERT(structElem);
288 
289     structElem->setUsed(fStructElemForElemId);
290 
291     SkPDFStructElem::ContentItemInfo contentItemInfo = {pageIndex, contentItemRef};
292     structElem->fContentItems.push_back(contentItemInfo);
293 
294     int structParentKey = kFirstContentItemStructParentKey + fStructElemForContentItem.size();
295     fStructElemForContentItem.push_back(structElem);
296     return structParentKey;
297 }
298 
emitStructElem(SkPDFIndirectReference parent,std::vector<SkPDFStructTree::IDTreeEntry> * idTree,SkPDFDocument * doc)299 SkPDFIndirectReference SkPDFStructElem::emitStructElem(
300         SkPDFIndirectReference parent,
301         std::vector<SkPDFStructTree::IDTreeEntry>* idTree,
302         SkPDFDocument* doc)
303 {
304     fRef = doc->reserveRef();
305 
306     SkPDFDict dict("StructElem");
307     dict.insertName("S", fStructType);
308 
309     if (!fAlt.isEmpty()) {
310         dict.insertTextString("Alt", fAlt);
311     }
312     if (!fLang.isEmpty()) {
313         dict.insertTextString("Lang", fLang);
314     }
315     dict.insertRef("P", parent);
316 
317     { // K
318         std::unique_ptr<SkPDFArray> kids = SkPDFMakeArray();
319         for (auto&& child : fChildren) {
320             if (child.fUsed) {
321                 kids->appendRef(child.emitStructElem(fRef, idTree, doc));
322             }
323         }
324         if (!fMarkedContent.empty()) {
325             // Use the mode page as /Pg and use integer mcid for marks on that page.
326             // SkPDFStructElem::fMarkedContent is already sorted by page, since it is append only in
327             // createMarkForElemId where pageIndex is the monotonically increasing current page.
328             size_t longestRun = 0;
329             unsigned longestPage = 0;
330             size_t currentRun = 0;
331             unsigned currentPage = 0;
332             for (const SkPDFStructElem::MarkedContentInfo& info : fMarkedContent) {
333                 unsigned thisPage = info.fLocation.fPageIndex;
334                 if (currentPage != thisPage) {
335                     SkASSERT(currentPage < thisPage);
336                     currentPage = thisPage;
337                     currentRun = 0;
338                 }
339                 ++currentRun;
340                 if (longestRun < currentRun) {
341                     longestRun = currentRun;
342                     longestPage = currentPage;
343                 }
344             }
345             for (const SkPDFStructElem::MarkedContentInfo& info : fMarkedContent) {
346                 if (info.fLocation.fPageIndex == longestPage) {
347                     kids->appendInt(info.fMcid);
348                 } else {
349                     std::unique_ptr<SkPDFDict> mcr = SkPDFMakeDict("MCR");
350                     mcr->insertRef("Pg", doc->getPage(info.fLocation.fPageIndex));
351                     mcr->insertInt("MCID", info.fMcid);
352                     kids->appendObject(std::move(mcr));
353                 }
354             }
355             dict.insertRef("Pg", doc->getPage(longestPage));
356         }
357         for (const SkPDFStructElem::ContentItemInfo& contentItemInfo : fContentItems) {
358             std::unique_ptr<SkPDFDict> contentItemDict = SkPDFMakeDict("OBJR");
359             contentItemDict->insertRef("Obj", contentItemInfo.fContentItemRef);
360             contentItemDict->insertRef("Pg", doc->getPage(contentItemInfo.fPageIndex));
361             kids->appendObject(std::move(contentItemDict));
362         }
363         dict.insertObject("K", std::move(kids));
364     }
365 
366     if (fAttributes) {
367         dict.insertObject("A", std::move(fAttributes));
368     }
369 
370     // If this StructElem ID was referenced, add /ID and add it to the IDTree.
371     if (fUsedInIDTree) {
372         dict.insertByteString("ID", SkPDFStructElem::StringFromElemId(fElemId));
373         idTree->push_back({fElemId, fRef});
374     }
375 
376     return doc->emit(dict, fRef);
377 }
378 
addStructElemTitle(int elemId,SkSpan<const char> title)379 void SkPDFStructTree::addStructElemTitle(int elemId, SkSpan<const char> title) {
380     if (!fRoot) {
381         return;
382     }
383     SkPDFStructElem** structElemPtr = fStructElemForElemId.find(elemId);
384     if (!structElemPtr) {
385         return;
386     }
387     SkPDFStructElem* structElem = *structElemPtr;
388     SkASSERT(structElem);
389 
390     if (structElem->fWantTitle) {
391         structElem->fTitle.append(title.data(), title.size());
392         // Arbitrary cutoff for size.
393         if (structElem->fTitle.size() > 1023) {
394             structElem->fWantTitle = false;
395         }
396     }
397 }
398 
emitStructTreeRoot(SkPDFDocument * doc) const399 SkPDFIndirectReference SkPDFStructTree::emitStructTreeRoot(SkPDFDocument* doc) const {
400     if (!fRoot || !fRoot->fUsed) {
401         return SkPDFIndirectReference();
402     }
403 
404     SkPDFIndirectReference structTreeRootRef = doc->reserveRef();
405 
406     unsigned pageCount = SkToUInt(doc->pageCount());
407 
408     // Build the StructTreeRoot.
409     SkPDFDict structTreeRoot("StructTreeRoot");
410     std::vector<IDTreeEntry> idTree;
411     structTreeRoot.insertRef("K", fRoot->emitStructElem(structTreeRootRef, &idTree, doc));
412     structTreeRoot.insertInt("ParentTreeNextKey", SkToInt(pageCount));
413 
414     // Build the parent tree, a number tree which consists of two things:
415     // For each page:
416     //   key: Page::StructParents
417     //   value: array of structure element ref indexed by the page's marked-content identifiers
418     // For each content item (usually an annotation)
419     //   key: ?::StructParent
420     //   value: structure element ref
421     SkPDFDict parentTree("ParentTree");
422     auto parentTreeNums = SkPDFMakeArray();
423 
424     // First, one entry per page.
425     SkASSERT(SkToUInt(fStructElemForMcidForPage.size()) <= pageCount);
426     for (int j = 0; j < fStructElemForMcidForPage.size(); ++j) {
427         const TArray<SkPDFStructElem*>& structElemForMcid = fStructElemForMcidForPage[j];
428         SkPDFArray structElemForMcidArray;
429         for (SkPDFStructElem* structElem : structElemForMcid) {
430             SkASSERT(structElem->fRef);
431             structElemForMcidArray.appendRef(structElem->fRef);
432         }
433         parentTreeNums->appendInt(j); // /Page /StructParents
434         parentTreeNums->appendRef(doc->emit(structElemForMcidArray));
435     }
436 
437     // Then, one entry per content item.
438     for (int j = 0; j < fStructElemForContentItem.size(); ++j) {
439         int structParentKey = kFirstContentItemStructParentKey + j;
440         SkPDFStructElem* structElem = fStructElemForContentItem[j];
441         parentTreeNums->appendInt(structParentKey); // /<content-item> /StructParent
442         parentTreeNums->appendRef(structElem->fRef);
443     }
444 
445     parentTree.insertObject("Nums", std::move(parentTreeNums));
446     structTreeRoot.insertRef("ParentTree", doc->emit(parentTree));
447 
448     // Build the IDTree, a mapping from every unique element identifier byte string to
449     // a reference to its corresponding structure element.
450     if (!idTree.empty()) {
451         std::sort(idTree.begin(), idTree.end(),
452                   [](const IDTreeEntry& a, const IDTreeEntry& b) {
453                     return a.elemId < b.elemId;
454                   });
455 
456         SkPDFDict idTreeLeaf;
457         auto limits = SkPDFMakeArray();
458         SkString lowestElemIdString = SkPDFStructElem::StringFromElemId(idTree.begin()->elemId);
459         limits->appendByteString(lowestElemIdString);
460         SkString highestElemIdString = SkPDFStructElem::StringFromElemId(idTree.rbegin()->elemId);
461         limits->appendByteString(highestElemIdString);
462         idTreeLeaf.insertObject("Limits", std::move(limits));
463         auto names = SkPDFMakeArray();
464         for (const IDTreeEntry& entry : idTree) {
465             names->appendByteString(SkPDFStructElem::StringFromElemId(entry.elemId));
466             names->appendRef(entry.structElemRef);
467         }
468         idTreeLeaf.insertObject("Names", std::move(names));
469         auto idTreeKids = SkPDFMakeArray();
470         idTreeKids->appendRef(doc->emit(idTreeLeaf));
471 
472         SkPDFDict idTreeRoot;
473         idTreeRoot.insertObject("Kids", std::move(idTreeKids));
474         structTreeRoot.insertRef("IDTree", doc->emit(idTreeRoot));
475     }
476 
477     return doc->emit(structTreeRoot, structTreeRootRef);
478 }
479 
480 namespace {
481 struct OutlineEntry {
482     struct Content {
483         SkString fText;
484         Location fLocation;
accumulate__anonf09f2a830311::OutlineEntry::Content485         void accumulate(Content const& child) {
486             fText += child.fText;
487             fLocation.accumulate(child.fLocation);
488         }
489     };
490 
491     Content fContent;
492     int fHeaderLevel;
493     SkPDFIndirectReference fRef;
494     SkPDFIndirectReference fStructureRef;
495     std::vector<OutlineEntry> fChildren = {};
496     size_t fDescendentsEmitted = 0;
497 
emitDescendents__anonf09f2a830311::OutlineEntry498     void emitDescendents(SkPDFDocument* const doc) {
499         fDescendentsEmitted = fChildren.size();
500         for (size_t i = 0; i < fChildren.size(); ++i) {
501             auto&& child = fChildren[i];
502             child.emitDescendents(doc);
503             fDescendentsEmitted += child.fDescendentsEmitted;
504 
505             SkPDFDict entry;
506             entry.insertTextString("Title", child.fContent.fText);
507 
508             auto destination = SkPDFMakeArray();
509             destination->appendRef(doc->getPage(child.fContent.fLocation.fPageIndex));
510             destination->appendName("XYZ");
511             destination->appendScalar(child.fContent.fLocation.fPoint.x());
512             destination->appendScalar(child.fContent.fLocation.fPoint.y());
513             destination->appendInt(0);
514             entry.insertObject("Dest", std::move(destination));
515 
516             entry.insertRef("Parent", child.fRef);
517             if (child.fStructureRef) {
518                 entry.insertRef("SE", child.fStructureRef);
519             }
520             if (0 < i) {
521                 entry.insertRef("Prev", fChildren[i-1].fRef);
522             }
523             if (i < fChildren.size()-1) {
524                 entry.insertRef("Next", fChildren[i+1].fRef);
525             }
526             if (!child.fChildren.empty()) {
527                 entry.insertRef("First", child.fChildren.front().fRef);
528                 entry.insertRef("Last", child.fChildren.back().fRef);
529                 entry.insertInt("Count", child.fDescendentsEmitted);
530             }
531             doc->emit(entry, child.fRef);
532         }
533     }
534 };
535 
create_outline_entry_content(SkPDFStructElem * const structElem)536 OutlineEntry::Content create_outline_entry_content(SkPDFStructElem* const structElem) {
537     SkString text;
538     if (!structElem->fTitle.isEmpty()) {
539         text = structElem->fTitle;
540     } else if (!structElem->fAlt.isEmpty()) {
541         text = structElem->fAlt;
542     }
543 
544     // The uppermost/leftmost point on the earliest page of this StructElem's marks.
545     Location structElemLocation;
546     for (auto&& mark : structElem->fMarkedContent) {
547         structElemLocation.accumulate(mark.fLocation);
548     }
549 
550     OutlineEntry::Content content{std::move(text), std::move(structElemLocation)};
551 
552     // Accumulate children
553     for (auto&& child : structElem->fChildren) {
554         if (child.fUsed) {
555             content.accumulate(create_outline_entry_content(&child));
556         }
557     }
558     return content;
559 }
create_outline_from_headers(SkPDFDocument * const doc,SkPDFStructElem * const structElem,STArray<7,OutlineEntry * > & stack)560 void create_outline_from_headers(SkPDFDocument* const doc, SkPDFStructElem* const structElem,
561                                  STArray<7, OutlineEntry*>& stack) {
562     const SkString& type = structElem->fStructType;
563     if (type.size() == 2 && type[0] == 'H' && '1' <= type[1] && type[1] <= '6') {
564         int level = type[1] - '0';
565         while (level <= stack.back()->fHeaderLevel) {
566             stack.pop_back();
567         }
568         OutlineEntry::Content content = create_outline_entry_content(structElem);
569         if (!content.fText.isEmpty()) {
570             OutlineEntry e{std::move(content), level, doc->reserveRef(), structElem->fRef};
571             stack.push_back(&stack.back()->fChildren.emplace_back(std::move(e)));
572             return;
573         }
574     }
575 
576     for (auto&& child : structElem->fChildren) {
577         if (child.fUsed) {
578             create_outline_from_headers(doc, &child, stack);
579         }
580     }
581 }
582 
583 } // namespace
584 
makeOutline(SkPDFDocument * doc) const585 SkPDFIndirectReference SkPDFStructTree::makeOutline(SkPDFDocument* doc) const {
586     if (!fRoot || !fRoot->fUsed ||
587         fOutline != SkPDF::Metadata::Outline::StructureElementHeaders)
588     {
589         return SkPDFIndirectReference();
590     }
591 
592     STArray<7, OutlineEntry*> stack;
593     OutlineEntry top{{SkString(), Location()}, 0, {}, {}};
594     stack.push_back(&top);
595     create_outline_from_headers(doc, fRoot, stack);
596     if (top.fChildren.empty()) {
597         return SkPDFIndirectReference();
598     }
599     top.emitDescendents(doc);
600     SkPDFIndirectReference outlineRef = doc->reserveRef();
601     SkPDFDict outline("Outlines");
602     outline.insertRef("First", top.fChildren.front().fRef);
603     outline.insertRef("Last", top.fChildren.back().fRef);
604     outline.insertInt("Count", top.fDescendentsEmitted);
605 
606     return doc->emit(outline, outlineRef);
607 }
608 
getRootLanguage()609 SkString SkPDFStructTree::getRootLanguage() {
610     return fRoot ? fRoot->fLang : SkString();
611 }
612