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