[pdf] Generate outline from structure tree Bug: chromium:840455 Change-Id: If064dad6081355b6ce68cd3d7d07288fea0fd9dc Reviewed-on: https://skia-review.googlesource.com/c/skia/+/765519 Reviewed-by: Brian Osman <brianosman@google.com> Commit-Queue: Ben Wagner <bungeman@google.com>
diff --git a/include/docs/SkPDFDocument.h b/include/docs/SkPDFDocument.h index 8abb19e..6e20976 100644 --- a/include/docs/SkPDFDocument.h +++ b/include/docs/SkPDFDocument.h
@@ -145,6 +145,11 @@ */ StructureElementNode* fStructureElementTreeRoot = nullptr; + enum class Outline : int { + None = 0, + StructureElementHeaders = 1, + } fOutline = Outline::None; + /** Executor to handle threaded work within PDF Backend. If this is nullptr, then all work will be done serially on the main thread. To have worker threads assist with various tasks, set this to a valid SkExecutor
diff --git a/src/pdf/SkPDFDevice.cpp b/src/pdf/SkPDFDevice.cpp index 4fd9fc2..93ebe26 100644 --- a/src/pdf/SkPDFDevice.cpp +++ b/src/pdf/SkPDFDevice.cpp
@@ -75,29 +75,31 @@ // out of scope. class ScopedOutputMarkedContentTags { public: - ScopedOutputMarkedContentTags(int nodeId, SkPDFDocument* document, SkDynamicMemoryWStream* out) + ScopedOutputMarkedContentTags(int nodeId, SkPoint point, SkPDFDocument* document, + SkDynamicMemoryWStream* out) : fOut(out) - , fMarkId(-1) { - if (nodeId) { - fMarkId = document->createMarkIdForNodeId(nodeId); - } - - if (fMarkId != -1) { + , fMark(nodeId ? document->createMarkIdForNodeId(nodeId, point) : SkPDFTagTree::Mark()) + { + if (fMark) { fOut->writeText("/P <</MCID "); - fOut->writeDecAsText(fMarkId); + fOut->writeDecAsText(fMark.id()); fOut->writeText(" >>BDC\n"); } } + explicit operator bool() const { return bool(fMark); } + + SkPoint& point() { return SkASSERT(fMark), fMark.point(); } + ~ScopedOutputMarkedContentTags() { - if (fMarkId != -1) { + if (fMark) { fOut->writeText("EMC\n"); } } private: SkDynamicMemoryWStream* fOut; - int fMarkId; + SkPDFTagTree::Mark fMark; }; } // namespace @@ -875,7 +877,15 @@ out->writeText("BT\n"); SK_AT_SCOPE_EXIT(out->writeText("ET\n")); - ScopedOutputMarkedContentTags mark(fNodeId, fDocument, out); + // Destinations are in absolute coordinates. + // The glyphs bounds go through the localToDevice separately for clipping. + SkMatrix pageXform = this->deviceToGlobal().asM33(); + pageXform.postConcat(fDocument->currentPageTransform()); + + ScopedOutputMarkedContentTags mark(fNodeId, {SK_ScalarNaN, SK_ScalarNaN}, fDocument, out); + if (!glyphRun.text().empty()) { + fDocument->addNodeTitle(fNodeId, glyphRun.text()); + } const int numGlyphs = typeface->countGlyphs(); @@ -952,6 +962,16 @@ font->noteGlyphUsage(gid); SkGlyphID encodedGlyph = font->glyphToPDFFontEncoding(gid); SkScalar advance = advanceScale * glyphs[index]->advanceX(); + if (mark) { + SkRect absoluteGlyphBounds = pageXform.mapRect(glyphBounds); + SkPoint& markPoint = mark.point(); + if (markPoint.isFinite()) { + markPoint.fX = std::min(absoluteGlyphBounds.fLeft , markPoint.fX); + markPoint.fY = std::max(absoluteGlyphBounds.fBottom, markPoint.fY); // PDF top + } else { + markPoint = SkPoint{absoluteGlyphBounds.fLeft, absoluteGlyphBounds.fBottom}; + } + } glyphPositioner.writeGlyph(encodedGlyph, advance, xy); } } @@ -981,8 +1001,20 @@ // TODO: implement drawMesh } -void SkPDFDevice::drawFormXObject(SkPDFIndirectReference xObject, SkDynamicMemoryWStream* content) { - ScopedOutputMarkedContentTags mark(fNodeId, fDocument, content); +void SkPDFDevice::drawFormXObject(SkPDFIndirectReference xObject, SkDynamicMemoryWStream* content, + SkPath* shape) { + SkPoint point{SK_ScalarNaN, SK_ScalarNaN}; + if (shape) { + // Destinations are in absolute coordinates. + SkMatrix pageXform = this->deviceToGlobal().asM33(); + pageXform.postConcat(fDocument->currentPageTransform()); + // The shape already has localToDevice applied. + + SkRect shapeBounds = shape->getBounds(); + pageXform.mapRect(&shapeBounds); + point = SkPoint{shapeBounds.fLeft, shapeBounds.fBottom}; + } + ScopedOutputMarkedContentTags mark(fNodeId, point, fDocument, content); SkASSERT(xObject); SkPDFWriteResourceName(content, SkPDFResourceType::kXObject, @@ -1138,7 +1170,7 @@ this->setGraphicState(SkPDFGraphicState::GetSMaskGraphicState( sMask, invertClip, SkPDFGraphicState::kAlpha_SMaskMode, fDocument), content.stream()); - this->drawFormXObject(xObject, content.stream()); + this->drawFormXObject(xObject, content.stream(), nullptr); this->clearMaskOnGraphicState(content.stream()); } @@ -1342,7 +1374,7 @@ if (shape == nullptr || blendMode == SkBlendMode::kDstOut || blendMode == SkBlendMode::kSrcATop) { ScopedContentEntry content(this, nullptr, SkMatrix::I(), stockPaint); - this->drawFormXObject(dst, content.stream()); + this->drawFormXObject(dst, content.stream(), nullptr); return; } else { blendMode = SkBlendMode::kClear; @@ -1382,7 +1414,7 @@ blendMode == SkBlendMode::kDstATop) { ScopedContentEntry content(this, nullptr, SkMatrix::I(), stockPaint); if (content) { - this->drawFormXObject(srcFormXObject, content.stream()); + this->drawFormXObject(srcFormXObject, content.stream(), nullptr); } if (blendMode == SkBlendMode::kSrc) { return; @@ -1390,7 +1422,7 @@ } else if (blendMode == SkBlendMode::kSrcATop) { ScopedContentEntry content(this, nullptr, SkMatrix::I(), stockPaint); if (content) { - this->drawFormXObject(dst, content.stream()); + this->drawFormXObject(dst, content.stream(), nullptr); } } @@ -1649,8 +1681,8 @@ if (!content) { return; } + SkPath shape = SkPath::Rect(SkRect::Make(subset)).makeTransform(matrix); if (content.needShape()) { - SkPath shape = SkPath::Rect(SkRect::Make(subset)).makeTransform(matrix); content.setShape(shape); } if (!content.needSource()) { @@ -1678,7 +1710,7 @@ fDocument->fPDFBitmapMap.set(key, pdfimage); } SkASSERT(pdfimage != SkPDFIndirectReference()); - this->drawFormXObject(pdfimage, content.stream()); + this->drawFormXObject(pdfimage, content.stream(), &shape); } /////////////////////////////////////////////////////////////////////////////////////////////////// @@ -1710,15 +1742,15 @@ if (!content) { return; } + SkPath shape = SkPath::Rect(SkRect::Make(device->imageInfo().dimensions())); + shape.transform(matrix); if (content.needShape()) { - SkPath shape = SkPath::Rect(SkRect::Make(device->imageInfo().dimensions())); - shape.transform(matrix); content.setShape(shape); } if (!content.needSource()) { return; } - this->drawFormXObject(pdfDevice->makeFormXObjectFromDevice(), content.stream()); + this->drawFormXObject(pdfDevice->makeFormXObjectFromDevice(), content.stream(), &shape); } void SkPDFDevice::drawSpecial(SkSpecialImage* srcImg, const SkMatrix& localToDevice,
diff --git a/src/pdf/SkPDFDevice.h b/src/pdf/SkPDFDevice.h index a5fd353..233a969 100644 --- a/src/pdf/SkPDFDevice.h +++ b/src/pdf/SkPDFDevice.h
@@ -192,7 +192,7 @@ void clearMaskOnGraphicState(SkDynamicMemoryWStream*); void setGraphicState(SkPDFIndirectReference gs, SkDynamicMemoryWStream*); - void drawFormXObject(SkPDFIndirectReference xObject, SkDynamicMemoryWStream*); + void drawFormXObject(SkPDFIndirectReference xObject, SkDynamicMemoryWStream*, SkPath* shape); bool hasEmptyClip() const { return this->cs().isEmpty(this->bounds()); }
diff --git a/src/pdf/SkPDFDocument.cpp b/src/pdf/SkPDFDocument.cpp index 9aec1d91..2c0f523 100644 --- a/src/pdf/SkPDFDocument.cpp +++ b/src/pdf/SkPDFDocument.cpp
@@ -217,7 +217,7 @@ fRasterScale = fMetadata.fRasterDPI / kDpiForRasterScaleOne; } if (fMetadata.fStructureElementTreeRoot) { - fTagTree.init(fMetadata.fStructureElementTreeRoot); + fTagTree.init(fMetadata.fStructureElementTreeRoot, fMetadata.fOutline); } fExecutor = fMetadata.fExecutor; } @@ -527,8 +527,12 @@ return fPageDevice->initialTransform(); } -int SkPDFDocument::createMarkIdForNodeId(int nodeId) { - return fTagTree.createMarkIdForNodeId(nodeId, SkToUInt(this->currentPageIndex())); +SkPDFTagTree::Mark SkPDFDocument::createMarkIdForNodeId(int nodeId, SkPoint p) { + return fTagTree.createMarkIdForNodeId(nodeId, SkToUInt(this->currentPageIndex()), p); +} + +void SkPDFDocument::addNodeTitle(int nodeId, SkSpan<const char> title) { + fTagTree.addNodeTitle(nodeId, std::move(title)); } int SkPDFDocument::createStructParentKeyForNodeId(int nodeId) { @@ -594,6 +598,10 @@ markInfo->insertBool("Marked", true); docCatalog->insertObject("MarkInfo", std::move(markInfo)); docCatalog->insertRef("StructTreeRoot", root); + + if (SkPDFIndirectReference outline = fTagTree.makeOutline(this)) { + docCatalog->insertRef("Outlines", outline); + } } auto docCatalogRef = this->emit(*docCatalog);
diff --git a/src/pdf/SkPDFDocumentPriv.h b/src/pdf/SkPDFDocumentPriv.h index 2625ac0..dffc239 100644 --- a/src/pdf/SkPDFDocumentPriv.h +++ b/src/pdf/SkPDFDocumentPriv.h
@@ -118,12 +118,14 @@ // Used to allow marked content to refer to its corresponding structure // tree node, via a page entry in the parent tree. Returns -1 if no // mark ID. - int createMarkIdForNodeId(int nodeId); + SkPDFTagTree::Mark createMarkIdForNodeId(int nodeId, SkPoint); // Used to allow annotations to refer to their corresponding structure // tree node, via the struct parent tree. Returns -1 if no struct parent // key. int createStructParentKeyForNodeId(int nodeId); + void addNodeTitle(int nodeId, SkSpan<const char>); + std::unique_ptr<SkPDFArray> getAnnotations(); SkPDFIndirectReference reserveRef() { return SkPDFIndirectReference{fNextObjectNumber++}; }
diff --git a/src/pdf/SkPDFTag.cpp b/src/pdf/SkPDFTag.cpp index 632e07c..cd78342 100644 --- a/src/pdf/SkPDFTag.cpp +++ b/src/pdf/SkPDFTag.cpp
@@ -22,6 +22,32 @@ // PDF we can handle. const int kFirstAnnotationStructParentKey = 100000; +namespace { +struct Location { + SkPoint fPoint{SK_ScalarNaN, SK_ScalarNaN}; + unsigned fPageIndex{0}; + + void accumulate(Location const& child) { + if (!child.fPoint.isFinite()) { + return; + } + if (!fPoint.isFinite()) { + *this = child; + return; + } + if (child.fPageIndex < fPageIndex) { + *this = child; + return; + } + if (child.fPageIndex == fPageIndex) { + fPoint.fX = std::min(child.fPoint.fX, fPoint.fX); + fPoint.fY = std::max(child.fPoint.fY, fPoint.fY); // PDF y-up + return; + } + } +}; +} // namespace + struct SkPDFTagNode { // Structure element nodes need a unique alphanumeric ID, // and we need to be able to output them sorted in lexicographic @@ -37,12 +63,14 @@ SkPDFTagNode* fChildren = nullptr; size_t fChildCount = 0; struct MarkedContentInfo { - unsigned fPageIndex; + Location fLocation; int fMarkId; }; TArray<MarkedContentInfo> fMarkedContent; int fNodeId; + bool fWantTitle; SkString fTypeString; + SkString fTitle; SkString fAlt; SkString fLang; SkPDFIndirectReference fRef; @@ -132,13 +160,21 @@ void SkPDFTagTree::Copy(SkPDF::StructureElementNode& node, SkPDFTagNode* dst, SkArenaAlloc* arena, - THashMap<int, SkPDFTagNode*>* nodeMap) { + THashMap<int, SkPDFTagNode*>* nodeMap, + bool wantTitle) { nodeMap->set(node.fNodeId, dst); for (int nodeId : node.fAdditionalNodeIds) { SkASSERT(!nodeMap->find(nodeId)); nodeMap->set(nodeId, dst); } dst->fNodeId = node.fNodeId; + + // Accumulate title text, need to be in sync with create_outline_from_headers + const char* type = node.fTypeString.c_str(); + wantTitle |= fOutline == SkPDF::Metadata::Outline::StructureElementHeaders && + type[0] == 'H' && '1' <= type[1] && type[1] <= '6'; + dst->fWantTitle = wantTitle; + dst->fTypeString = node.fTypeString; dst->fAlt = node.fAlt; dst->fLang = node.fLang; @@ -148,26 +184,35 @@ dst->fChildCount = childCount; dst->fChildren = children; for (size_t i = 0; i < childCount; ++i) { - Copy(*node.fChildVector[i], &children[i], arena, nodeMap); + Copy(*node.fChildVector[i], &children[i], arena, nodeMap, wantTitle); } dst->fAttributes = std::move(node.fAttributes.fAttrs); } -void SkPDFTagTree::init(SkPDF::StructureElementNode* node) { +void SkPDFTagTree::init(SkPDF::StructureElementNode* node, SkPDF::Metadata::Outline outline) { if (node) { fRoot = fArena.make<SkPDFTagNode>(); - Copy(*node, fRoot, &fArena, &fNodeMap); + fOutline = outline; + Copy(*node, fRoot, &fArena, &fNodeMap, false); } } -int SkPDFTagTree::createMarkIdForNodeId(int nodeId, unsigned pageIndex) { +int SkPDFTagTree::Mark::id() { + return fNode ? fNode->fMarkedContent[fMarkIndex].fMarkId : -1; +} + +SkPoint& SkPDFTagTree::Mark::point() { + return fNode->fMarkedContent[fMarkIndex].fLocation.fPoint; +} + +auto SkPDFTagTree::createMarkIdForNodeId(int nodeId, unsigned pageIndex, SkPoint point) -> Mark { if (!fRoot) { - return -1; + return Mark(); } SkPDFTagNode** tagPtr = fNodeMap.find(nodeId); if (!tagPtr) { - return -1; + return Mark(); } SkPDFTagNode* tag = *tagPtr; SkASSERT(tag); @@ -176,9 +221,9 @@ } TArray<SkPDFTagNode*>& pageMarks = fMarksPerPage[pageIndex]; int markId = pageMarks.size(); - tag->fMarkedContent.push_back({pageIndex, markId}); + tag->fMarkedContent.push_back({{point, pageIndex}, markId}); pageMarks.push_back(tag); - return markId; + return Mark(tag, tag->fMarkedContent.size() - 1); } int SkPDFTagTree::createStructParentKeyForNodeId(int nodeId, unsigned pageIndex) { @@ -236,7 +281,7 @@ } for (const SkPDFTagNode::MarkedContentInfo& info : node->fMarkedContent) { std::unique_ptr<SkPDFDict> mcr = SkPDFMakeDict("MCR"); - mcr->insertRef("Pg", doc->getPage(info.fPageIndex)); + mcr->insertRef("Pg", doc->getPage(info.fLocation.fPageIndex)); mcr->insertInt("MCID", info.fMarkId); kids->appendObject(std::move(mcr)); } @@ -287,6 +332,26 @@ tag->fAnnotations.push_back(annotationInfo); } +void SkPDFTagTree::addNodeTitle(int nodeId, SkSpan<const char> title) { + if (!fRoot) { + return; + } + SkPDFTagNode** tagPtr = fNodeMap.find(nodeId); + if (!tagPtr) { + return; + } + SkPDFTagNode* tag = *tagPtr; + SkASSERT(tag); + + if (tag->fWantTitle) { + tag->fTitle.append(title.data(), title.size()); + // Arbitrary cutoff for size. + if (tag->fTitle.size() > 1023) { + tag->fWantTitle = false; + } + } +} + SkPDFIndirectReference SkPDFTagTree::makeStructTreeRoot(SkPDFDocument* doc) { if (!fRoot || can_discard(fRoot)) { return SkPDFIndirectReference(); @@ -372,3 +437,136 @@ return doc->emit(structTreeRoot, ref); } + +namespace { +struct OutlineEntry { + struct Content { + SkString fText; + Location fLocation; + void accumulate(Content const& child) { + fText += child.fText; + fLocation.accumulate(child.fLocation); + } + }; + + Content fContent; + int fHeaderLevel; + SkPDFIndirectReference fRef; + SkPDFIndirectReference fStructureRef; + std::vector<OutlineEntry> fChildren = {}; + size_t fDescendentsEmitted = 0; + + void emitDescendents(SkPDFDocument* const doc) { + fDescendentsEmitted = fChildren.size(); + for (size_t i = 0; i < fChildren.size(); ++i) { + auto&& child = fChildren[i]; + child.emitDescendents(doc); + fDescendentsEmitted += child.fDescendentsEmitted; + + SkPDFDict entry; + entry.insertTextString("Title", child.fContent.fText); + + auto destination = SkPDFMakeArray(); + destination->appendRef(doc->getPage(child.fContent.fLocation.fPageIndex)); + destination->appendName("XYZ"); + destination->appendScalar(child.fContent.fLocation.fPoint.x()); + destination->appendScalar(child.fContent.fLocation.fPoint.y()); + destination->appendInt(0); + entry.insertObject("Dest", std::move(destination)); + + entry.insertRef("Parent", child.fRef); + if (child.fStructureRef) { + entry.insertRef("SE", child.fStructureRef); + } + if (0 < i) { + entry.insertRef("Prev", fChildren[i-1].fRef); + } + if (i < fChildren.size()-1) { + entry.insertRef("Next", fChildren[i+1].fRef); + } + if (!child.fChildren.empty()) { + entry.insertRef("First", child.fChildren.front().fRef); + entry.insertRef("Last", child.fChildren.back().fRef); + entry.insertInt("Count", child.fDescendentsEmitted); + } + doc->emit(entry, child.fRef); + } + } +}; + +OutlineEntry::Content create_outline_entry_content(SkPDFTagNode* const node) { + SkString text; + if (!node->fTitle.isEmpty()) { + text = node->fTitle; + } else if (!node->fAlt.isEmpty()) { + text = node->fAlt; + } + + // The uppermost/leftmost point on the earliest page of this node's marks. + Location markPoint; + for (auto&& mark : node->fMarkedContent) { + markPoint.accumulate(mark.fLocation); + } + + OutlineEntry::Content content{std::move(text), std::move(markPoint)}; + + // Accumulate children + SkSpan<SkPDFTagNode> children(node->fChildren, node->fChildCount); + for (auto&& child : children) { + if (can_discard(&child)) { + continue; + } + content.accumulate(create_outline_entry_content(&child)); + } + return content; +} +void create_outline_from_headers(SkPDFDocument* const doc, SkPDFTagNode* const node, + STArray<7, OutlineEntry*>& stack) { + char const *type = node->fTypeString.c_str(); + if (type[0] == 'H' && '1' <= type[1] && type[1] <= '6') { + int level = type[1] - '0'; + while (level <= stack.back()->fHeaderLevel) { + stack.pop_back(); + } + OutlineEntry::Content content = create_outline_entry_content(node); + if (!content.fText.isEmpty()) { + OutlineEntry e{std::move(content), level, doc->reserveRef(), node->fRef}; + stack.push_back(&stack.back()->fChildren.emplace_back(std::move(e))); + return; + } + } + + SkSpan<SkPDFTagNode> children(node->fChildren, node->fChildCount); + for (auto&& child : children) { + if (can_discard(&child)) { + continue; + } + create_outline_from_headers(doc, &child, stack); + } +} + +} // namespace + +SkPDFIndirectReference SkPDFTagTree::makeOutline(SkPDFDocument* doc) { + if (!fRoot || can_discard(fRoot) || + fOutline != SkPDF::Metadata::Outline::StructureElementHeaders) + { + return SkPDFIndirectReference(); + } + + STArray<7, OutlineEntry*> stack; + OutlineEntry top{{SkString(), Location()}, 0, {}, {}}; + stack.push_back(&top); + create_outline_from_headers(doc, fRoot, stack); + if (top.fChildren.empty()) { + return SkPDFIndirectReference(); + } + top.emitDescendents(doc); + SkPDFIndirectReference outlineRef = doc->reserveRef(); + SkPDFDict outline("Outlines"); + outline.insertRef("First", top.fChildren.front().fRef); + outline.insertRef("Last", top.fChildren.back().fRef); + outline.insertInt("Count", top.fDescendentsEmitted); + + return doc->emit(outline, outlineRef); +}
diff --git a/src/pdf/SkPDFTag.h b/src/pdf/SkPDFTag.h index b783910..cd25adc 100644 --- a/src/pdf/SkPDFTag.h +++ b/src/pdf/SkPDFTag.h
@@ -21,18 +21,36 @@ public: SkPDFTagTree(); ~SkPDFTagTree(); - void init(SkPDF::StructureElementNode*); + void init(SkPDF::StructureElementNode*, SkPDF::Metadata::Outline); + + class Mark { + SkPDFTagNode *const fNode; + size_t const fMarkIndex; + public: + Mark(SkPDFTagNode* node, size_t index) : fNode(node), fMarkIndex(index) {} + Mark() : Mark(nullptr, 0) {} + Mark(const Mark&) = delete; + Mark& operator=(const Mark&) = delete; + Mark(Mark&&) = default; + Mark& operator=(Mark&&) = delete; + + explicit operator bool() const { return fNode; } + int id(); + SkPoint& point(); + }; // Used to allow marked content to refer to its corresponding structure - // tree node, via a page entry in the parent tree. Returns -1 if no - // mark ID. - int createMarkIdForNodeId(int nodeId, unsigned pageIndex); + // tree node, via a page entry in the parent tree. Returns a false mark if + // nodeId is 0. + Mark createMarkIdForNodeId(int nodeId, unsigned pageIndex, SkPoint); // Used to allow annotations to refer to their corresponding structure // tree node, via the struct parent tree. Returns -1 if no struct parent // key. int createStructParentKeyForNodeId(int nodeId, unsigned pageIndex); void addNodeAnnotation(int nodeId, SkPDFIndirectReference annotationRef, unsigned pageIndex); + void addNodeTitle(int nodeId, SkSpan<const char>); SkPDFIndirectReference makeStructTreeRoot(SkPDFDocument* doc); + SkPDFIndirectReference makeOutline(SkPDFDocument* doc); private: // An entry in a map from a node ID to an indirect reference to its @@ -42,10 +60,11 @@ SkPDFIndirectReference ref; }; - static void Copy(SkPDF::StructureElementNode& node, - SkPDFTagNode* dst, - SkArenaAlloc* arena, - skia_private::THashMap<int, SkPDFTagNode*>* nodeMap); + void Copy(SkPDF::StructureElementNode& node, + SkPDFTagNode* dst, + SkArenaAlloc* arena, + skia_private::THashMap<int, SkPDFTagNode*>* nodeMap, + bool wantTitle); SkPDFIndirectReference PrepareTagTreeToEmit(SkPDFIndirectReference parent, SkPDFTagNode* node, SkPDFDocument* doc); @@ -53,6 +72,7 @@ SkArenaAlloc fArena; skia_private::THashMap<int, SkPDFTagNode*> fNodeMap; SkPDFTagNode* fRoot = nullptr; + SkPDF::Metadata::Outline fOutline; skia_private::TArray<skia_private::TArray<SkPDFTagNode*>> fMarksPerPage; std::vector<IDTreeEntry> fIdTreeEntries; std::vector<int> fParentTreeAnnotationNodeIds;
diff --git a/src/pdf/SkPDFTypes.h b/src/pdf/SkPDFTypes.h index 3726017..370c948 100644 --- a/src/pdf/SkPDFTypes.h +++ b/src/pdf/SkPDFTypes.h
@@ -35,7 +35,7 @@ struct SkPDFIndirectReference { int fValue = -1; - explicit operator bool() { return fValue != -1; } + explicit operator bool() const { return fValue != -1; } }; inline static bool operator==(SkPDFIndirectReference u, SkPDFIndirectReference v) {
diff --git a/tests/PDFTaggedTest.cpp b/tests/PDFTaggedTest.cpp index e56f408..1231a69 100644 --- a/tests/PDFTaggedTest.cpp +++ b/tests/PDFTaggedTest.cpp
@@ -45,6 +45,7 @@ SkPDF::Metadata metadata; metadata.fTitle = "Example Tagged PDF"; metadata.fCreator = "Skia"; + metadata.fOutline = SkPDF::Metadata::Outline::StructureElementHeaders; SkPDF::DateTime now; SkPDFUtils::GetDateTime(&now); metadata.fCreation = now; @@ -59,6 +60,7 @@ auto h1 = std::make_unique<PDFTag>(); h1->fNodeId = 2; h1->fTypeString = "H1"; + h1->fAlt = "A Header"; root->fChildVector.push_back(std::move(h1)); // Initial paragraph.