diff --git a/PdfForQtLib/PdfForQtLib.pro b/PdfForQtLib/PdfForQtLib.pro index 0fb2e72..a03d561 100644 --- a/PdfForQtLib/PdfForQtLib.pro +++ b/PdfForQtLib/PdfForQtLib.pro @@ -70,6 +70,7 @@ SOURCES += \ sources/pdfsecurityhandler.cpp \ sources/pdfsignaturehandler.cpp \ sources/pdfsnapper.cpp \ + sources/pdfstructuretree.cpp \ sources/pdftextlayout.cpp \ sources/pdfutils.cpp \ sources/pdfwidgettool.cpp \ @@ -128,6 +129,7 @@ HEADERS += \ sources/pdfsignaturehandler.h \ sources/pdfsignaturehandler_impl.h \ sources/pdfsnapper.h \ + sources/pdfstructuretree.h \ sources/pdftextlayout.h \ sources/pdfwidgettool.h \ sources/pdfwidgetutils.h \ diff --git a/PdfForQtLib/sources/pdfcatalog.cpp b/PdfForQtLib/sources/pdfcatalog.cpp index 529f628..bac845d 100644 --- a/PdfForQtLib/sources/pdfcatalog.cpp +++ b/PdfForQtLib/sources/pdfcatalog.cpp @@ -25,6 +25,20 @@ namespace pdf { +// Entries for "Info" entry in trailer dictionary +static constexpr const char* PDF_DOCUMENT_INFO_ENTRY_TITLE = "Title"; +static constexpr const char* PDF_DOCUMENT_INFO_ENTRY_AUTHOR = "Author"; +static constexpr const char* PDF_DOCUMENT_INFO_ENTRY_SUBJECT = "Subject"; +static constexpr const char* PDF_DOCUMENT_INFO_ENTRY_KEYWORDS = "Keywords"; +static constexpr const char* PDF_DOCUMENT_INFO_ENTRY_CREATOR = "Creator"; +static constexpr const char* PDF_DOCUMENT_INFO_ENTRY_PRODUCER = "Producer"; +static constexpr const char* PDF_DOCUMENT_INFO_ENTRY_CREATION_DATE = "CreationDate"; +static constexpr const char* PDF_DOCUMENT_INFO_ENTRY_MODIFIED_DATE = "ModDate"; +static constexpr const char* PDF_DOCUMENT_INFO_ENTRY_TRAPPED = "Trapped"; +static constexpr const char* PDF_DOCUMENT_INFO_ENTRY_TRAPPED_TRUE = "True"; +static constexpr const char* PDF_DOCUMENT_INFO_ENTRY_TRAPPED_FALSE = "False"; +static constexpr const char* PDF_DOCUMENT_INFO_ENTRY_TRAPPED_UNKNOWN = "Unknown"; + static constexpr const char* PDF_VIEWER_PREFERENCES_DICTIONARY = "ViewerPreferences"; static constexpr const char* PDF_VIEWER_PREFERENCES_HIDE_TOOLBAR = "HideToolbar"; static constexpr const char* PDF_VIEWER_PREFERENCES_HIDE_MENUBAR = "HideMenubar"; @@ -133,6 +147,15 @@ PDFCatalog PDFCatalog::parse(const PDFObject& catalog, const PDFDocument* docume catalogObject.m_pageMode = loader.readEnumByName(catalogDictionary->get("PageMode"), pageModes.begin(), pageModes.end(), PageMode::UseNone); } + if (const PDFDictionary* actionDictionary = document->getDictionaryFromObject(catalogDictionary->get("AA"))) + { + catalogObject.m_documentActions[WillClose] = PDFAction::parse(&document->getStorage(), actionDictionary->get("WC")); + catalogObject.m_documentActions[WillSave] = PDFAction::parse(&document->getStorage(), actionDictionary->get("WS")); + catalogObject.m_documentActions[DidSave] = PDFAction::parse(&document->getStorage(), actionDictionary->get("DS")); + catalogObject.m_documentActions[WillPrint] = PDFAction::parse(&document->getStorage(), actionDictionary->get("WP")); + catalogObject.m_documentActions[DidPrint] = PDFAction::parse(&document->getStorage(), actionDictionary->get("DP")); + } + catalogObject.m_version = loader.readNameFromDictionary(catalogDictionary, "Version"); if (const PDFDictionary* namesDictionary = document->getDictionaryFromObject(catalogDictionary->get("Names"))) @@ -172,6 +195,8 @@ PDFCatalog PDFCatalog::parse(const PDFObject& catalog, const PDFDocument* docume catalogObject.m_formObject = catalogDictionary->get("AcroForm"); catalogObject.m_extensions = PDFDeveloperExtensions::parse(catalogDictionary->get("Extensions"), document); catalogObject.m_documentSecurityStore = PDFDocumentSecurityStore::parse(catalogDictionary->get("DSS"), document); + catalogObject.m_threads = loader.readObjectList(catalogDictionary->get("Threads")); + catalogObject.m_metadata = catalogDictionary->get("Metadata"); return catalogObject; } @@ -573,4 +598,163 @@ PDFDeveloperExtensions PDFDeveloperExtensions::parse(const PDFObject& object, co return extensions; } +PDFDocumentInfo PDFDocumentInfo::parse(const PDFObject& object, const PDFObjectStorage* storage) +{ + PDFDocumentInfo info; + + if (const PDFDictionary* infoDictionary = storage->getDictionaryFromObject(object)) + { + auto readTextString = [storage, infoDictionary](const char* entry, QString& fillEntry) + { + if (infoDictionary->hasKey(entry)) + { + const PDFObject& stringObject = storage->getObject(infoDictionary->get(entry)); + if (stringObject.isString()) + { + // We have succesfully read the string, convert it according to encoding + fillEntry = PDFEncoding::convertTextString(stringObject.getString()); + } + else if (!stringObject.isNull()) + { + throw PDFException(PDFTranslationContext::tr("Bad format of document info entry in trailer dictionary. String expected.")); + } + } + }; + readTextString(PDF_DOCUMENT_INFO_ENTRY_TITLE, info.title); + readTextString(PDF_DOCUMENT_INFO_ENTRY_AUTHOR, info.author); + readTextString(PDF_DOCUMENT_INFO_ENTRY_SUBJECT, info.subject); + readTextString(PDF_DOCUMENT_INFO_ENTRY_KEYWORDS, info.keywords); + readTextString(PDF_DOCUMENT_INFO_ENTRY_CREATOR, info.creator); + readTextString(PDF_DOCUMENT_INFO_ENTRY_PRODUCER, info.producer); + + auto readDate= [storage, infoDictionary](const char* entry, QDateTime& fillEntry) + { + if (infoDictionary->hasKey(entry)) + { + const PDFObject& stringObject = storage->getObject(infoDictionary->get(entry)); + if (stringObject.isString()) + { + // We have succesfully read the string, convert it to date time + fillEntry = PDFEncoding::convertToDateTime(stringObject.getString()); + + if (!fillEntry.isValid()) + { + throw PDFException(PDFTranslationContext::tr("Bad format of document info entry in trailer dictionary. String with date time format expected.")); + } + } + else if (!stringObject.isNull()) + { + throw PDFException(PDFTranslationContext::tr("Bad format of document info entry in trailer dictionary. String with date time format expected.")); + } + } + }; + readDate(PDF_DOCUMENT_INFO_ENTRY_CREATION_DATE, info.creationDate); + readDate(PDF_DOCUMENT_INFO_ENTRY_MODIFIED_DATE, info.modifiedDate); + + if (infoDictionary->hasKey(PDF_DOCUMENT_INFO_ENTRY_TRAPPED)) + { + const PDFObject& nameObject = storage->getObject(infoDictionary->get(PDF_DOCUMENT_INFO_ENTRY_TRAPPED)); + if (nameObject.isName()) + { + const QByteArray& name = nameObject.getString(); + if (name == PDF_DOCUMENT_INFO_ENTRY_TRAPPED_TRUE) + { + info.trapped = Trapped::True; + } + else if (name == PDF_DOCUMENT_INFO_ENTRY_TRAPPED_FALSE) + { + info.trapped = Trapped::False; + } + else if (name == PDF_DOCUMENT_INFO_ENTRY_TRAPPED_UNKNOWN) + { + info.trapped = Trapped::Unknown; + } + else + { + throw PDFException(PDFTranslationContext::tr("Bad format of document info entry in trailer dictionary. Trapping information expected")); + } + } + else if (nameObject.isBool()) + { + info.trapped = nameObject.getBool() ? Trapped::True : Trapped::False; + } + else + { + throw PDFException(PDFTranslationContext::tr("Bad format of document info entry in trailer dictionary. Trapping information expected")); + } + } + + // Scan for extra items + constexpr const char* PREDEFINED_ITEMS[] = { PDF_DOCUMENT_INFO_ENTRY_TITLE, PDF_DOCUMENT_INFO_ENTRY_AUTHOR, PDF_DOCUMENT_INFO_ENTRY_SUBJECT, + PDF_DOCUMENT_INFO_ENTRY_KEYWORDS, PDF_DOCUMENT_INFO_ENTRY_CREATOR, PDF_DOCUMENT_INFO_ENTRY_PRODUCER, + PDF_DOCUMENT_INFO_ENTRY_CREATION_DATE, PDF_DOCUMENT_INFO_ENTRY_MODIFIED_DATE, PDF_DOCUMENT_INFO_ENTRY_TRAPPED }; + for (size_t i = 0; i < infoDictionary->getCount(); ++i) + { + QByteArray key = infoDictionary->getKey(i).getString(); + if (std::none_of(std::begin(PREDEFINED_ITEMS), std::end(PREDEFINED_ITEMS), [&key](const char* item) { return item == key; })) + { + const PDFObject& value = storage->getObject(infoDictionary->getValue(i)); + if (value.isString()) + { + const QByteArray& stringValue = value.getString(); + QDateTime dateTime = PDFEncoding::convertToDateTime(stringValue); + if (dateTime.isValid()) + { + info.extra[key] = dateTime; + } + else + { + info.extra[key] = PDFEncoding::convertTextString(stringValue); + } + } + } + } + } + + return info; +} + +PDFArticleThread PDFArticleThread::parse(const PDFObjectStorage* storage, const PDFObject& object) +{ + PDFArticleThread result; + + if (const PDFDictionary* dictionary = storage->getDictionaryFromObject(object)) + { + PDFDocumentDataLoaderDecorator loader(storage); + PDFObjectReference firstBeadReference = loader.readReferenceFromDictionary(dictionary, "F"); + + std::set visitedBeads; + PDFObjectReference currentBead = firstBeadReference; + + while (!visitedBeads.count(currentBead)) + { + visitedBeads.insert(currentBead); + + // Read bead + if (const PDFDictionary* beadDictionary = storage->getDictionaryFromObject(storage->getObjectByReference(currentBead))) + { + Bead bead; + bead.self = currentBead; + bead.thread = loader.readReferenceFromDictionary(beadDictionary, "T"); + bead.next = loader.readReferenceFromDictionary(beadDictionary, "N"); + bead.previous = loader.readReferenceFromDictionary(beadDictionary, "V"); + bead.page = loader.readReferenceFromDictionary(beadDictionary, "P"); + bead.rect = loader.readRectangle(beadDictionary->get("R"), QRectF()); + + currentBead = bead.next; + result.m_beads.push_back(bead); + } + else + { + // current bead will be the same, the cycle will break + } + } + + result.m_information = PDFDocumentInfo::parse(dictionary->get("I"), storage); + result.m_metadata = loader.readReferenceFromDictionary(dictionary, "Metadata"); + } + + return result; +} + } // namespace pdf diff --git a/PdfForQtLib/sources/pdfcatalog.h b/PdfForQtLib/sources/pdfcatalog.h index e3fe5dc..1bd587b 100644 --- a/PdfForQtLib/sources/pdfcatalog.h +++ b/PdfForQtLib/sources/pdfcatalog.h @@ -104,6 +104,39 @@ private: PDFInteger m_startNumber; }; +/// Info about the document. Title, Author, Keywords... It also stores "extra" +/// values, which are in info dictionary. They can be either strings, or date +/// time (QString or QDateTime). +struct PDFDocumentInfo +{ + /// Indicates, that document was modified that it includes trapping information. + /// See PDF Reference 1.7, Section 10.10.5 "Trapping Support". + enum class Trapped + { + True, ///< Fully trapped + False, ///< Not yet trapped + Unknown ///< Either unknown, or it has been trapped partly, not fully + }; + + /// Parses info from catalog dictionary. If object cannot be parsed, or error occurs, + /// then exception is thrown. This function may throw exceptions, if error occured. + /// \param object Object containing info dictionary + /// \param storage Storage of objects + static PDFDocumentInfo parse(const PDFObject& object, const PDFObjectStorage* storage); + + QString title; + QString author; + QString subject; + QString keywords; + QString creator; + QString producer; + QDateTime creationDate; + QDateTime modifiedDate; + Trapped trapped = Trapped::Unknown; + PDFVersion version; + std::map extra; +}; + class PDFViewerPreferences { public: @@ -225,6 +258,40 @@ private: std::map m_VRI; }; +/// Article thread. Each thread contains beads, which can be across multiple pages. +class PDFArticleThread +{ +public: + explicit inline PDFArticleThread() = default; + + struct Bead + { + PDFObjectReference self; + PDFObjectReference thread; + PDFObjectReference next; + PDFObjectReference previous; + PDFObjectReference page; + QRectF rect; + }; + using Beads = std::vector; + using Information = PDFDocumentInfo; + + const Beads& getBeads() const { return m_beads; } + const Information& getInformation() const { return m_information; } + const PDFObjectReference& getMetadata() const { return m_metadata; } + + /// Parses article thread from object. If object cannot be parsed, or error occurs, + /// then empty object is returned. + /// \param storage Storage + /// \param object Object + static PDFArticleThread parse(const PDFObjectStorage* storage, const PDFObject& object); + +private: + Beads m_beads; + Information m_information; + PDFObjectReference m_metadata; +}; + /// Document extensions. Contains information about developer's extensions /// used in document. class PDFFORQTLIBSHARED_EXPORT PDFDeveloperExtensions @@ -268,6 +335,16 @@ public: static constexpr const size_t INVALID_PAGE_INDEX = std::numeric_limits::max(); + enum DocumentAction + { + WillClose, + WillSave, + DidSave, + WillPrint, + DidPrint, + LastDocumentAction + }; + /// Returns viewer preferences of the application const PDFViewerPreferences* getViewerPreferences() const { return &m_viewerPreferences; } @@ -299,6 +376,9 @@ public: const PDFObject& getFormObject() const { return m_formObject; } const PDFDeveloperExtensions& getExtensions() const { return m_extensions; } const PDFDocumentSecurityStore& getDocumentSecurityStore() const { return m_documentSecurityStore; } + const std::vector& getArticleThreads() const { return m_threads; } + const PDFAction* getDocumentAction(DocumentAction action) const { return m_documentActions.at(action).get(); } + const PDFObject& getMetadata() const { return m_metadata; } /// Returns destination using the key. If destination with the key is not found, /// then nullptr is returned. @@ -318,12 +398,15 @@ private: PDFOptionalContentProperties m_optionalContentProperties; QSharedPointer m_outlineRoot; PDFActionPtr m_openAction; + std::array m_documentActions; PageLayout m_pageLayout = PageLayout::SinglePage; PageMode m_pageMode = PageMode::UseNone; QByteArray m_baseURI; PDFObject m_formObject; PDFDeveloperExtensions m_extensions; PDFDocumentSecurityStore m_documentSecurityStore; + std::vector m_threads; + PDFObject m_metadata; // Maps from Names dictionary std::map m_destinations; diff --git a/PdfForQtLib/sources/pdfdocument.cpp b/PdfForQtLib/sources/pdfdocument.cpp index bdabb05..3dcaee0 100644 --- a/PdfForQtLib/sources/pdfdocument.cpp +++ b/PdfForQtLib/sources/pdfdocument.cpp @@ -25,20 +25,7 @@ namespace pdf { -// Entries for "Info" entry in trailer dictionary static constexpr const char* PDF_DOCUMENT_INFO_ENTRY = "Info"; -static constexpr const char* PDF_DOCUMENT_INFO_ENTRY_TITLE = "Title"; -static constexpr const char* PDF_DOCUMENT_INFO_ENTRY_AUTHOR = "Author"; -static constexpr const char* PDF_DOCUMENT_INFO_ENTRY_SUBJECT = "Subject"; -static constexpr const char* PDF_DOCUMENT_INFO_ENTRY_KEYWORDS = "Keywords"; -static constexpr const char* PDF_DOCUMENT_INFO_ENTRY_CREATOR = "Creator"; -static constexpr const char* PDF_DOCUMENT_INFO_ENTRY_PRODUCER = "Producer"; -static constexpr const char* PDF_DOCUMENT_INFO_ENTRY_CREATION_DATE = "CreationDate"; -static constexpr const char* PDF_DOCUMENT_INFO_ENTRY_MODIFIED_DATE = "ModDate"; -static constexpr const char* PDF_DOCUMENT_INFO_ENTRY_TRAPPED = "Trapped"; -static constexpr const char* PDF_DOCUMENT_INFO_ENTRY_TRAPPED_TRUE = "True"; -static constexpr const char* PDF_DOCUMENT_INFO_ENTRY_TRAPPED_FALSE = "False"; -static constexpr const char* PDF_DOCUMENT_INFO_ENTRY_TRAPPED_UNKNOWN = "Unknown"; QByteArray PDFObjectStorage::getDecodedStream(const PDFStream* stream) const { @@ -110,123 +97,7 @@ void PDFDocument::initInfo() if (dictionary->hasKey(PDF_DOCUMENT_INFO_ENTRY)) { - const PDFObject& info = getObject(dictionary->get(PDF_DOCUMENT_INFO_ENTRY)); - - if (info.isDictionary()) - { - const PDFDictionary* infoDictionary = info.getDictionary(); - Q_ASSERT(infoDictionary); - - auto readTextString = [this, infoDictionary](const char* entry, QString& fillEntry) - { - if (infoDictionary->hasKey(entry)) - { - const PDFObject& stringObject = getObject(infoDictionary->get(entry)); - if (stringObject.isString()) - { - // We have succesfully read the string, convert it according to encoding - fillEntry = PDFEncoding::convertTextString(stringObject.getString()); - } - else if (!stringObject.isNull()) - { - throw PDFException(tr("Bad format of document info entry in trailer dictionary. String expected.")); - } - } - }; - readTextString(PDF_DOCUMENT_INFO_ENTRY_TITLE, m_info.title); - readTextString(PDF_DOCUMENT_INFO_ENTRY_AUTHOR, m_info.author); - readTextString(PDF_DOCUMENT_INFO_ENTRY_SUBJECT, m_info.subject); - readTextString(PDF_DOCUMENT_INFO_ENTRY_KEYWORDS, m_info.keywords); - readTextString(PDF_DOCUMENT_INFO_ENTRY_CREATOR, m_info.creator); - readTextString(PDF_DOCUMENT_INFO_ENTRY_PRODUCER, m_info.producer); - - auto readDate= [this, infoDictionary](const char* entry, QDateTime& fillEntry) - { - if (infoDictionary->hasKey(entry)) - { - const PDFObject& stringObject = getObject(infoDictionary->get(entry)); - if (stringObject.isString()) - { - // We have succesfully read the string, convert it to date time - fillEntry = PDFEncoding::convertToDateTime(stringObject.getString()); - - if (!fillEntry.isValid()) - { - throw PDFException(tr("Bad format of document info entry in trailer dictionary. String with date time format expected.")); - } - } - else if (!stringObject.isNull()) - { - throw PDFException(tr("Bad format of document info entry in trailer dictionary. String with date time format expected.")); - } - } - }; - readDate(PDF_DOCUMENT_INFO_ENTRY_CREATION_DATE, m_info.creationDate); - readDate(PDF_DOCUMENT_INFO_ENTRY_MODIFIED_DATE, m_info.modifiedDate); - - if (infoDictionary->hasKey(PDF_DOCUMENT_INFO_ENTRY_TRAPPED)) - { - const PDFObject& nameObject = getObject(infoDictionary->get(PDF_DOCUMENT_INFO_ENTRY_TRAPPED)); - if (nameObject.isName()) - { - const QByteArray& name = nameObject.getString(); - if (name == PDF_DOCUMENT_INFO_ENTRY_TRAPPED_TRUE) - { - m_info.trapped = Info::Trapped::True; - } - else if (name == PDF_DOCUMENT_INFO_ENTRY_TRAPPED_FALSE) - { - m_info.trapped = Info::Trapped::False; - } - else if (name == PDF_DOCUMENT_INFO_ENTRY_TRAPPED_UNKNOWN) - { - m_info.trapped = Info::Trapped::Unknown; - } - else - { - throw PDFException(tr("Bad format of document info entry in trailer dictionary. Trapping information expected")); - } - } - else if (nameObject.isBool()) - { - m_info.trapped = nameObject.getBool() ? Info::Trapped::True : Info::Trapped::False; - } - else - { - throw PDFException(tr("Bad format of document info entry in trailer dictionary. Trapping information expected")); - } - } - - // Scan for extra items - constexpr const char* PREDEFINED_ITEMS[] = { PDF_DOCUMENT_INFO_ENTRY_TITLE, PDF_DOCUMENT_INFO_ENTRY_AUTHOR, PDF_DOCUMENT_INFO_ENTRY_SUBJECT, - PDF_DOCUMENT_INFO_ENTRY_KEYWORDS, PDF_DOCUMENT_INFO_ENTRY_CREATOR, PDF_DOCUMENT_INFO_ENTRY_PRODUCER, - PDF_DOCUMENT_INFO_ENTRY_CREATION_DATE, PDF_DOCUMENT_INFO_ENTRY_MODIFIED_DATE, PDF_DOCUMENT_INFO_ENTRY_TRAPPED }; - for (size_t i = 0; i < infoDictionary->getCount(); ++i) - { - QByteArray key = infoDictionary->getKey(i).getString(); - if (std::none_of(std::begin(PREDEFINED_ITEMS), std::end(PREDEFINED_ITEMS), [&key](const char* item) { return item == key; })) - { - const PDFObject& value = getObject(infoDictionary->getValue(i)); - if (value.isString()) - { - const QByteArray& stringValue = value.getString(); - QDateTime dateTime = PDFEncoding::convertToDateTime(stringValue); - if (dateTime.isValid()) - { - m_info.extra[key] = dateTime; - } - else - { - m_info.extra[key] = PDFEncoding::convertTextString(stringValue); - } - } - } - } - } - else if (!info.isNull()) // Info may be invalid... - { - throw PDFException(tr("Bad format of document info entry in trailer dictionary.")); - } + m_info = PDFDocumentInfo::parse(dictionary->get(PDF_DOCUMENT_INFO_ENTRY), &m_pdfObjectStorage); } } diff --git a/PdfForQtLib/sources/pdfdocument.h b/PdfForQtLib/sources/pdfdocument.h index ecbae32..e087352 100644 --- a/PdfForQtLib/sources/pdfdocument.h +++ b/PdfForQtLib/sources/pdfdocument.h @@ -408,35 +408,8 @@ public: const PDFObjectStorage& getStorage() const { return m_pdfObjectStorage; } - /// Info about the document. Title, Author, Keywords... It also stores "extra" - /// values, which are in info dictionary. They can be either strings, or date - /// time (QString or QDateTime). - struct Info - { - /// Indicates, that document was modified that it includes trapping information. - /// See PDF Reference 1.7, Section 10.10.5 "Trapping Support". - enum class Trapped - { - True, ///< Fully trapped - False, ///< Not yet trapped - Unknown ///< Either unknown, or it has been trapped partly, not fully - }; - - QString title; - QString author; - QString subject; - QString keywords; - QString creator; - QString producer; - QDateTime creationDate; - QDateTime modifiedDate; - Trapped trapped = Trapped::Unknown; - PDFVersion version; - std::map extra; - }; - /// Returns info about the document (title, author, etc.) - const Info* getInfo() const { return &m_info; } + const PDFDocumentInfo* getInfo() const { return &m_info; } /// If object is reference, the dereference attempt is performed /// and object is returned. If it is not a reference, then self @@ -494,7 +467,7 @@ private: PDFObjectStorage m_pdfObjectStorage; /// Info about the PDF document - Info m_info; + PDFDocumentInfo m_info; /// Catalog object PDFCatalog m_catalog; diff --git a/PdfForQtLib/sources/pdfstructuretree.cpp b/PdfForQtLib/sources/pdfstructuretree.cpp new file mode 100644 index 0000000..b0a2a89 --- /dev/null +++ b/PdfForQtLib/sources/pdfstructuretree.cpp @@ -0,0 +1,163 @@ +// Copyright (C) 2019-2020 Jakub Melka +// +// This file is part of PdfForQt. +// +// PdfForQt is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// PdfForQt is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with PDFForQt. If not, see . + +#include "pdfstructuretree.h" + +#include + +namespace pdf +{ + +/// Attribute definition structure +struct PDFStructureTreeAttributeDefinition +{ + constexpr inline PDFStructureTreeAttributeDefinition() = default; + constexpr inline PDFStructureTreeAttributeDefinition(PDFStructureTreeAttribute::Attribute type, + const char* name, + bool inheritable) : + type(type), + name(name), + inheritable(inheritable) + { + + } + + /// Returns attribute definition for given attribute name. This function + /// always returns valid pointer. For uknown attribute, it returns + /// user attribute definition. + /// \param name Attribute name + const PDFStructureTreeAttributeDefinition* getDefinition(const QByteArray& name); + + PDFStructureTreeAttribute::Owner getOwnerFromString(const QByteArray& string); + + PDFStructureTreeAttribute::Attribute type = PDFStructureTreeAttribute::Attribute::User; + const char* name = nullptr; + bool inheritable = false; +}; + + +static constexpr std::array, 16> s_ownerDefinitions = +{ + std::pair("Layout", PDFStructureTreeAttribute::Owner::Layout), + std::pair("List", PDFStructureTreeAttribute::Owner::List), + std::pair("PrintField", PDFStructureTreeAttribute::Owner::PrintField), + std::pair("Table", PDFStructureTreeAttribute::Owner::Table), + std::pair("Artifact", PDFStructureTreeAttribute::Owner::Artifact), + std::pair("XML-1.00", PDFStructureTreeAttribute::Owner::XML_1_00), + std::pair("HTML-3.20", PDFStructureTreeAttribute::Owner::HTML_3_20), + std::pair("HTML-4.01", PDFStructureTreeAttribute::Owner::HTML_4_01), + std::pair("HTML-5.00", PDFStructureTreeAttribute::Owner::HTML_5_00), + std::pair("OEB-1.00", PDFStructureTreeAttribute::Owner::OEB_1_00), + std::pair("RTF-1.05", PDFStructureTreeAttribute::Owner::RTF_1_05), + std::pair("CSS-1.00", PDFStructureTreeAttribute::Owner::CSS_1_00), + std::pair("CSS-2.00", PDFStructureTreeAttribute::Owner::CSS_2_00), + std::pair("CSS-3.00", PDFStructureTreeAttribute::Owner::CSS_3_00), + std::pair("RDFa-1.10", PDFStructureTreeAttribute::Owner::RDFa_1_10), + std::pair("ARIA-1.1", PDFStructureTreeAttribute::Owner::ARIA_1_1) +}; + +static constexpr std::array s_attributeDefinitions = +{ + PDFStructureTreeAttributeDefinition(PDFStructureTreeAttribute::Attribute::User, "", false), // User + + // Standard layout attributes + PDFStructureTreeAttributeDefinition(PDFStructureTreeAttribute::Attribute::Placement, "Placement", false), + PDFStructureTreeAttributeDefinition(PDFStructureTreeAttribute::Attribute::WritingMode, "WritingMode", true), + PDFStructureTreeAttributeDefinition(PDFStructureTreeAttribute::Attribute::BackgroundColor, "BackgroundColor", false), + PDFStructureTreeAttributeDefinition(PDFStructureTreeAttribute::Attribute::BorderColor, "BorderColor", true), + PDFStructureTreeAttributeDefinition(PDFStructureTreeAttribute::Attribute::BorderStyle, "BorderStyle", false), + PDFStructureTreeAttributeDefinition(PDFStructureTreeAttribute::Attribute::BorderThickness, "BorderThickness", true), + PDFStructureTreeAttributeDefinition(PDFStructureTreeAttribute::Attribute::Color, "Color", true), + PDFStructureTreeAttributeDefinition(PDFStructureTreeAttribute::Attribute::Padding, "Padding", false), + + PDFStructureTreeAttributeDefinition(PDFStructureTreeAttribute::Attribute::SpaceBefore, "SpaceBefore", false), + PDFStructureTreeAttributeDefinition(PDFStructureTreeAttribute::Attribute::SpaceAfter, "SpaceAfter", false), + PDFStructureTreeAttributeDefinition(PDFStructureTreeAttribute::Attribute::StartIndent, "StartIndent", true), + PDFStructureTreeAttributeDefinition(PDFStructureTreeAttribute::Attribute::EndIndent, "EndIndent", true), + + PDFStructureTreeAttributeDefinition(PDFStructureTreeAttribute::Attribute::TextIndent, "TextIndent", true), + PDFStructureTreeAttributeDefinition(PDFStructureTreeAttribute::Attribute::TextAlign, "TextAlign", true), + PDFStructureTreeAttributeDefinition(PDFStructureTreeAttribute::Attribute::BBox, "BBox", false), + PDFStructureTreeAttributeDefinition(PDFStructureTreeAttribute::Attribute::Width, "Width", false), + PDFStructureTreeAttributeDefinition(PDFStructureTreeAttribute::Attribute::Height, "Height", false), + PDFStructureTreeAttributeDefinition(PDFStructureTreeAttribute::Attribute::BlockAlign, "BlockAlign", true), + PDFStructureTreeAttributeDefinition(PDFStructureTreeAttribute::Attribute::InlineAlign, "InlineAlign", true), + PDFStructureTreeAttributeDefinition(PDFStructureTreeAttribute::Attribute::TBorderStyle, "TBorderStyle", true), + PDFStructureTreeAttributeDefinition(PDFStructureTreeAttribute::Attribute::TPadding, "TPadding", true), + PDFStructureTreeAttributeDefinition(PDFStructureTreeAttribute::Attribute::LineHeight, "LineHeight", true), + PDFStructureTreeAttributeDefinition(PDFStructureTreeAttribute::Attribute::BaselineShift, "BaselineShift", false), + PDFStructureTreeAttributeDefinition(PDFStructureTreeAttribute::Attribute::TextDecorationType, "TextDecorationType", true), + PDFStructureTreeAttributeDefinition(PDFStructureTreeAttribute::Attribute::TextDecorationColor, "TextDecorationColor", true), + PDFStructureTreeAttributeDefinition(PDFStructureTreeAttribute::Attribute::TextDecorationThickness, "TextDecorationThickness", true), + PDFStructureTreeAttributeDefinition(PDFStructureTreeAttribute::Attribute::ColumnCount, "ColumnCount", false), + PDFStructureTreeAttributeDefinition(PDFStructureTreeAttribute::Attribute::ColumnWidths, "ColumnWidths", false), + PDFStructureTreeAttributeDefinition(PDFStructureTreeAttribute::Attribute::ColumnGap, "ColumnGap", false), + PDFStructureTreeAttributeDefinition(PDFStructureTreeAttribute::Attribute::GlyphOrientationVertical, "GlyphOrientationVertical", true), + PDFStructureTreeAttributeDefinition(PDFStructureTreeAttribute::Attribute::RubyAlign, "RubyAlign", true), + PDFStructureTreeAttributeDefinition(PDFStructureTreeAttribute::Attribute::RubyPosition, "RubyPosition", true), + + // List attributes + PDFStructureTreeAttributeDefinition(PDFStructureTreeAttribute::Attribute::ListNumbering, "ListNumbering", true), + PDFStructureTreeAttributeDefinition(PDFStructureTreeAttribute::Attribute::ContinuedList, "ContinuedList", false), + PDFStructureTreeAttributeDefinition(PDFStructureTreeAttribute::Attribute::ContinuedFrom, "ContinuedFrom", false), + + // PrintField attributes + PDFStructureTreeAttributeDefinition(PDFStructureTreeAttribute::Attribute::Role, "Role", false), + PDFStructureTreeAttributeDefinition(PDFStructureTreeAttribute::Attribute::Checked, "Checked", false), + PDFStructureTreeAttributeDefinition(PDFStructureTreeAttribute::Attribute::Checked, "checked", false), + PDFStructureTreeAttributeDefinition(PDFStructureTreeAttribute::Attribute::Desc, "Desc", false), + + // Table attributes + PDFStructureTreeAttributeDefinition(PDFStructureTreeAttribute::Attribute::RowSpan, "RowSpan", false), + PDFStructureTreeAttributeDefinition(PDFStructureTreeAttribute::Attribute::ColSpan, "ColSpan", false), + PDFStructureTreeAttributeDefinition(PDFStructureTreeAttribute::Attribute::Headers, "Headers", false), + PDFStructureTreeAttributeDefinition(PDFStructureTreeAttribute::Attribute::Scope, "Scope", false), + PDFStructureTreeAttributeDefinition(PDFStructureTreeAttribute::Attribute::Short, "Short", false), + + // Artifact attributes + PDFStructureTreeAttributeDefinition(PDFStructureTreeAttribute::Attribute::Type, "Type", false), + PDFStructureTreeAttributeDefinition(PDFStructureTreeAttribute::Attribute::Subtype, "Subtype", false) +}; + +const PDFStructureTreeAttributeDefinition* PDFStructureTreeAttributeDefinition::getDefinition(const QByteArray& name) +{ + for (const PDFStructureTreeAttributeDefinition& definition : s_attributeDefinitions) + { + if (name == definition.name) + { + return &definition; + } + } + + Q_ASSERT(s_attributeDefinitions.front().type == PDFStructureTreeAttribute::Attribute::User); + return &s_attributeDefinitions.front(); +} + +PDFStructureTreeAttribute::Owner PDFStructureTreeAttributeDefinition::getOwnerFromString(const QByteArray& string) +{ + for (const auto& item : s_ownerDefinitions) + { + if (string == item.first) + { + return item.second; + } + } + + return PDFStructureTreeAttribute::Owner::User; +} + +} // namespace pdf diff --git a/PdfForQtLib/sources/pdfstructuretree.h b/PdfForQtLib/sources/pdfstructuretree.h new file mode 100644 index 0000000..57cf765 --- /dev/null +++ b/PdfForQtLib/sources/pdfstructuretree.h @@ -0,0 +1,132 @@ +// Copyright (C) 2019-2020 Jakub Melka +// +// This file is part of PdfForQt. +// +// PdfForQt is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// PdfForQt is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with PDFForQt. If not, see . + +#ifndef PDFSTRUCTURETREE_H +#define PDFSTRUCTURETREE_H + +#include "pdfobject.h" + +namespace pdf +{ + +struct PDFStructureTreeAttributeDefinition; + +class PDFStructureTreeAttribute +{ +public: + + enum class Owner + { + /// Defined for user owner + User, + + Layout, + List, + PrintField, + Table, + + Artifact, + + XML_1_00, + HTML_3_20, + HTML_4_01, + HTML_5_00, + OEB_1_00, + RTF_1_05, + CSS_1_00, + CSS_2_00, + CSS_3_00, + RDFa_1_10, + ARIA_1_1, + }; + + enum Attribute + { + User, + + // Standard layout attributes + Placement, + WritingMode, + BackgroundColor, + BorderColor, + BorderStyle, + BorderThickness, + Color, + Padding, + + // Block element attributes + SpaceBefore, + SpaceAfter, + StartIndent, + EndIndent, + TextIndent, + TextAlign, + BBox, + Width, + Height, + BlockAlign, + InlineAlign, + TBorderStyle, + TPadding, + LineHeight, + BaselineShift, + TextDecorationType, + TextDecorationColor, + TextDecorationThickness, + ColumnCount, + ColumnWidths, + ColumnGap, + GlyphOrientationVertical, + RubyAlign, + RubyPosition, + + // List attributes + ListNumbering, + ContinuedList, + ContinuedFrom, + + // PrintField attributes + Role, + Checked, + Desc, + + // Table attributes + RowSpan, + ColSpan, + Headers, + Scope, + Short, + + // Artifact attributes + Type, + Subtype, + + LastAttribute + }; + +private: +}; + +class PDFStructureTree +{ +public: + PDFStructureTree(); +}; + +} // namespace pdf + +#endif // PDFSTRUCTURETREE_H diff --git a/PdfForQtViewer/pdfdocumentpropertiesdialog.cpp b/PdfForQtViewer/pdfdocumentpropertiesdialog.cpp index bcce0fe..376a3f4 100644 --- a/PdfForQtViewer/pdfdocumentpropertiesdialog.cpp +++ b/PdfForQtViewer/pdfdocumentpropertiesdialog.cpp @@ -70,7 +70,7 @@ void PDFDocumentPropertiesDialog::initializeProperties(const pdf::PDFDocument* d // Initialize document properties QTreeWidgetItem* propertiesRoot = new QTreeWidgetItem({ tr("Properties") }); - const pdf::PDFDocument::Info* info = document->getInfo(); + const pdf::PDFDocumentInfo* info = document->getInfo(); const pdf::PDFCatalog* catalog = document->getCatalog(); new QTreeWidgetItem(propertiesRoot, { tr("PDF version"), QString::fromLatin1(document->getVersion()) }); new QTreeWidgetItem(propertiesRoot, { tr("Title"), info->title }); @@ -85,15 +85,15 @@ void PDFDocumentPropertiesDialog::initializeProperties(const pdf::PDFDocument* d QString trapped; switch (info->trapped) { - case pdf::PDFDocument::Info::Trapped::True: + case pdf::PDFDocumentInfo::Trapped::True: trapped = tr("True"); break; - case pdf::PDFDocument::Info::Trapped::False: + case pdf::PDFDocumentInfo::Trapped::False: trapped = tr("False"); break; - case pdf::PDFDocument::Info::Trapped::Unknown: + case pdf::PDFDocumentInfo::Trapped::Unknown: trapped = tr("Unknown"); break;