diff --git a/PdfForQtLib/PdfForQtLib.pro b/PdfForQtLib/PdfForQtLib.pro index a03d561..95b3919 100644 --- a/PdfForQtLib/PdfForQtLib.pro +++ b/PdfForQtLib/PdfForQtLib.pro @@ -61,6 +61,7 @@ SOURCES += \ sources/pdfoptimizer.cpp \ sources/pdfoptionalcontent.cpp \ sources/pdfoutline.cpp \ + sources/pdfpagenavigation.cpp \ sources/pdfpagetransition.cpp \ sources/pdfparser.cpp \ sources/pdfdocument.cpp \ @@ -116,6 +117,7 @@ HEADERS += \ sources/pdfoptimizer.h \ sources/pdfoptionalcontent.h \ sources/pdfoutline.h \ + sources/pdfpagenavigation.h \ sources/pdfpagetransition.h \ sources/pdfpainterutils.h \ sources/pdfparser.h \ diff --git a/PdfForQtLib/sources/pdfaction.h b/PdfForQtLib/sources/pdfaction.h index 6223d68..ec75ea3 100644 --- a/PdfForQtLib/sources/pdfaction.h +++ b/PdfForQtLib/sources/pdfaction.h @@ -73,7 +73,10 @@ enum class DestinationType }; /// Destination to the specific location of the document. Destination can also be 'Named' type, -/// in this case, destination in name tree is found and used. +/// in this case, destination in name tree is found and used. Important note: because structure +/// destination has almost exactly same syntax as page destination, it should be checked, +/// if indirect reference returned by function \p getPageReference references really page, +/// or some structure element. class PDFDestination { public: diff --git a/PdfForQtLib/sources/pdfcatalog.cpp b/PdfForQtLib/sources/pdfcatalog.cpp index d42fdae..bd17b43 100644 --- a/PdfForQtLib/sources/pdfcatalog.cpp +++ b/PdfForQtLib/sources/pdfcatalog.cpp @@ -575,6 +575,11 @@ PDFViewerPreferences PDFViewerPreferences::parse(const PDFObject& catalogDiction throw PDFException(PDFTranslationContext::tr("Expected integer.")); } } + + // Enforce + PDFDocumentDataLoaderDecorator loader(document); + std::vector enforce = loader.readNameArrayFromDictionary(viewerPreferencesDictionary, "Enforce"); + result.m_optionFlags.setFlag(EnforcePrintScaling, std::find(enforce.cbegin(), enforce.cend(), "PrintScaling") != enforce.cend()); } else if (!viewerPreferencesObject.isNull()) { diff --git a/PdfForQtLib/sources/pdfcatalog.h b/PdfForQtLib/sources/pdfcatalog.h index ba2109b..c3f52a3 100644 --- a/PdfForQtLib/sources/pdfcatalog.h +++ b/PdfForQtLib/sources/pdfcatalog.h @@ -150,7 +150,8 @@ public: FitWindow = 0x0008, ///< Resize window to fit first displayed page CenterWindow = 0x0010, ///< Position of the document's window should be centered on the screen DisplayDocTitle = 0x0020, ///< Display documents title instead of file name (introduced in PDF 1.4) - PickTrayByPDFSize = 0x0040 ///< Pick tray by PDF size (printing option) + PickTrayByPDFSize = 0x0040, ///< Pick tray by PDF size (printing option) + EnforcePrintScaling = 0x0080 ///< Enforce print scaling settings }; Q_DECLARE_FLAGS(OptionFlags, OptionFlag) diff --git a/PdfForQtLib/sources/pdffile.cpp b/PdfForQtLib/sources/pdffile.cpp index 75f6025..295afdf 100644 --- a/PdfForQtLib/sources/pdffile.cpp +++ b/PdfForQtLib/sources/pdffile.cpp @@ -470,6 +470,48 @@ PDFCollectionFolder PDFCollectionFolder::parse(const PDFObjectStorage* storage, return result; } +PDFCollectionNavigator PDFCollectionNavigator::parse(const PDFObjectStorage* storage, PDFObject object) +{ + PDFCollectionNavigator result; + + if (const PDFDictionary* dictionary = storage->getDictionaryFromObject(object)) + { + PDFDocumentDataLoaderDecorator loader(storage); + + auto getLayout = [&loader](const PDFObject& object) + { + constexpr const std::array layouts = { + std::pair{ "D", Layout::Details }, + std::pair{ "T", Layout::Tile }, + std::pair{ "H", Layout::Hidden }, + std::pair{ "FilmStrip", Layout::FilmStrip }, + std::pair{ "FreeForm", Layout::FreeForm }, + std::pair{ "Linear", Layout::Linear }, + std::pair{ "Tree", Layout::Tree } + }; + + return loader.readEnumByName(object, layouts.begin(), layouts.end(), Layout::Invalid); + }; + + result.m_layouts = None; + + PDFObject layoutObject = storage->getObject(dictionary->get("Layout")); + if (layoutObject.isArray()) + { + for (const PDFObject& object : *layoutObject.getArray()) + { + result.m_layouts |= getLayout(object); + } + } + else + { + result.m_layouts = getLayout(layoutObject); + } + } + + return result; +} + QString PDFCollectionItem::getString(const QByteArray& key, const PDFObjectStorage* storage) const { if (const PDFDictionary* dictionary = storage->getDictionaryFromObject(m_object)) diff --git a/PdfForQtLib/sources/pdffile.h b/PdfForQtLib/sources/pdffile.h index 994d122..ca9164a 100644 --- a/PdfForQtLib/sources/pdffile.h +++ b/PdfForQtLib/sources/pdffile.h @@ -314,6 +314,39 @@ private: PDFObject m_object; }; +/// Collection navigator. It contains modes of display. Interactive +/// PDF processor should display first layout it is capable of. +class PDFFORQTLIBSHARED_EXPORT PDFCollectionNavigator +{ +public: + explicit inline PDFCollectionNavigator() = default; + + enum Layout + { + None = 0x0000, + Invalid = 0x0001, + Details = 0x0002, + Tile = 0x0004, + Hidden = 0x0008, + FilmStrip = 0x0010, + FreeForm = 0x0020, + Linear = 0x0040, + Tree = 0x0080 + }; + Q_DECLARE_FLAGS(Layouts, Layout) + + /// Returns true, if navigator has valid layout + bool hasLayout() const { return (m_layouts != None) && !m_layouts.testFlag(Invalid); } + + /// Returns current layouts + Layouts getLayout() const { return m_layouts; } + + static PDFCollectionNavigator parse(const PDFObjectStorage* storage, PDFObject object); + +private: + Layouts m_layouts = None; +}; + class PDFFORQTLIBSHARED_EXPORT PDFEmbeddedFile { public: diff --git a/PdfForQtLib/sources/pdfpagenavigation.cpp b/PdfForQtLib/sources/pdfpagenavigation.cpp new file mode 100644 index 0000000..2a38fe0 --- /dev/null +++ b/PdfForQtLib/sources/pdfpagenavigation.cpp @@ -0,0 +1,174 @@ +// Copyright (C) 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 "pdfpagenavigation.h" + +namespace pdf +{ + +PDFPageNavigation::PDFPageNavigation(const PDFDocument* document, QObject* parent) : + QObject(parent), + m_currentPageIndex(0), + m_document(document) +{ + if (m_document && m_document->getCatalog()->getPageCount() > 0) + { + navigateToPage(0); + } +} + +void PDFPageNavigation::navigateToPage(size_t pageIndex) +{ + if (m_currentPageIndex == pageIndex) + { + // Jakub Melka: We are at the same page! Do nothing. + return; + } + + const bool isForward = pageIndex > m_currentPageIndex; + const PDFObjectStorage* const storage = &m_document->getStorage(); + const PDFPage* page = m_document->getCatalog()->getPage(pageIndex); + + // We will perform steps as in PDF 2.0 specification, chapter 12.4.4.2 + // Step a) navigate to page and set current navigation node + m_currentPageIndex = pageIndex; + m_transition = PDFPageTransition::parse(storage, page->getTransition(storage)); + m_currentNavigationNode = PDFNavigationNode::parse(storage, page->getFirstSubpageNavigationNode(storage)); + + // Step b) Execute initially page navigation node. If we are navigating forward, + // then "next" action should be executed and current node should be changed to next. + // If we are navigating backward, then "previous" action should be executed + // and current node should be changed to previous. + executeNavigationNode(isForward ? Direction::Forward : Direction::Backward); + + // Step c) Navigate to new page - send navigation request. + emit pageChangeRequest(m_currentPageIndex, &m_transition); +} + +void PDFPageNavigation::navigateForward() +{ + if (m_currentNavigationNode) + { + executeNavigationNode(Direction::Forward); + } + else if (m_currentPageIndex + 1 < m_document->getCatalog()->getPageCount()) + { + navigateToPage(m_currentPageIndex + 1); + } +} + +void PDFPageNavigation::navigateBackward() +{ + if (m_currentNavigationNode) + { + executeNavigationNode(Direction::Backward); + } + else if (m_currentPageIndex > 0) + { + navigateToPage(m_currentPageIndex - 1); + } +} + +void PDFPageNavigation::executeNavigationNode(Direction direction) +{ + if (!m_currentNavigationNode) + { + return; + } + + // We will make a copy of object, because this object can change, + // when actions are executed. + PDFNavigationNode copy = *m_currentNavigationNode; + m_currentNavigationNode = PDFNavigationNode::parse(&m_document->getStorage(), m_document->getObjectByReference((direction == Direction::Forward) ? copy.getNextNode() : copy.getPreviousNode())); + + switch (direction) + { + case Direction::Forward: + { + if (const PDFAction* action = copy.getNextAction().get()) + { + for (const PDFAction* currentAction : action->getActionList()) + { + emit actionTriggered(currentAction); + } + } + break; + } + + case Direction::Backward: + { + if (const PDFAction* action = copy.getPreviousAction().get()) + { + for (const PDFAction* currentAction : action->getActionList()) + { + emit actionTriggered(currentAction); + } + } + break; + } + + default: + Q_ASSERT(false); + break; + } +} + +PDFActionPtr PDFNavigationNode::getPreviousAction() const +{ + return m_previousAction; +} + +PDFActionPtr PDFNavigationNode::getNextAction() const +{ + return m_nextAction; +} + +PDFObjectReference PDFNavigationNode::getPreviousNode() const +{ + return m_previousNode; +} + +PDFObjectReference PDFNavigationNode::getNextNode() const +{ + return m_nextNode; +} + +PDFReal PDFNavigationNode::getDuration() const +{ + return m_duration; +} + +std::optional PDFNavigationNode::parse(const PDFObjectStorage* storage, PDFObject object) +{ + std::optional result; + + if (const PDFDictionary* dictionary = storage->getDictionaryFromObject(object)) + { + PDFDocumentDataLoaderDecorator loader(storage); + + result = PDFNavigationNode(); + result->m_nextAction = PDFAction::parse(storage, dictionary->get("NA")); + result->m_previousAction = PDFAction::parse(storage, dictionary->get("PA")); + result->m_previousNode = loader.readReferenceFromDictionary(dictionary, "Prev"); + result->m_nextNode = loader.readReferenceFromDictionary(dictionary, "Next"); + result->m_duration = loader.readNumberFromDictionary(dictionary, "Dur", 0.0); + } + + return result; +} + +} // namespace pdf diff --git a/PdfForQtLib/sources/pdfpagenavigation.h b/PdfForQtLib/sources/pdfpagenavigation.h new file mode 100644 index 0000000..64f9880 --- /dev/null +++ b/PdfForQtLib/sources/pdfpagenavigation.h @@ -0,0 +1,112 @@ +// Copyright (C) 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 PDFPAGENAVIGATION_H +#define PDFPAGENAVIGATION_H + +#include "pdfpage.h" +#include "pdfcatalog.h" +#include "pdfdocument.h" +#include "pdfpagetransition.h" +#include "pdfaction.h" + +#include + +namespace pdf +{ + +/// Page navigation node for subpage navigation. +class PDFNavigationNode +{ +public: + explicit PDFNavigationNode() = default; + + PDFActionPtr getPreviousAction() const; + PDFActionPtr getNextAction() const; + + PDFObjectReference getPreviousNode() const; + PDFObjectReference getNextNode() const; + + PDFReal getDuration() const; + + /// Parses navigation node. If error occurs, then empty optional + /// item is returned. + /// \param storage Storage + /// \param object Object + static std::optional parse(const PDFObjectStorage* storage, PDFObject object); + +private: + PDFActionPtr m_previousAction; + PDFActionPtr m_nextAction; + PDFObjectReference m_previousNode; + PDFObjectReference m_nextNode; + PDFReal m_duration = 0.0; +}; + +/// Navigation object, which helps to navigate trough document. +/// It also handles subpage navigation, if subpage navigation +/// steps are present. +class PDFFORQTLIBSHARED_EXPORT PDFPageNavigation : public QObject +{ + Q_OBJECT + +public: + explicit PDFPageNavigation(const PDFDocument* document, QObject* parent); + + /// Navigates to specified page. Page index must be valid. + /// \param pageIndex Page index + void navigateToPage(size_t pageIndex); + + /// Navigates forward. If subpage navigation is present, + /// navigates to next page navigation node. Otherwise + /// navigates to next page. + void navigateForward(); + + /// Navigates backward. If subpage navigation is present, + /// navigates to previous page navigation node. Otherwise + /// navigates to previous page. + void navigateBackward(); + +signals: + /// This signal is emitted, if subpage navigation is requested. + void actionTriggered(const PDFAction* action); + + /// This signal is emitted, if navigation to another page is required. + /// Page index is always valid. If document contains no pages, then + /// this signal is never emitted. Also, transition is always a valid + /// object (not null pointer). + void pageChangeRequest(const size_t pageIndex, const PDFPageTransition* transition); + +private: + + enum class Direction + { + Forward, ///< We navigate forward + Backward ///< We navigate backward + }; + + void executeNavigationNode(Direction direction); + + size_t m_currentPageIndex; + std::optional m_currentNavigationNode; + PDFPageTransition m_transition; + const PDFDocument* m_document; +}; + +} // namespace pdf + +#endif // PDFPAGENAVIGATION_H