diff --git a/PdfForQtLib/PdfForQtLib.pro b/PdfForQtLib/PdfForQtLib.pro index 419990e..7a1d3be 100644 --- a/PdfForQtLib/PdfForQtLib.pro +++ b/PdfForQtLib/PdfForQtLib.pro @@ -81,6 +81,7 @@ HEADERS += \ sources/pdfjbig2decoder.h \ sources/pdfmeshqualitysettings.h \ sources/pdfmultimedia.h \ + sources/pdfnametreeloader.h \ sources/pdfobject.h \ sources/pdfoptionalcontent.h \ sources/pdfoutline.h \ diff --git a/PdfForQtLib/sources/pdfaction.cpp b/PdfForQtLib/sources/pdfaction.cpp index d2ca276..dadcb43 100644 --- a/PdfForQtLib/sources/pdfaction.cpp +++ b/PdfForQtLib/sources/pdfaction.cpp @@ -39,6 +39,13 @@ void PDFAction::apply(const std::function& callback) } } +std::vector PDFAction::getActionList() const +{ + std::vector result; + fillActionList(result); + return result; +} + PDFActionPtr PDFAction::parseImpl(const PDFDocument* document, PDFObject object, std::set& usedReferences) { if (object.isReference()) @@ -273,10 +280,37 @@ PDFActionPtr PDFAction::parseImpl(const PDFDocument* document, PDFObject object, { return PDFActionPtr(new PDFActionGoTo3DView(dictionary->get("TA"), dictionary->get("V"))); } + else if (name == "JavaScript") + { + QByteArray textJavaScript; + const PDFObject& javaScriptObject = document->getObject(dictionary->get("JS")); + if (javaScriptObject.isString()) + { + textJavaScript = javaScriptObject.getString(); + } + else if (javaScriptObject.isStream()) + { + textJavaScript = document->getDecodedStream(javaScriptObject.getStream()); + } + return PDFActionPtr(new PDFActionJavaScript(PDFEncoding::convertTextString(textJavaScript))); + } return PDFActionPtr(); } +void PDFAction::fillActionList(std::vector& actionList) const +{ + actionList.push_back(this); + + for (const PDFActionPtr& actionPointer : m_nextActions) + { + if (actionPointer) + { + actionPointer->fillActionList(actionList); + } + } +} + PDFDestination PDFDestination::parse(const PDFDocument* document, PDFObject object) { PDFDestination result; diff --git a/PdfForQtLib/sources/pdfaction.h b/PdfForQtLib/sources/pdfaction.h index 5144f46..700c231 100644 --- a/PdfForQtLib/sources/pdfaction.h +++ b/PdfForQtLib/sources/pdfaction.h @@ -50,7 +50,8 @@ enum class ActionType SetOCGState, Rendition, Transition, - GoTo3DView + GoTo3DView, + JavaScript }; enum class DestinationType @@ -105,7 +106,7 @@ private: using PDFActionPtr = QSharedPointer; /// Base class for action types. -class PDFAction +class PDFFORQTLIBSHARED_EXPORT PDFAction { public: explicit PDFAction() = default; @@ -127,9 +128,14 @@ public: /// the 'Next' entry, as described by PDF 1.7 specification. void apply(const std::function& callback); + /// Returns list of actions to be executed + std::vector getActionList() const; + private: static PDFActionPtr parseImpl(const PDFDocument* document, PDFObject object, std::set& usedReferences); + void fillActionList(std::vector& actionList) const; + std::vector m_nextActions; }; @@ -490,6 +496,23 @@ private: PDFObject m_view; }; +class PDFActionJavaScript : public PDFAction +{ +public: + explicit PDFActionJavaScript(const QString& javaScript) : + m_javaScript(javaScript) + { + + } + + virtual ActionType getType() const override { return ActionType::JavaScript; } + + const QString& getJavaScript() const { return m_javaScript; } + +private: + QString m_javaScript; +}; + } // namespace pdf #endif // PDFACTION_H diff --git a/PdfForQtLib/sources/pdfcatalog.cpp b/PdfForQtLib/sources/pdfcatalog.cpp index eb0de9c..b2c6a15 100644 --- a/PdfForQtLib/sources/pdfcatalog.cpp +++ b/PdfForQtLib/sources/pdfcatalog.cpp @@ -19,6 +19,7 @@ #include "pdfdocument.h" #include "pdfexception.h" #include "pdfnumbertreeloader.h" +#include "pdfnametreeloader.h" namespace pdf { @@ -42,6 +43,28 @@ static constexpr const char* PDF_VIEWER_PREFERENCES_PICK_TRAY_BY_PDF_SIZE = "Pic static constexpr const char* PDF_VIEWER_PREFERENCES_NUMBER_OF_COPIES = "NumCopies"; static constexpr const char* PDF_VIEWER_PREFERENCES_PRINT_PAGE_RANGE = "PrintPageRange"; +size_t PDFCatalog::getPageIndexFromPageReference(PDFObjectReference reference) const +{ + auto it = std::find_if(m_pages.cbegin(), m_pages.cend(), [reference](const PDFPage& page) { return page.getPageReference() == reference; }); + if (it != m_pages.cend()) + { + return std::distance(m_pages.cbegin(), it); + } + + return INVALID_PAGE_INDEX; +} + +const PDFDestination* PDFCatalog::getDestination(const QByteArray& key) const +{ + auto it = m_destinations.find(key); + if (it != m_destinations.cend()) + { + return &it->second; + } + + return nullptr; +} + PDFCatalog PDFCatalog::parse(const PDFObject& catalog, const PDFDocument* document) { if (!catalog.isDictionary()) @@ -109,6 +132,41 @@ PDFCatalog PDFCatalog::parse(const PDFObject& catalog, const PDFDocument* docume catalogObject.m_pageMode = loader.readEnumByName(catalogDictionary->get("PageMode"), pageModes.begin(), pageModes.end(), PageMode::UseNone); } + catalogObject.m_version = loader.readNameFromDictionary(catalogDictionary, "Version"); + + if (const PDFDictionary* namesDictionary = document->getDictionaryFromObject(catalogDictionary->get("Names"))) + { + auto parseDestination = [](const PDFDocument* document, PDFObject object) + { + object = document->getObject(object); + if (object.isDictionary()) + { + object = object.getDictionary()->get("D"); + } + return PDFDestination::parse(document, qMove(object)); + }; + + catalogObject.m_destinations = PDFNameTreeLoader::parse(document, namesDictionary->get("Dests"), parseDestination); + catalogObject.m_javaScriptActions = PDFNameTreeLoader::parse(document, namesDictionary->get("JavaScript"), &PDFAction::parse); + catalogObject.m_embeddedFiles = PDFNameTreeLoader::parse(document, namesDictionary->get("EmbeddedFiles"), &PDFFileSpecification::parse); + } + + // Examine "Dests" dictionary + if (const PDFDictionary* destsDictionary = document->getDictionaryFromObject(catalogDictionary->get("Dests"))) + { + const size_t count = destsDictionary->getCount(); + for (size_t i = 0; i < count; ++i) + { + catalogObject.m_destinations[destsDictionary->getKey(i)] = PDFDestination::parse(document, destsDictionary->getValue(i)); + } + } + + // Examine "URI" dictionary + if (const PDFDictionary* URIDictionary = document->getDictionaryFromObject(catalogDictionary->get("URI"))) + { + catalogObject.m_baseURI = loader.readStringFromDictionary(URIDictionary, "Base"); + } + return catalogObject; } diff --git a/PdfForQtLib/sources/pdfcatalog.h b/PdfForQtLib/sources/pdfcatalog.h index c7c937f..ea5dbb8 100644 --- a/PdfForQtLib/sources/pdfcatalog.h +++ b/PdfForQtLib/sources/pdfcatalog.h @@ -194,7 +194,7 @@ private: PDFInteger m_numberOfCopies = 1; }; -class PDFCatalog +class PDFFORQTLIBSHARED_EXPORT PDFCatalog { public: constexpr inline PDFCatalog() = default; @@ -205,6 +205,8 @@ public: constexpr inline PDFCatalog& operator=(const PDFCatalog&) = default; constexpr inline PDFCatalog& operator=(PDFCatalog&&) = default; + static constexpr const size_t INVALID_PAGE_INDEX = std::numeric_limits::max(); + /// Returns viewer preferences of the application const PDFViewerPreferences* getViewerPreferences() const { return &m_viewerPreferences; } @@ -214,6 +216,9 @@ public: /// Returns the page const PDFPage* getPage(size_t index) const { return &m_pages.at(index); } + /// Returns page index. If page is not found, then INVALID_PAGE_INDEX is returned. + size_t getPageIndexFromPageReference(PDFObjectReference reference) const; + /// Returns optional content properties const PDFOptionalContentProperties* getOptionalContentProperties() const { return &m_optionalContentProperties; } @@ -223,14 +228,25 @@ public: /// Returns action, which should be performed const PDFAction* getOpenAction() const { return m_openAction.data(); } + /// Returns version of the PDF specification, to which the document conforms. + const QByteArray& getVersion() const { return m_version; } + PageLayout getPageLayout() const { return m_pageLayout; } PageMode getPageMode() const { return m_pageMode; } + const QByteArray& getBaseURI() const { return m_baseURI; } + + /// Returns destination using the key. If destination with the key is not found, + /// then nullptr is returned. + /// \param key Destination key + /// \returns Pointer to the destination, or nullptr + const PDFDestination* getDestination(const QByteArray& key) const; /// Parses catalog from catalog dictionary. If object cannot be parsed, or error occurs, /// then exception is thrown. static PDFCatalog parse(const PDFObject& catalog, const PDFDocument* document); private: + QByteArray m_version; PDFViewerPreferences m_viewerPreferences; std::vector m_pages; std::vector m_pageLabels; @@ -239,6 +255,12 @@ private: PDFActionPtr m_openAction; PageLayout m_pageLayout = PageLayout::SinglePage; PageMode m_pageMode = PageMode::UseNone; + QByteArray m_baseURI; + + // Maps from Names dictionary + std::map m_destinations; + std::map m_javaScriptActions; + std::map m_embeddedFiles; }; } // namespace pdf diff --git a/PdfForQtLib/sources/pdfencoding.h b/PdfForQtLib/sources/pdfencoding.h index 8b3281c..6557547 100644 --- a/PdfForQtLib/sources/pdfencoding.h +++ b/PdfForQtLib/sources/pdfencoding.h @@ -18,6 +18,8 @@ #ifndef PDFENCODING_H #define PDFENCODING_H +#include "pdfglobal.h" + #include #include @@ -33,7 +35,7 @@ using EncodingTable = std::array; /// This class can convert byte stream to the QString in unicode encoding. /// PDF has several encodings, see PDF Reference 1.7, Appendix D. -class PDFEncoding +class PDFFORQTLIBSHARED_EXPORT PDFEncoding { public: explicit PDFEncoding() = delete; diff --git a/PdfForQtLib/sources/pdffile.h b/PdfForQtLib/sources/pdffile.h index fd7f61e..c20e925 100644 --- a/PdfForQtLib/sources/pdffile.h +++ b/PdfForQtLib/sources/pdffile.h @@ -45,7 +45,7 @@ private: }; /// File specification -class PDFFileSpecification +class PDFFORQTLIBSHARED_EXPORT PDFFileSpecification { public: explicit PDFFileSpecification() = default; diff --git a/PdfForQtLib/sources/pdfnametreeloader.h b/PdfForQtLib/sources/pdfnametreeloader.h new file mode 100644 index 0000000..a996e6e --- /dev/null +++ b/PdfForQtLib/sources/pdfnametreeloader.h @@ -0,0 +1,94 @@ +// Copyright (C) 2019 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 PDFNAMETREELOADER_H +#define PDFNAMETREELOADER_H + +#include "pdfdocument.h" + +#include +#include + +namespace pdf +{ + +/// This class can load a number tree into the array +template +class PDFNameTreeLoader +{ +public: + explicit PDFNameTreeLoader() = delete; + + using MappedObjects = std::map; + using LoadMethod = std::function; + + /// Parses the name tree and loads its items into the map. Some errors are ignored, + /// e.g. when kid is null. Objects are retrieved by \p loadMethod. + /// \param document Document + /// \param root Root of the name tree + /// \param loadMethod Parsing method, which retrieves parsed object + static MappedObjects parse(const PDFDocument* document, const PDFObject& root, const LoadMethod& loadMethod) + { + MappedObjects result; + parseImpl(result, document, root, loadMethod); + return result; + } + +private: + static void parseImpl(MappedObjects& objects, const PDFDocument* document, const PDFObject& root, const LoadMethod& loadMethod) + { + if (const PDFDictionary* dictionary = document->getDictionaryFromObject(root)) + { + // Jakub Melka: First, load the objects into the map + const PDFObject& namedItems = document->getObject(dictionary->get("Names")); + if (namedItems.isArray()) + { + const PDFArray* namedItemsArray = namedItems.getArray(); + const size_t count = namedItemsArray->getCount() / 2; + for (size_t i = 0; i < count; ++i) + { + const size_t numberIndex = 2 * i; + const size_t valueIndex = 2 * i + 1; + + const PDFObject& name = document->getObject(namedItemsArray->getItem(numberIndex)); + if (!name.isString()) + { + continue; + } + + objects[name.getString()] = loadMethod(document, namedItemsArray->getItem(valueIndex)); + } + } + + // Then, follow the kids + const PDFObject& kids = document->getObject(dictionary->get("Kids")); + if (kids.isArray()) + { + const PDFArray* kidsArray = kids.getArray(); + const size_t count = kidsArray->getCount(); + for (size_t i = 0; i < count; ++i) + { + parseImpl(objects, document, kidsArray->getItem(i), loadMethod); + } + } + } + } +}; + +} // namespace pdf + +#endif // PDFNAMETREELOADER_H diff --git a/PdfForQtLib/sources/pdfnumbertreeloader.h b/PdfForQtLib/sources/pdfnumbertreeloader.h index 152ac86..f4bfdbe 100644 --- a/PdfForQtLib/sources/pdfnumbertreeloader.h +++ b/PdfForQtLib/sources/pdfnumbertreeloader.h @@ -53,11 +53,8 @@ public: private: static void parseImpl(Objects& objects, const PDFDocument* document, const PDFObject& root) { - const PDFObject& dereferencedRoot = document->getObject(root); - if (dereferencedRoot.isDictionary()) + if (const PDFDictionary* dictionary = document->getDictionaryFromObject(root)) { - const PDFDictionary* dictionary = dereferencedRoot.getDictionary(); - // First, load the objects into the array const PDFObject& numberedItems = document->getObject(dictionary->get("Nums")); if (numberedItems.isArray()) diff --git a/PdfForQtLib/sources/pdfoptionalcontent.cpp b/PdfForQtLib/sources/pdfoptionalcontent.cpp index 712fea1..7850612 100644 --- a/PdfForQtLib/sources/pdfoptionalcontent.cpp +++ b/PdfForQtLib/sources/pdfoptionalcontent.cpp @@ -343,13 +343,13 @@ OCState PDFOptionalContentActivity::getState(PDFObjectReference ocg) const return OCState::Unknown; } -void PDFOptionalContentActivity::setState(PDFObjectReference ocg, OCState state) +void PDFOptionalContentActivity::setState(PDFObjectReference ocg, OCState state, bool preserveRadioButtons) { auto it = m_states.find(ocg); if (it != m_states.cend() && it->second != state) { // We are changing the state. If new state is ON, then we must check radio button groups. - if (state == OCState::ON) + if (state == OCState::ON && preserveRadioButtons) { for (const std::vector& radioButtonGroup : m_properties->getDefaultConfiguration().getRadioButtonGroups()) { diff --git a/PdfForQtLib/sources/pdfoptionalcontent.h b/PdfForQtLib/sources/pdfoptionalcontent.h index 4cd1f2c..a01e3ab 100644 --- a/PdfForQtLib/sources/pdfoptionalcontent.h +++ b/PdfForQtLib/sources/pdfoptionalcontent.h @@ -164,18 +164,20 @@ public: /// Gets the optional content groups state. If optional content group doesn't exist, /// then it returns Unknown state. - /// \param ocg Optional conteng group + /// \param ocg Optional content group OCState getState(PDFObjectReference ocg) const; /// Sets the state of optional content group. If optional content group doesn't exist, /// then nothing happens. If optional content group is contained in radio button group, then /// all other optional content groups in the group are switched off, if we are /// switching this one to ON state. If we are switching it off, then nothing happens (as all - /// optional content groups in radio button group can be switched off). + /// optional content groups in radio button group can be switched off). This behaviour can be + /// controlled via parameter \p preserveRadioButtons. /// \param ocg Optional content group /// \param state New state of the optional content group + /// \param preserveRadioButtons Switch off other radio buttons in group? /// \note If something changed, then signal \p optionalContentGroupStateChanged is emitted. - void setState(PDFObjectReference ocg, OCState state); + void setState(PDFObjectReference ocg, OCState state, bool preserveRadioButtons = true); /// Applies configuration to the current state of optional content groups void applyConfiguration(const PDFOptionalContentConfiguration& configuration); diff --git a/PdfForQtLib/sources/pdfpage.cpp b/PdfForQtLib/sources/pdfpage.cpp index e64cc35..2b4e3a4 100644 --- a/PdfForQtLib/sources/pdfpage.cpp +++ b/PdfForQtLib/sources/pdfpage.cpp @@ -150,6 +150,7 @@ void PDFPage::parseImpl(std::vector& pages, const PDFDocument* document) { // Are we in internal node, or leaf (page object)? + PDFObjectReference objectReference = root.isReference() ? root.getReference() : PDFObjectReference(); const PDFObject& dereferenced = document->getObject(root); if (dereferenced.isDictionary()) @@ -198,6 +199,7 @@ void PDFPage::parseImpl(std::vector& pages, { PDFPage page; + page.m_pageReference = objectReference; page.m_mediaBox = currentInheritableAttributes.getMediaBox(); page.m_cropBox = currentInheritableAttributes.getCropBox(); page.m_resources = document->getObject(currentInheritableAttributes.getResources()); diff --git a/PdfForQtLib/sources/pdfpage.h b/PdfForQtLib/sources/pdfpage.h index 83b5b3a..675e7e3 100644 --- a/PdfForQtLib/sources/pdfpage.h +++ b/PdfForQtLib/sources/pdfpage.h @@ -94,6 +94,8 @@ public: inline QRectF getTrimBoxMM() const { return getRectMM(m_trimBox); } inline QRectF getArtBoxMM() const { return getRectMM(m_artBox); } + inline PDFObjectReference getPageReference() const { return m_pageReference; } + QRectF getRotatedMediaBox() const; QRectF getRotatedCropBox() const; @@ -120,6 +122,7 @@ private: PageRotation m_pageRotation = PageRotation::None; PDFObject m_resources; PDFObject m_contents; + PDFObjectReference m_pageReference; }; } // namespace pdf diff --git a/PdfForQtViewer/pdfforqtviewer.qrc b/PdfForQtViewer/pdfforqtviewer.qrc index 0708dc6..2605ff2 100644 --- a/PdfForQtViewer/pdfforqtviewer.qrc +++ b/PdfForQtViewer/pdfforqtviewer.qrc @@ -17,5 +17,6 @@ resources/zoom-in.svg resources/zoom-out.svg resources/bookmark.svg + resources/security.svg diff --git a/PdfForQtViewer/pdfviewermainwindow.cpp b/PdfForQtViewer/pdfviewermainwindow.cpp index 3d8bdc2..5a37061 100644 --- a/PdfForQtViewer/pdfviewermainwindow.cpp +++ b/PdfForQtViewer/pdfviewermainwindow.cpp @@ -47,6 +47,11 @@ #include #include #include +#include + +#ifdef Q_OS_WIN +#include "Windows.h" +#endif namespace pdfviewer { @@ -251,7 +256,216 @@ void PDFViewerMainWindow::onPageZoomSpinboxEditingFinished() void PDFViewerMainWindow::onActionTriggered(const pdf::PDFAction* action) { + Q_ASSERT(action); + for (const pdf::PDFAction* currentAction : action->getActionList()) + { + switch (action->getType()) + { + case pdf::ActionType::GoTo: + { + const pdf::PDFActionGoTo* typedAction = dynamic_cast(currentAction); + pdf::PDFDestination destination = typedAction->getDestination(); + if (destination.getDestinationType() == pdf::DestinationType::Named) + { + if (const pdf::PDFDestination* targetDestination = m_pdfDocument->getCatalog()->getDestination(destination.getName())) + { + destination = *targetDestination; + } + else + { + destination = pdf::PDFDestination(); + QMessageBox::critical(this, tr("Go to action"), tr("Failed to go to destination '%1'. Destination wasn't found.").arg(pdf::PDFEncoding::convertTextString(destination.getName()))); + } + } + + if (destination.getDestinationType() != pdf::DestinationType::Invalid && + destination.getPageReference() != pdf::PDFObjectReference()) + { + const size_t pageIndex = m_pdfDocument->getCatalog()->getPageIndexFromPageReference(destination.getPageReference()); + if (pageIndex != pdf::PDFCatalog::INVALID_PAGE_INDEX) + { + m_pdfWidget->getDrawWidgetProxy()->goToPage(pageIndex); + } + } + + break; + } + + case pdf::ActionType::Launch: + { + if (!m_settings->getSettings().m_allowLaunchApplications) + { + // Launching of applications is disabled -> continue to next action + continue; + } + + const pdf::PDFActionLaunch* typedAction = dynamic_cast(currentAction); +#ifdef Q_OS_WIN + const pdf::PDFActionLaunch::Win& winSpecification = typedAction->getWinSpecification(); + if (!winSpecification.file.isEmpty()) + { + QString message = tr("Would you like to launch application '%1' in working directory '%2' with parameters '%3'?").arg(QString::fromLatin1(winSpecification.file), QString::fromLatin1(winSpecification.directory), QString::fromLatin1(winSpecification.parameters)); + if (QMessageBox::question(this, tr("Launch application"), message) == QMessageBox::Yes) + { + auto getStringOrNULL = [](const QByteArray& array) -> LPCSTR + { + if (!array.isEmpty()) + { + return array.data(); + } + return NULL; + }; + + const HINSTANCE result = ::ShellExecuteA(NULL, getStringOrNULL(winSpecification.operation), getStringOrNULL(winSpecification.file), getStringOrNULL(winSpecification.parameters), getStringOrNULL(winSpecification.directory), SW_SHOWNORMAL); + if (result <= HINSTANCE(32)) + { + // Error occured + QMessageBox::warning(this, tr("Launch application"), tr("Executing application failed. Error code is %1.").arg(reinterpret_cast(result))); + } + } + + // Continue next action + continue; + } + + const pdf::PDFFileSpecification& fileSpecification = typedAction->getFileSpecification(); + QString plaftormFileName = fileSpecification.getPlatformFileName(); + if (!plaftormFileName.isEmpty()) + { + QString message = tr("Would you like to launch application '%1'?").arg(plaftormFileName); + if (QMessageBox::question(this, tr("Launch application"), message) == QMessageBox::Yes) + { + const HINSTANCE result = ::ShellExecuteW(NULL, NULL, plaftormFileName.toStdWString().c_str(), NULL, NULL, SW_SHOWNORMAL); + if (result <= HINSTANCE(32)) + { + // Error occured + QMessageBox::warning(this, tr("Launch application"), tr("Executing application failed. Error code is %1.").arg(reinterpret_cast(result))); + } + } + + // Continue next action + continue; + } +#endif + break; + } + + case pdf::ActionType::URI: + { + if (!m_settings->getSettings().m_allowLaunchURI) + { + // Launching of URI is disabled -> continue to next action + continue; + } + + const pdf::PDFActionURI* typedAction = dynamic_cast(currentAction); + QByteArray URI = m_pdfDocument->getCatalog()->getBaseURI() + typedAction->getURI(); + QString urlString = QString::fromLatin1(URI); + QString message = tr("Would you like to open URL '%1'?").arg(urlString); + if (QMessageBox::question(this, tr("Open URL"), message) == QMessageBox::Yes) + { + if (!QDesktopServices::openUrl(QUrl(urlString))) + { + // Error occured + QMessageBox::warning(this, tr("Open URL"), tr("Opening url '%1' failed.").arg(urlString)); + } + } + + break; + } + + case pdf::ActionType::Named: + { + const pdf::PDFActionNamed* typedAction = dynamic_cast(currentAction); + switch (typedAction->getNamedActionType()) + { + case pdf::PDFActionNamed::NamedActionType::NextPage: + m_pdfWidget->getDrawWidgetProxy()->performOperation(pdf::PDFDrawWidgetProxy::NavigateNextPage); + break; + + case pdf::PDFActionNamed::NamedActionType::PrevPage: + m_pdfWidget->getDrawWidgetProxy()->performOperation(pdf::PDFDrawWidgetProxy::NavigatePreviousPage); + break; + + case pdf::PDFActionNamed::NamedActionType::FirstPage: + m_pdfWidget->getDrawWidgetProxy()->performOperation(pdf::PDFDrawWidgetProxy::NavigateDocumentStart); + break; + + case pdf::PDFActionNamed::NamedActionType::LastPage: + m_pdfWidget->getDrawWidgetProxy()->performOperation(pdf::PDFDrawWidgetProxy::NavigateDocumentEnd); + break; + + default: + break; + } + + break; + } + + case pdf::ActionType::SetOCGState: + { + const pdf::PDFActionSetOCGState* typedAction = dynamic_cast(currentAction); + const pdf::PDFActionSetOCGState::StateChangeItems& stateChanges = typedAction->getStateChangeItems(); + const bool isRadioButtonsPreserved = typedAction->isRadioButtonsPreserved(); + + if (m_optionalContentActivity) + { + for (const pdf::PDFActionSetOCGState::StateChangeItem& stateChange : stateChanges) + { + pdf::OCState newState = pdf::OCState::Unknown; + switch (stateChange.first) + { + case pdf::PDFActionSetOCGState::SwitchType::ON: + newState = pdf::OCState::ON; + break; + + case pdf::PDFActionSetOCGState::SwitchType::OFF: + newState = pdf::OCState::OFF; + break; + + case pdf::PDFActionSetOCGState::SwitchType::Toggle: + { + pdf::OCState oldState = m_optionalContentActivity->getState(stateChange.second); + switch (oldState) + { + case pdf::OCState::ON: + newState = pdf::OCState::OFF; + break; + + case pdf::OCState::OFF: + newState = pdf::OCState::ON; + break; + + case pdf::OCState::Unknown: + break; + + default: + Q_ASSERT(false); + break; + } + + break; + } + + default: + Q_ASSERT(false); + } + + if (newState != pdf::OCState::Unknown) + { + m_optionalContentActivity->setState(stateChange.second, newState, isRadioButtonsPreserved); + } + } + } + + break; + } + + default: + break; + } + } } void PDFViewerMainWindow::onProgressStarted() diff --git a/PdfForQtViewer/pdfviewersettings.cpp b/PdfForQtViewer/pdfviewersettings.cpp index 8f66c69..46d0054 100644 --- a/PdfForQtViewer/pdfviewersettings.cpp +++ b/PdfForQtViewer/pdfviewersettings.cpp @@ -22,6 +22,8 @@ void PDFViewerSettings::readSettings(QSettings& settings) m_settings.m_preferredMeshResolutionRatio = settings.value("preferredMeshResolutionRatio", defaultSettings.m_preferredMeshResolutionRatio).toDouble(); m_settings.m_minimalMeshResolutionRatio = settings.value("minimalMeshResolutionRatio", defaultSettings.m_minimalMeshResolutionRatio).toDouble(); m_settings.m_colorTolerance = settings.value("colorTolerance", defaultSettings.m_colorTolerance).toDouble(); + m_settings.m_allowLaunchApplications = settings.value("allowLaunchApplications", defaultSettings.m_allowLaunchApplications).toBool(); + m_settings.m_allowLaunchURI = settings.value("allowLaunchURI", defaultSettings.m_allowLaunchURI).toBool(); settings.endGroup(); emit settingsChanged(); @@ -38,6 +40,8 @@ void PDFViewerSettings::writeSettings(QSettings& settings) settings.setValue("preferredMeshResolutionRatio", m_settings.m_preferredMeshResolutionRatio); settings.setValue("minimalMeshResolutionRatio", m_settings.m_minimalMeshResolutionRatio); settings.setValue("colorTolerance", m_settings.m_colorTolerance); + settings.setValue("allowLaunchApplications", m_settings.m_allowLaunchApplications); + settings.setValue("allowLaunchURI", m_settings.m_allowLaunchURI); settings.endGroup(); } diff --git a/PdfForQtViewer/pdfviewersettings.h b/PdfForQtViewer/pdfviewersettings.h index 1f8fd3e..8e0712f 100644 --- a/PdfForQtViewer/pdfviewersettings.h +++ b/PdfForQtViewer/pdfviewersettings.h @@ -28,7 +28,9 @@ public: m_rendererSamples(16), m_preferredMeshResolutionRatio(0.02), m_minimalMeshResolutionRatio(0.005), - m_colorTolerance(0.01) + m_colorTolerance(0.01), + m_allowLaunchApplications(true), + m_allowLaunchURI(true) { } @@ -41,6 +43,8 @@ public: pdf::PDFReal m_preferredMeshResolutionRatio; pdf::PDFReal m_minimalMeshResolutionRatio; pdf::PDFReal m_colorTolerance; + bool m_allowLaunchApplications; + bool m_allowLaunchURI; }; const Settings& getSettings() const { return m_settings; } diff --git a/PdfForQtViewer/pdfviewersettingsdialog.cpp b/PdfForQtViewer/pdfviewersettingsdialog.cpp index 5668a0e..e6741bf 100644 --- a/PdfForQtViewer/pdfviewersettingsdialog.cpp +++ b/PdfForQtViewer/pdfviewersettingsdialog.cpp @@ -21,6 +21,7 @@ PDFViewerSettingsDialog::PDFViewerSettingsDialog(const PDFViewerSettings::Settin new QListWidgetItem(QIcon(":/resources/engine.svg"), tr("Engine"), ui->optionsPagesWidget, EngineSettings); new QListWidgetItem(QIcon(":/resources/rendering.svg"), tr("Rendering"), ui->optionsPagesWidget, RenderingSettings); new QListWidgetItem(QIcon(":/resources/shading.svg"), tr("Shading"), ui->optionsPagesWidget, ShadingSettings); + new QListWidgetItem(QIcon(":/resources/security.svg"), tr("Security"), ui->optionsPagesWidget, SecuritySettings); ui->renderingEngineComboBox->addItem(tr("Software"), static_cast(pdf::RendererEngine::Software)); ui->renderingEngineComboBox->addItem(tr("Hardware accelerated (OpenGL)"), static_cast(pdf::RendererEngine::OpenGL)); @@ -30,20 +31,20 @@ PDFViewerSettingsDialog::PDFViewerSettingsDialog(const PDFViewerSettings::Settin ui->multisampleAntialiasingSamplesCountComboBox->addItem(QString::number(i), i); } - for (QWidget* widget : { ui->engineInfoLabel, ui->renderingInfoLabel }) + for (QWidget* widget : { ui->engineInfoLabel, ui->renderingInfoLabel, ui->securityInfoLabel }) { widget->setMinimumWidth(widget->sizeHint().width()); } - for (QCheckBox* checkBox : { ui->multisampleAntialiasingCheckBox, ui->antialiasingCheckBox, ui->textAntialiasingCheckBox, ui->smoothPicturesCheckBox, ui->ignoreOptionalContentCheckBox, ui->clipToCropBoxCheckBox }) + for (QCheckBox* checkBox : findChildren()) { connect(checkBox, &QCheckBox::clicked, this, &PDFViewerSettingsDialog::saveData); } - for (QComboBox* comboBox : { ui->renderingEngineComboBox, ui->multisampleAntialiasingSamplesCountComboBox }) + for (QComboBox* comboBox : findChildren()) { connect(comboBox, QOverload::of(&QComboBox::currentIndexChanged), this, &PDFViewerSettingsDialog::saveData); } - for (QDoubleSpinBox* spinBox : { ui->preferredMeshResolutionEdit, ui->minimalMeshResolutionEdit, ui->colorToleranceEdit }) + for (QDoubleSpinBox* spinBox : findChildren()) { connect(spinBox, QOverload::of(&QDoubleSpinBox::valueChanged), this, &PDFViewerSettingsDialog::saveData); } @@ -76,6 +77,10 @@ void PDFViewerSettingsDialog::on_optionsPagesWidget_currentItemChanged(QListWidg ui->stackedWidget->setCurrentWidget(ui->shadingPage); break; + case SecuritySettings: + ui->stackedWidget->setCurrentWidget(ui->securityPage); + break; + default: Q_ASSERT(false); break; @@ -123,6 +128,10 @@ void PDFViewerSettingsDialog::loadData() ui->preferredMeshResolutionEdit->setValue(m_settings.m_preferredMeshResolutionRatio); ui->minimalMeshResolutionEdit->setValue(m_settings.m_minimalMeshResolutionRatio); ui->colorToleranceEdit->setValue(m_settings.m_colorTolerance); + + // Security + ui->allowLaunchCheckBox->setChecked(m_settings.m_allowLaunchApplications); + ui->allowRunURICheckBox->setChecked(m_settings.m_allowLaunchURI); } void PDFViewerSettingsDialog::saveData() @@ -178,6 +187,14 @@ void PDFViewerSettingsDialog::saveData() { m_settings.m_colorTolerance = ui->colorToleranceEdit->value(); } + else if (sender == ui->allowLaunchCheckBox) + { + m_settings.m_allowLaunchApplications = ui->allowLaunchCheckBox->isChecked(); + } + else if (sender == ui->allowRunURICheckBox) + { + m_settings.m_allowLaunchURI = ui->allowRunURICheckBox->isChecked(); + } loadData(); } diff --git a/PdfForQtViewer/pdfviewersettingsdialog.h b/PdfForQtViewer/pdfviewersettingsdialog.h index 4d926fe..2e6470a 100644 --- a/PdfForQtViewer/pdfviewersettingsdialog.h +++ b/PdfForQtViewer/pdfviewersettingsdialog.h @@ -27,7 +27,8 @@ public: { EngineSettings, RenderingSettings, - ShadingSettings + ShadingSettings, + SecuritySettings }; const PDFViewerSettings::Settings& getSettings() const { return m_settings; } diff --git a/PdfForQtViewer/pdfviewersettingsdialog.ui b/PdfForQtViewer/pdfviewersettingsdialog.ui index b979e6b..50bfea7 100644 --- a/PdfForQtViewer/pdfviewersettingsdialog.ui +++ b/PdfForQtViewer/pdfviewersettingsdialog.ui @@ -26,7 +26,7 @@ - 0 + 3 @@ -366,6 +366,86 @@ + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Security Settings + + + + + + + + Enable + + + + + + + Allow URI link launch + + + + + + + Allow application launch + + + + + + + Enable + + + + + + + + + <html><head/><body><p>If launch of application is allowed, then, after <span style=" font-weight:600;">Launch</span> action is triggered, message box appears, which asks the user, if he wants to execute external application. If user confirms the dialog, then external application is executed (for example, by windows function <span style=" font-weight:600;">ShellExecute</span>). When this option is turned off, then no dialog appears and external application is never executed.</p><p>If <span style=" font-weight:600;">URI link </span>launch is allowed, then, again, message box appears and asks the user, if he wants to open the URI link. If user confirms the dialog, then URI link is opened by standard web browser (or standard application, if URI links to the file system).</p><p>User should be really carefull with these settings, and only confirm the execution, if the document is safe, i.e. it comes from reliable source. Launching external applications and following URI links can be harmfull to the user's computer.</p></body></html> + + + true + + + + + + + Qt::Vertical + + + + 20 + 74 + + + + + + + + + diff --git a/PdfForQtViewer/resources/security.svg b/PdfForQtViewer/resources/security.svg new file mode 100644 index 0000000..4ef577b --- /dev/null +++ b/PdfForQtViewer/resources/security.svg @@ -0,0 +1,122 @@ + + + + + + + + + + + + image/svg+xml + + + + + Jakub Melka + + + + + + + + + + + + + + + + + + + + +