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