diff --git a/PdfExampleGenerator/main.cpp b/PdfExampleGenerator/main.cpp index 88776de..f435cd4 100644 --- a/PdfExampleGenerator/main.cpp +++ b/PdfExampleGenerator/main.cpp @@ -24,4 +24,5 @@ int main(int argc, char *argv[]) QApplication a(argc, argv); PDFExamplesGenerator::generateAnnotationsExample(); PDFExamplesGenerator::generatePageBoxesExample(); + PDFExamplesGenerator::generateOutlineExample(); } diff --git a/PdfExampleGenerator/pdfexamplesgenerator.cpp b/PdfExampleGenerator/pdfexamplesgenerator.cpp index 9c36024..4b2829d 100644 --- a/PdfExampleGenerator/pdfexamplesgenerator.cpp +++ b/PdfExampleGenerator/pdfexamplesgenerator.cpp @@ -322,3 +322,39 @@ void PDFExamplesGenerator::generatePageBoxesExample() pdf::PDFDocumentWriter writer(nullptr); writer.write("Ex_PageBoxes.pdf", &document, false); } + +void PDFExamplesGenerator::generateOutlineExample() +{ + pdf::PDFDocumentBuilder builder; + builder.setDocumentTitle("Test document - Outline"); + builder.setDocumentAuthor("Jakub Melka"); + builder.setDocumentCreator(QCoreApplication::applicationName()); + builder.setDocumentSubject("Testing outline"); + builder.setLanguage(QLocale::system()); + + pdf::PDFObjectReference pageReference = builder.appendPage(QRectF(0, 0, 200, 200)); + pdf::PDFOutlineItem root; + + pdf::PDFOutlineItem* child = new pdf::PDFOutlineItem(); + child->setTitle("Toto je kořen"); + child->setAction(pdf::PDFActionPtr(new pdf::PDFActionGoTo(pdf::PDFDestination::createFit(pageReference), pdf::PDFDestination()))); + root.addChild(QSharedPointer(child)); + child = new pdf::PDFOutlineItem(); + child->setTitle("Toto je druhý kořen"); + child->setAction(pdf::PDFActionPtr(new pdf::PDFActionGoTo(pdf::PDFDestination::createFit(pageReference), pdf::PDFDestination()))); + child->setFontBold(true); + child->setTextColor(Qt::green); + root.addChild(QSharedPointer(child)); + + pdf::PDFOutlineItem* subchild = new pdf::PDFOutlineItem(); + subchild->setTitle("Toto je dítě"); + subchild->setAction(pdf::PDFActionPtr(new pdf::PDFActionGoTo(pdf::PDFDestination::createFit(pageReference), pdf::PDFDestination()))); + child->addChild(QSharedPointer(subchild)); + + builder.setOutline(&root); + + // Write result to a file + pdf::PDFDocument document = builder.build(); + pdf::PDFDocumentWriter writer(nullptr); + writer.write("Ex_Outline.pdf", &document, false); +} diff --git a/PdfExampleGenerator/pdfexamplesgenerator.h b/PdfExampleGenerator/pdfexamplesgenerator.h index 4ee6382..a92be54 100644 --- a/PdfExampleGenerator/pdfexamplesgenerator.h +++ b/PdfExampleGenerator/pdfexamplesgenerator.h @@ -25,6 +25,7 @@ public: static void generateAnnotationsExample(); static void generatePageBoxesExample(); + static void generateOutlineExample(); }; #endif // PDFEXAMPLESGENERATOR_H diff --git a/PdfForQtLib/sources/pdfdocumentbuilder.cpp b/PdfForQtLib/sources/pdfdocumentbuilder.cpp index 0743ddd..3e8ab38 100644 --- a/PdfForQtLib/sources/pdfdocumentbuilder.cpp +++ b/PdfForQtLib/sources/pdfdocumentbuilder.cpp @@ -1059,6 +1059,115 @@ QRectF PDFDocumentBuilder::getPolygonsBoundingRect(const Polygons& polygons) con return rect; } +PDFObjectReference PDFDocumentBuilder::createOutlineItem(const PDFOutlineItem* root, bool writeOutlineData) +{ + PDFObjectFactory objectBuilder; + + objectBuilder.beginDictionary(); + + if (writeOutlineData) + { + // Title + objectBuilder.beginDictionaryItem("Title"); + objectBuilder << root->getTitle(); + objectBuilder.endDictionaryItem(); + + // Destination + const PDFActionGoTo* action = dynamic_cast(root->getAction()); + if (action) + { + objectBuilder.beginDictionaryItem("Dest"); + objectBuilder << action->getDestination(); + objectBuilder.endDictionaryItem(); + } + + // Color + if (root->getTextColor().isValid() && root->getTextColor() != Qt::black) + { + objectBuilder.beginDictionaryItem("C"); + objectBuilder << root->getTextColor(); + objectBuilder.endDictionaryItem(); + } + + // Flags + PDFInteger flags = 0; + if (root->isFontItalics()) + { + flags += 1; + } + if (root->isFontBold()) + { + flags += 2; + } + + if (flags > 0) + { + objectBuilder.beginDictionaryItem("F"); + objectBuilder << flags; + objectBuilder.endDictionaryItem(); + } + } + + // Create descendands + std::vector children; + children.reserve(root->getChildCount()); + for (size_t i = 0; i < root->getChildCount(); ++i) + { + children.push_back(createOutlineItem(root->getChild(i), true)); + } + + if (!children.empty()) + { + // First/Last pointers + objectBuilder.beginDictionaryItem("First"); + objectBuilder << children.front(); + objectBuilder.endDictionaryItem(); + objectBuilder.beginDictionaryItem("Last"); + objectBuilder << children.back(); + objectBuilder.endDictionaryItem(); + } + + size_t totalCount = root->getTotalCount(); + if (totalCount > 0) + { + objectBuilder.beginDictionaryItem("Count"); + objectBuilder << PDFInteger(totalCount); + objectBuilder.endDictionaryItem(); + } + + objectBuilder.endDictionary(); + PDFObjectReference parentReference = addObject(objectBuilder.takeObject()); + + for (size_t i = 0; i < children.size(); ++i) + { + PDFObjectFactory fixPointersObjectBuilder; + fixPointersObjectBuilder.beginDictionary(); + + fixPointersObjectBuilder.beginDictionaryItem("Parent"); + fixPointersObjectBuilder << parentReference; + fixPointersObjectBuilder.endDictionaryItem(); + + if (i > 0) + { + fixPointersObjectBuilder.beginDictionaryItem("Prev"); + fixPointersObjectBuilder << children[i - 1]; + fixPointersObjectBuilder.endDictionaryItem(); + } + + if (i + 1 < children.size()) + { + fixPointersObjectBuilder.beginDictionaryItem("Next"); + fixPointersObjectBuilder << children[i + 1]; + fixPointersObjectBuilder.endDictionaryItem(); + } + + fixPointersObjectBuilder.endDictionary(); + mergeTo(children[i], fixPointersObjectBuilder.takeObject()); + } + + return parentReference; +} + void PDFDocumentBuilder::flattenPageTree() { PDFObjectReference pageTreeRoot = getPageTreeRoot(); @@ -1166,6 +1275,11 @@ std::vector PDFDocumentBuilder::getPages() const return result; } +void PDFDocumentBuilder::setOutline(const PDFOutlineItem* root) +{ + setOutline(createOutlineItem(root, false)); +} + std::vector PDFDocumentBuilder::copyFrom(const std::vector& objects, const PDFObjectStorage& storage, bool createReferences) { // 1) Collect all references, which we must copy. If object is referenced, then @@ -4180,6 +4294,20 @@ void PDFDocumentBuilder::setLanguage(QLocale locale) } +void PDFDocumentBuilder::setOutline(PDFObjectReference outline) +{ + PDFObjectFactory objectBuilder; + + objectBuilder.beginDictionary(); + objectBuilder.beginDictionaryItem("Outlines"); + objectBuilder << outline; + objectBuilder.endDictionaryItem(); + objectBuilder.endDictionary(); + PDFObject updatedCatalog = objectBuilder.takeObject(); + mergeTo(getCatalogReference(), updatedCatalog); +} + + void PDFDocumentBuilder::setPageArtBox(PDFObjectReference page, QRectF box) { @@ -4270,6 +4398,21 @@ void PDFDocumentBuilder::setPageTrimBox(PDFObjectReference page, } +void PDFDocumentBuilder::setPageUserUnit(PDFObjectReference page, + PDFReal unit) +{ + PDFObjectFactory objectBuilder; + + objectBuilder.beginDictionary(); + objectBuilder.beginDictionaryItem("UserUnit"); + objectBuilder << unit; + objectBuilder.endDictionaryItem(); + objectBuilder.endDictionary(); + PDFObject updatedPageObject = objectBuilder.takeObject(); + mergeTo(page, updatedPageObject); +} + + void PDFDocumentBuilder::updateTrailerDictionary(PDFInteger objectCount) { PDFObjectFactory objectBuilder; diff --git a/PdfForQtLib/sources/pdfdocumentbuilder.h b/PdfForQtLib/sources/pdfdocumentbuilder.h index 071fb11..abcff87 100644 --- a/PdfForQtLib/sources/pdfdocumentbuilder.h +++ b/PdfForQtLib/sources/pdfdocumentbuilder.h @@ -305,6 +305,11 @@ public: /// be flattened to use this function. \sa flattenPageTree std::vector getPages() const; + /// Sets document outline root item corresponds to invisible root. + /// Top-level items are children of the root. + /// \param root Root item + void setOutline(const PDFOutlineItem* root); + /// Adds a new objet to the object storage /// \param object Object PDFObjectReference addObject(PDFObject object); @@ -1250,6 +1255,11 @@ public: void setLanguage(QLocale locale); + /// Set document outline. + /// \param outline Document outline root + void setOutline(PDFObjectReference outline); + + /// Sets art box to the page. Art box defines page's meaningful content. /// \param page Page /// \param box Box @@ -1296,6 +1306,13 @@ public: QRectF box); + /// Sets page's user unit. It specifies user space unit, in multiples of 1 / 72 inch. + /// \param page Page + /// \param unit Unit (multiple of 1pt = 1 / 72 inch) + void setPageUserUnit(PDFObjectReference page, + PDFReal unit); + + /// This function is used to update trailer dictionary. Must be called each time the final document is /// being built. /// \param objectCount Number of objects (including empty ones) @@ -1312,6 +1329,7 @@ private: PDFObjectReference getDocumentInfo() const; void updateDocumentInfo(PDFObject info); QRectF getPolygonsBoundingRect(const Polygons& Polygons) const; + PDFObjectReference createOutlineItem(const PDFOutlineItem* root, bool writeOutlineData); PDFObjectStorage m_storage; PDFVersion m_version; diff --git a/PdfForQtLib/sources/pdfoutline.cpp b/PdfForQtLib/sources/pdfoutline.cpp index 9758e62..dccf956 100644 --- a/PdfForQtLib/sources/pdfoutline.cpp +++ b/PdfForQtLib/sources/pdfoutline.cpp @@ -23,6 +23,18 @@ namespace pdf { +size_t PDFOutlineItem::getTotalCount() const +{ + size_t count = m_children.size(); + + for (size_t i = 0; i < m_children.size(); ++i) + { + count += getChild(i)->getTotalCount(); + } + + return count; +} + QSharedPointer PDFOutlineItem::parse(const PDFDocument* document, const PDFObject& root) { const PDFObject& rootDereferenced = document->getObject(root); diff --git a/PdfForQtLib/sources/pdfoutline.h b/PdfForQtLib/sources/pdfoutline.h index b2ac2ce..8426a18 100644 --- a/PdfForQtLib/sources/pdfoutline.h +++ b/PdfForQtLib/sources/pdfoutline.h @@ -31,7 +31,7 @@ namespace pdf class PDFDocument; /// Outline item -class PDFOutlineItem +class PDFFORQTLIBSHARED_EXPORT PDFOutlineItem { public: explicit PDFOutlineItem() = default; @@ -40,6 +40,7 @@ public: void setTitle(const QString& title) { m_title = title; } size_t getChildCount() const { return m_children.size(); } + size_t getTotalCount() const; const PDFOutlineItem* getChild(size_t index) const { return m_children[index].get(); } void addChild(QSharedPointer child) { m_children.emplace_back(qMove(child)); } QSharedPointer getChildPtr(size_t index) const { return m_children[index]; } diff --git a/generated_code_definition.xml b/generated_code_definition.xml index 8175b5f..849e801 100644 --- a/generated_code_definition.xml +++ b/generated_code_definition.xml @@ -9815,6 +9815,63 @@ return rootNodeReference; Set document language. _void + + + + + + + + + + outline + _PDFObjectReference + Document outline root + + + Parameters + + _void + + + + + + + + + + + + Outlines + DictionaryItemSimple + outline + + + + Dictionary + + + + CreateObject + updatedCatalog + _PDFObject + + + + + + Code + + _void + mergeTo(getCatalogReference(), updatedCatalog); + + + Structure + setOutline + Set document outline. + _void + @@ -10199,6 +10256,70 @@ return rootNodeReference; Sets trim box to the page. Trim box is physical region, of the printed page after trimming. _void + + + + + + + + + + page + _PDFObjectReference + Page + + + + + unit + _PDFReal + Unit (multiple of 1pt = 1 / 72 inch) + + + Parameters + + _void + + + + + + + + + + + + UserUnit + DictionaryItemSimple + unit + + + + Dictionary + + + + CreateObject + updatedPageObject + _PDFObject + + + + + + Code + + _void + mergeTo(page, updatedPageObject); + + + Structure + setPageUserUnit + Sets page's user unit. It specifies user space unit, in multiples of 1 / 72 inch. + _void +