mirror of
https://github.com/JakubMelka/PDF4QT.git
synced 2025-03-31 03:30:18 +02:00
Finishing of outline items
This commit is contained in:
parent
39059c645e
commit
5954b7f409
@ -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 \
|
||||
|
@ -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;
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
@ -45,7 +45,7 @@ private:
|
||||
};
|
||||
|
||||
/// File specification
|
||||
class PDFFileSpecification
|
||||
class PDFFORQTLIBSHARED_EXPORT PDFFileSpecification
|
||||
{
|
||||
public:
|
||||
explicit PDFFileSpecification() = default;
|
||||
|
94
PdfForQtLib/sources/pdfnametreeloader.h
Normal file
94
PdfForQtLib/sources/pdfnametreeloader.h
Normal 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
|
@ -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())
|
||||
|
@ -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())
|
||||
{
|
||||
|
@ -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);
|
||||
|
@ -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());
|
||||
|
@ -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
|
||||
|
@ -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>
|
||||
|
@ -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()
|
||||
|
@ -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();
|
||||
}
|
||||
|
||||
|
@ -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; }
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -27,7 +27,8 @@ public:
|
||||
{
|
||||
EngineSettings,
|
||||
RenderingSettings,
|
||||
ShadingSettings
|
||||
ShadingSettings,
|
||||
SecuritySettings
|
||||
};
|
||||
|
||||
const PDFViewerSettings::Settings& getSettings() const { return m_settings; }
|
||||
|
@ -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><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></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>
|
||||
|
122
PdfForQtViewer/resources/security.svg
Normal file
122
PdfForQtViewer/resources/security.svg
Normal 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 |
Loading…
x
Reference in New Issue
Block a user