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