Finishing of outline items

This commit is contained in:
Jakub Melka 2019-11-30 16:26:32 +01:00
parent 39059c645e
commit 5954b7f409
21 changed files with 702 additions and 21 deletions

View File

@ -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 \

View File

@ -39,6 +39,13 @@ void PDFAction::apply(const std::function<void (const PDFAction*)>& callback)
}
}
std::vector<const PDFAction*> PDFAction::getActionList() const
{
std::vector<const PDFAction*> result;
fillActionList(result);
return result;
}
PDFActionPtr PDFAction::parseImpl(const PDFDocument* document, PDFObject object, std::set<PDFObjectReference>& 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<const PDFAction*>& 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;

View File

@ -50,7 +50,8 @@ enum class ActionType
SetOCGState,
Rendition,
Transition,
GoTo3DView
GoTo3DView,
JavaScript
};
enum class DestinationType
@ -105,7 +106,7 @@ private:
using PDFActionPtr = QSharedPointer<PDFAction>;
/// 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<void(const PDFAction* action)>& callback);
/// Returns list of actions to be executed
std::vector<const PDFAction*> getActionList() const;
private:
static PDFActionPtr parseImpl(const PDFDocument* document, PDFObject object, std::set<PDFObjectReference>& usedReferences);
void fillActionList(std::vector<const PDFAction*>& actionList) const;
std::vector<PDFActionPtr> 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

View File

@ -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<PDFDestination>::parse(document, namesDictionary->get("Dests"), parseDestination);
catalogObject.m_javaScriptActions = PDFNameTreeLoader<PDFActionPtr>::parse(document, namesDictionary->get("JavaScript"), &PDFAction::parse);
catalogObject.m_embeddedFiles = PDFNameTreeLoader<PDFFileSpecification>::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;
}

View File

@ -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<size_t>::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<PDFPage> m_pages;
std::vector<PDFPageLabel> 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<QByteArray, PDFDestination> m_destinations;
std::map<QByteArray, PDFActionPtr> m_javaScriptActions;
std::map<QByteArray, PDFFileSpecification> m_embeddedFiles;
};
} // namespace pdf

View File

@ -18,6 +18,8 @@
#ifndef PDFENCODING_H
#define PDFENCODING_H
#include "pdfglobal.h"
#include <QString>
#include <QDateTime>
@ -33,7 +35,7 @@ using EncodingTable = std::array<QChar, 256>;
/// 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;

View File

@ -45,7 +45,7 @@ private:
};
/// File specification
class PDFFileSpecification
class PDFFORQTLIBSHARED_EXPORT PDFFileSpecification
{
public:
explicit PDFFileSpecification() = default;

View File

@ -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 <https://www.gnu.org/licenses/>.
#ifndef PDFNAMETREELOADER_H
#define PDFNAMETREELOADER_H
#include "pdfdocument.h"
#include <map>
#include <functional>
namespace pdf
{
/// This class can load a number tree into the array
template<typename Type>
class PDFNameTreeLoader
{
public:
explicit PDFNameTreeLoader() = delete;
using MappedObjects = std::map<QByteArray, Type>;
using LoadMethod = std::function<Type(const PDFDocument*, const PDFObject&)>;
/// 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

View File

@ -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())

View File

@ -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<PDFObjectReference>& radioButtonGroup : m_properties->getDefaultConfiguration().getRadioButtonGroups())
{

View File

@ -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);

View File

@ -150,6 +150,7 @@ void PDFPage::parseImpl(std::vector<PDFPage>& 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<PDFPage>& pages,
{
PDFPage page;
page.m_pageReference = objectReference;
page.m_mediaBox = currentInheritableAttributes.getMediaBox();
page.m_cropBox = currentInheritableAttributes.getCropBox();
page.m_resources = document->getObject(currentInheritableAttributes.getResources());

View File

@ -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

View File

@ -17,5 +17,6 @@
<file>resources/zoom-in.svg</file>
<file>resources/zoom-out.svg</file>
<file>resources/bookmark.svg</file>
<file>resources/security.svg</file>
</qresource>
</RCC>

View File

@ -47,6 +47,11 @@
#include <QSpinBox>
#include <QLabel>
#include <QDoubleSpinBox>
#include <QDesktopServices>
#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<const pdf::PDFActionGoTo*>(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<const pdf::PDFActionLaunch*>(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<intptr_t>(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<intptr_t>(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<const pdf::PDFActionURI*>(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<const pdf::PDFActionNamed*>(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<const pdf::PDFActionSetOCGState*>(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()

View File

@ -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();
}

View File

@ -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; }

View File

@ -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<int>(pdf::RendererEngine::Software));
ui->renderingEngineComboBox->addItem(tr("Hardware accelerated (OpenGL)"), static_cast<int>(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<QCheckBox*>())
{
connect(checkBox, &QCheckBox::clicked, this, &PDFViewerSettingsDialog::saveData);
}
for (QComboBox* comboBox : { ui->renderingEngineComboBox, ui->multisampleAntialiasingSamplesCountComboBox })
for (QComboBox* comboBox : findChildren<QComboBox*>())
{
connect(comboBox, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &PDFViewerSettingsDialog::saveData);
}
for (QDoubleSpinBox* spinBox : { ui->preferredMeshResolutionEdit, ui->minimalMeshResolutionEdit, ui->colorToleranceEdit })
for (QDoubleSpinBox* spinBox : findChildren<QDoubleSpinBox*>())
{
connect(spinBox, QOverload<double>::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();
}

View File

@ -27,7 +27,8 @@ public:
{
EngineSettings,
RenderingSettings,
ShadingSettings
ShadingSettings,
SecuritySettings
};
const PDFViewerSettings::Settings& getSettings() const { return m_settings; }

View File

@ -26,7 +26,7 @@
<item>
<widget class="QStackedWidget" name="stackedWidget">
<property name="currentIndex">
<number>0</number>
<number>3</number>
</property>
<widget class="QWidget" name="enginePage">
<layout class="QVBoxLayout" name="enginePageLayout">
@ -366,6 +366,86 @@
</item>
</layout>
</widget>
<widget class="QWidget" name="securityPage">
<layout class="QVBoxLayout" name="securityPageLayout">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QGroupBox" name="securitySettingsGroupBox">
<property name="title">
<string>Security Settings</string>
</property>
<layout class="QVBoxLayout" name="securitySettingsGroupBoxLayout">
<item>
<layout class="QGridLayout" name="securitySettingsGridLayout">
<item row="0" column="1">
<widget class="QCheckBox" name="allowLaunchCheckBox">
<property name="text">
<string>Enable</string>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="allowRunURILabel">
<property name="text">
<string>Allow URI link launch</string>
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QLabel" name="allowRunExeLabel">
<property name="text">
<string>Allow application launch</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QCheckBox" name="allowRunURICheckBox">
<property name="text">
<string>Enable</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QLabel" name="securityInfoLabel">
<property name="text">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;If launch of application is allowed, then, after &lt;span style=&quot; font-weight:600;&quot;&gt;Launch&lt;/span&gt; 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 &lt;span style=&quot; font-weight:600;&quot;&gt;ShellExecute&lt;/span&gt;). When this option is turned off, then no dialog appears and external application is never executed.&lt;/p&gt;&lt;p&gt;If &lt;span style=&quot; font-weight:600;&quot;&gt;URI link &lt;/span&gt;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).&lt;/p&gt;&lt;p&gt;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.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<spacer name="securityPageSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>74</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
</widget>
</item>
</layout>

View File

@ -0,0 +1,122 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="30mm"
height="30mm"
viewBox="0 0 30 30"
version="1.1"
id="svg5291"
inkscape:version="0.92.4 (5da689c313, 2019-01-14)"
sodipodi:docname="security.svg">
<defs
id="defs5285">
<inkscape:path-effect
effect="bspline"
id="path-effect831"
is_visible="true"
weight="33.333333"
steps="2"
helper_size="0"
apply_no_weight="true"
apply_with_weight="true"
only_selected="false" />
</defs>
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="0.35"
inkscape:cx="-887.45193"
inkscape:cy="248.19877"
inkscape:document-units="mm"
inkscape:current-layer="layer1"
showgrid="false"
inkscape:window-width="3840"
inkscape:window-height="2035"
inkscape:window-x="-13"
inkscape:window-y="-13"
inkscape:window-maximized="1" />
<metadata
id="metadata5288">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
<dc:creator>
<cc:Agent>
<dc:title>Jakub Melka</dc:title>
</cc:Agent>
</dc:creator>
<cc:license
rdf:resource="http://creativecommons.org/licenses/by-sa/4.0/" />
</cc:Work>
<cc:License
rdf:about="http://creativecommons.org/licenses/by-sa/4.0/">
<cc:permits
rdf:resource="http://creativecommons.org/ns#Reproduction" />
<cc:permits
rdf:resource="http://creativecommons.org/ns#Distribution" />
<cc:requires
rdf:resource="http://creativecommons.org/ns#Notice" />
<cc:requires
rdf:resource="http://creativecommons.org/ns#Attribution" />
<cc:permits
rdf:resource="http://creativecommons.org/ns#DerivativeWorks" />
<cc:requires
rdf:resource="http://creativecommons.org/ns#ShareAlike" />
</cc:License>
</rdf:RDF>
</metadata>
<g
inkscape:label="Vrstva 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(0,-267)">
<rect
style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:2;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="rect827"
width="22.395088"
height="12.425969"
x="4.2049851"
y="280.18005"
ry="1.3063761"
rx="0.80319941" />
<path
style="fill:none;stroke:#000000;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;stroke-miterlimit:4;stroke-dasharray:none"
d="m 8.8351933,280.08557 c -0.015751,-1.9059 -0.0315,-3.81153 0.2362696,-5.37063 0.2677698,-1.55909 0.8190596,-2.77193 2.8738961,-3.37025 2.054836,-0.59831 5.615048,-0.58257 7.670115,0.0158 2.055068,0.59836 2.606322,1.77962 2.889831,3.29942 0.283509,1.51979 0.299258,3.37799 0.31501,5.23664"
id="path829"
inkscape:connector-curvature="0"
inkscape:path-effect="#path-effect831"
inkscape:original-d="m 8.8351933,280.08557 c -0.015483,-1.9059 -0.031233,-3.81153 -0.047247,-5.71689 0.5514038,-1.21278 1.1026949,-2.42562 1.6536457,-3.63803 3.558604,0.0155 7.118816,0.0312 10.677827,0.0472 0.55144,1.18083 1.102695,2.36209 1.653646,3.54353 0.01602,1.8583 0.03176,3.7165 0.04725,5.57515" />
<ellipse
style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="path839"
cx="15.56845"
cy="285.39047"
rx="2.5390604"
ry="2.4555387" />
<rect
style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="rect845"
width="2.5513394"
height="4.9136906"
x="14.315848"
y="286.88913"
rx="0.80319828"
ry="1.1575521" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 4.5 KiB