mirror of
https://github.com/JakubMelka/PDF4QT.git
synced 2025-03-24 23:30:25 +01:00
Undo/redo manager, fixing form bugs
This commit is contained in:
parent
57b3711210
commit
ba74169eaa
@ -479,6 +479,8 @@ private:
|
|||||||
PDFCatalog m_catalog;
|
PDFCatalog m_catalog;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
using PDFDocumentPointer = QSharedPointer<PDFDocument>;
|
||||||
|
|
||||||
/// Helper class for document updates (for example, add/delete annotations,
|
/// Helper class for document updates (for example, add/delete annotations,
|
||||||
/// fill form fields, do some other minor operations) and also for major
|
/// fill form fields, do some other minor operations) and also for major
|
||||||
/// updates (document reset). It also contains modification flags, which are
|
/// updates (document reset). It also contains modification flags, which are
|
||||||
|
@ -22,19 +22,19 @@
|
|||||||
namespace pdf
|
namespace pdf
|
||||||
{
|
{
|
||||||
|
|
||||||
PDFForm PDFForm::parse(const PDFObjectStorage* storage, PDFObject object)
|
PDFForm PDFForm::parse(const PDFDocument* document, PDFObject object)
|
||||||
{
|
{
|
||||||
PDFForm form;
|
PDFForm form;
|
||||||
|
|
||||||
if (const PDFDictionary* formDictionary = storage->getDictionaryFromObject(object))
|
if (const PDFDictionary* formDictionary = document->getDictionaryFromObject(object))
|
||||||
{
|
{
|
||||||
PDFDocumentDataLoaderDecorator loader(storage);
|
PDFDocumentDataLoaderDecorator loader(document);
|
||||||
|
|
||||||
std::vector<PDFObjectReference> fieldRoots = loader.readReferenceArrayFromDictionary(formDictionary, "Fields");
|
std::vector<PDFObjectReference> fieldRoots = loader.readReferenceArrayFromDictionary(formDictionary, "Fields");
|
||||||
form.m_formFields.reserve(fieldRoots.size());
|
form.m_formFields.reserve(fieldRoots.size());
|
||||||
for (const PDFObjectReference& fieldRootReference : fieldRoots)
|
for (const PDFObjectReference& fieldRootReference : fieldRoots)
|
||||||
{
|
{
|
||||||
form.m_formFields.emplace_back(PDFFormField::parse(storage, fieldRootReference, nullptr));
|
form.m_formFields.emplace_back(PDFFormField::parse(&document->getStorage(), fieldRootReference, nullptr));
|
||||||
}
|
}
|
||||||
|
|
||||||
form.m_formType = FormType::AcroForm;
|
form.m_formType = FormType::AcroForm;
|
||||||
@ -51,11 +51,73 @@ PDFForm PDFForm::parse(const PDFObjectStorage* storage, PDFObject object)
|
|||||||
// Jakub Melka: handle XFA form
|
// Jakub Melka: handle XFA form
|
||||||
form.m_formType = FormType::XFAForm;
|
form.m_formType = FormType::XFAForm;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// As post-processing, delete all form fields, which are nullptr (are incorrectly defined)
|
||||||
|
form.m_formFields.erase(std::remove_if(form.m_formFields.begin(), form.m_formFields.end(), [](const auto& field){ return !field; }), form.m_formFields.end());
|
||||||
|
form.updateWidgetToFormFieldMapping();
|
||||||
|
|
||||||
|
// If we have form, then we must also look for 'rogue' form fields, which are
|
||||||
|
// incorrectly not in the 'Fields' entry of this form. We do this by iterating
|
||||||
|
// all pages, and their annotations and try to find these 'rogue' fields.
|
||||||
|
bool rogueFieldFound = false;
|
||||||
|
const size_t pageCount = document->getCatalog()->getPageCount();
|
||||||
|
for (size_t i = 0; i < pageCount; ++i)
|
||||||
|
{
|
||||||
|
const PDFPage* page = document->getCatalog()->getPage(i);
|
||||||
|
for (PDFObjectReference annotationReference : page->getAnnotations())
|
||||||
|
{
|
||||||
|
const PDFDictionary* annotationDictionary = document->getDictionaryFromObject(document->getObjectByReference(annotationReference));
|
||||||
|
|
||||||
|
if (form.m_widgetToFormField.count(annotationReference))
|
||||||
|
{
|
||||||
|
// This widget/form field is already present
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (loader.readNameFromDictionary(annotationDictionary, "Subtype") == "Widget")
|
||||||
|
{
|
||||||
|
rogueFieldFound = true;
|
||||||
|
form.m_formFields.emplace_back(PDFFormField::parse(&document->getStorage(), annotationReference, nullptr));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// As post-processing, delete all form fields, which are nullptr (are incorrectly defined)
|
||||||
|
form.m_formFields.erase(std::remove_if(form.m_formFields.begin(), form.m_formFields.end(), [](const auto& field){ return !field; }), form.m_formFields.end());
|
||||||
|
|
||||||
|
if (rogueFieldFound)
|
||||||
|
{
|
||||||
|
form.updateWidgetToFormFieldMapping();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return form;
|
return form;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void PDFForm::updateWidgetToFormFieldMapping()
|
||||||
|
{
|
||||||
|
m_widgetToFormField.clear();
|
||||||
|
|
||||||
|
if (isAcroForm() || isXFAForm())
|
||||||
|
{
|
||||||
|
for (const PDFFormFieldPointer& formFieldPtr : getFormFields())
|
||||||
|
{
|
||||||
|
formFieldPtr->fillWidgetToFormFieldMapping(m_widgetToFormField);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const PDFFormField* PDFForm::getFormFieldForWidget(PDFObjectReference widget) const
|
||||||
|
{
|
||||||
|
auto it = m_widgetToFormField.find(widget);
|
||||||
|
if (it != m_widgetToFormField.cend())
|
||||||
|
{
|
||||||
|
return it->second;
|
||||||
|
}
|
||||||
|
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
void PDFFormField::fillWidgetToFormFieldMapping(PDFWidgetToFormFieldMapping& mapping)
|
void PDFFormField::fillWidgetToFormFieldMapping(PDFWidgetToFormFieldMapping& mapping)
|
||||||
{
|
{
|
||||||
for (const auto& childField : m_childFields)
|
for (const auto& childField : m_childFields)
|
||||||
@ -142,6 +204,34 @@ PDFFormFieldPointer PDFFormField::parse(const PDFObjectStorage* storage, PDFObje
|
|||||||
}
|
}
|
||||||
result.reset(formField);
|
result.reset(formField);
|
||||||
|
|
||||||
|
PDFObject parentV = parentField ? parentField->getValue() : PDFObject();
|
||||||
|
PDFObject parentDV = parentField ? parentField->getDefaultValue() : PDFObject();
|
||||||
|
FieldFlags parentFlags = parentField ? parentField->getFlags() : None;
|
||||||
|
|
||||||
|
formField->m_selfReference = reference;
|
||||||
|
formField->m_fieldType = formFieldType;
|
||||||
|
formField->m_parentField = parentField;
|
||||||
|
formField->m_fieldNames[Partial] = loader.readTextStringFromDictionary(fieldDictionary, "T", QString());
|
||||||
|
formField->m_fieldNames[UserCaption] = loader.readTextStringFromDictionary(fieldDictionary, "TU", QString());
|
||||||
|
formField->m_fieldNames[Export] = loader.readTextStringFromDictionary(fieldDictionary, "TM", QString());
|
||||||
|
formField->m_fieldFlags = fieldDictionary->hasKey("Ff") ? static_cast<FieldFlags>(loader.readIntegerFromDictionary(fieldDictionary, "Ff", 0)) : parentFlags;
|
||||||
|
formField->m_value = fieldDictionary->hasKey("V") ? fieldDictionary->get("V") : parentV;
|
||||||
|
formField->m_defaultValue = fieldDictionary->hasKey("DV") ? fieldDictionary->get("DV") : parentDV;
|
||||||
|
formField->m_additionalActions = PDFAnnotationAdditionalActions::parse(storage, fieldDictionary->get("AA"));
|
||||||
|
|
||||||
|
// Generate fully qualified name. If partial name is empty, then fully qualified name
|
||||||
|
// is generated from parent fully qualified name (i.e. it is same as parent's name).
|
||||||
|
// This is according the PDF specification 1.7.
|
||||||
|
QStringList names;
|
||||||
|
if (parentField)
|
||||||
|
{
|
||||||
|
names << parentField->getName(FullyQualified);
|
||||||
|
}
|
||||||
|
names << formField->m_fieldNames[Partial];
|
||||||
|
names.removeAll(QString());
|
||||||
|
|
||||||
|
formField->m_fieldNames[FullyQualified] = names.join(".");
|
||||||
|
|
||||||
std::vector<PDFObjectReference> kids = loader.readReferenceArrayFromDictionary(fieldDictionary, "Kids");
|
std::vector<PDFObjectReference> kids = loader.readReferenceArrayFromDictionary(fieldDictionary, "Kids");
|
||||||
if (kids.empty())
|
if (kids.empty())
|
||||||
{
|
{
|
||||||
@ -182,22 +272,6 @@ PDFFormFieldPointer PDFFormField::parse(const PDFObjectStorage* storage, PDFObje
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
PDFObject parentV = parentField ? parentField->getValue() : PDFObject();
|
|
||||||
PDFObject parentDV = parentField ? parentField->getDefaultValue() : PDFObject();
|
|
||||||
FieldFlags parentFlags = parentField ? parentField->getFlags() : None;
|
|
||||||
|
|
||||||
formField->m_selfReference = reference;
|
|
||||||
formField->m_fieldType = formFieldType;
|
|
||||||
formField->m_parentField = parentField;
|
|
||||||
formField->m_fieldNames[Partial] = loader.readTextStringFromDictionary(fieldDictionary, "T", QString());
|
|
||||||
formField->m_fieldNames[UserCaption] = loader.readTextStringFromDictionary(fieldDictionary, "TU", QString());
|
|
||||||
formField->m_fieldNames[Export] = loader.readTextStringFromDictionary(fieldDictionary, "TM", QString());
|
|
||||||
formField->m_fieldNames[FullyQualified] = parentField ? QString("%1.%2").arg(parentField->getName(FullyQualified), formField->m_fieldNames[Partial]) : formField->m_fieldNames[Partial];
|
|
||||||
formField->m_fieldFlags = fieldDictionary->hasKey("Ff") ? static_cast<FieldFlags>(loader.readIntegerFromDictionary(fieldDictionary, "Ff", 0)) : parentFlags;
|
|
||||||
formField->m_value = fieldDictionary->hasKey("V") ? fieldDictionary->get("V") : parentV;
|
|
||||||
formField->m_defaultValue = fieldDictionary->hasKey("DV") ? fieldDictionary->get("DV") : parentDV;
|
|
||||||
formField->m_additionalActions = PDFAnnotationAdditionalActions::parse(storage, fieldDictionary->get("AA"));
|
|
||||||
|
|
||||||
if (formFieldButton)
|
if (formFieldButton)
|
||||||
{
|
{
|
||||||
formFieldButton->m_options = loader.readTextStringList(fieldDictionary->get("Opt"));
|
formFieldButton->m_options = loader.readTextStringList(fieldDictionary->get("Opt"));
|
||||||
@ -297,17 +371,6 @@ PDFFormManager::~PDFFormManager()
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const PDFFormField* PDFFormManager::getFormFieldForWidget(PDFObjectReference widget) const
|
|
||||||
{
|
|
||||||
auto it = m_widgetToFormField.find(widget);
|
|
||||||
if (it != m_widgetToFormField.cend())
|
|
||||||
{
|
|
||||||
return it->second;
|
|
||||||
}
|
|
||||||
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
PDFAnnotationManager* PDFFormManager::getAnnotationManager() const
|
PDFAnnotationManager* PDFFormManager::getAnnotationManager() const
|
||||||
{
|
{
|
||||||
return m_annotationManager;
|
return m_annotationManager;
|
||||||
@ -333,15 +396,13 @@ void PDFFormManager::setDocument(const PDFModifiedDocument& document)
|
|||||||
{
|
{
|
||||||
if (m_document)
|
if (m_document)
|
||||||
{
|
{
|
||||||
m_form = PDFForm::parse(&m_document->getStorage(), m_document->getCatalog()->getFormObject());
|
m_form = PDFForm::parse(m_document, m_document->getCatalog()->getFormObject());
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// Clean the form
|
// Clean the form
|
||||||
m_form = PDFForm();
|
m_form = PDFForm();
|
||||||
}
|
}
|
||||||
|
|
||||||
updateWidgetToFormFieldMapping();
|
|
||||||
}
|
}
|
||||||
else if (document.hasFlag(PDFModifiedDocument::FormField))
|
else if (document.hasFlag(PDFModifiedDocument::FormField))
|
||||||
{
|
{
|
||||||
@ -372,17 +433,4 @@ void PDFFormManager::updateFieldValues()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void PDFFormManager::updateWidgetToFormFieldMapping()
|
|
||||||
{
|
|
||||||
m_widgetToFormField.clear();
|
|
||||||
|
|
||||||
if (hasAcroForm() || hasXFAForm())
|
|
||||||
{
|
|
||||||
for (const PDFFormFieldPointer& formFieldPtr : m_form.getFormFields())
|
|
||||||
{
|
|
||||||
formFieldPtr->fillWidgetToFormFieldMapping(m_widgetToFormField);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace pdf
|
} // namespace pdf
|
||||||
|
@ -329,13 +329,23 @@ public:
|
|||||||
const std::optional<PDFInteger>& getQuadding() const { return m_quadding; }
|
const std::optional<PDFInteger>& getQuadding() const { return m_quadding; }
|
||||||
const PDFObject& getXFA() const { return m_xfa; }
|
const PDFObject& getXFA() const { return m_xfa; }
|
||||||
|
|
||||||
|
bool isAcroForm() const { return getFormType() == PDFForm::FormType::AcroForm; }
|
||||||
|
bool isXFAForm() const { return getFormType() == PDFForm::FormType::XFAForm; }
|
||||||
|
|
||||||
|
/// Returns form field for widget. If widget doesn't have attached form field,
|
||||||
|
/// then nullptr is returned.
|
||||||
|
/// \param widget Widget annotation
|
||||||
|
const PDFFormField* getFormFieldForWidget(PDFObjectReference widget) const;
|
||||||
|
|
||||||
/// Parses form from the object. If some error occurs
|
/// Parses form from the object. If some error occurs
|
||||||
/// then empty form is returned, no exception is thrown.
|
/// then empty form is returned, no exception is thrown.
|
||||||
/// \param storage Storage
|
/// \param document Document
|
||||||
/// \param reference Field reference
|
/// \param reference Field reference
|
||||||
static PDFForm parse(const PDFObjectStorage* storage, PDFObject object);
|
static PDFForm parse(const PDFDocument* document, PDFObject object);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
void updateWidgetToFormFieldMapping();
|
||||||
|
|
||||||
FormType m_formType = FormType::None;
|
FormType m_formType = FormType::None;
|
||||||
PDFFormFields m_formFields;
|
PDFFormFields m_formFields;
|
||||||
bool m_needAppearances = false;
|
bool m_needAppearances = false;
|
||||||
@ -345,6 +355,7 @@ private:
|
|||||||
std::optional<QByteArray> m_defaultAppearance;
|
std::optional<QByteArray> m_defaultAppearance;
|
||||||
std::optional<PDFInteger> m_quadding;
|
std::optional<PDFInteger> m_quadding;
|
||||||
PDFObject m_xfa;
|
PDFObject m_xfa;
|
||||||
|
PDFWidgetToFormFieldMapping m_widgetToFormField;
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Form manager. Manages all form widgets functionality - triggers actions,
|
/// Form manager. Manages all form widgets functionality - triggers actions,
|
||||||
@ -375,7 +386,7 @@ public:
|
|||||||
/// Returns form field for widget. If widget doesn't have attached form field,
|
/// Returns form field for widget. If widget doesn't have attached form field,
|
||||||
/// then nullptr is returned.
|
/// then nullptr is returned.
|
||||||
/// \param widget Widget annotation
|
/// \param widget Widget annotation
|
||||||
const PDFFormField* getFormFieldForWidget(PDFObjectReference widget) const;
|
const PDFFormField* getFormFieldForWidget(PDFObjectReference widget) const { return m_form.getFormFieldForWidget(widget); }
|
||||||
|
|
||||||
PDFAnnotationManager* getAnnotationManager() const;
|
PDFAnnotationManager* getAnnotationManager() const;
|
||||||
void setAnnotationManager(PDFAnnotationManager* annotationManager);
|
void setAnnotationManager(PDFAnnotationManager* annotationManager);
|
||||||
@ -391,14 +402,12 @@ public:
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
void updateFieldValues();
|
void updateFieldValues();
|
||||||
void updateWidgetToFormFieldMapping();
|
|
||||||
|
|
||||||
PDFDrawWidgetProxy* m_proxy;
|
PDFDrawWidgetProxy* m_proxy;
|
||||||
PDFAnnotationManager* m_annotationManager;
|
PDFAnnotationManager* m_annotationManager;
|
||||||
const PDFDocument* m_document;
|
const PDFDocument* m_document;
|
||||||
FormAppearanceFlags m_flags;
|
FormAppearanceFlags m_flags;
|
||||||
PDFForm m_form;
|
PDFForm m_form;
|
||||||
PDFWidgetToFormFieldMapping m_widgetToFormField;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace pdf
|
} // namespace pdf
|
||||||
|
@ -42,6 +42,7 @@ SOURCES += \
|
|||||||
pdfsendmail.cpp \
|
pdfsendmail.cpp \
|
||||||
pdfsidebarwidget.cpp \
|
pdfsidebarwidget.cpp \
|
||||||
pdftexttospeech.cpp \
|
pdftexttospeech.cpp \
|
||||||
|
pdfundoredomanager.cpp \
|
||||||
pdfviewermainwindow.cpp \
|
pdfviewermainwindow.cpp \
|
||||||
pdfviewersettings.cpp \
|
pdfviewersettings.cpp \
|
||||||
pdfviewersettingsdialog.cpp
|
pdfviewersettingsdialog.cpp
|
||||||
@ -55,6 +56,7 @@ HEADERS += \
|
|||||||
pdfsendmail.h \
|
pdfsendmail.h \
|
||||||
pdfsidebarwidget.h \
|
pdfsidebarwidget.h \
|
||||||
pdftexttospeech.h \
|
pdftexttospeech.h \
|
||||||
|
pdfundoredomanager.h \
|
||||||
pdfviewermainwindow.h \
|
pdfviewermainwindow.h \
|
||||||
pdfviewersettings.h \
|
pdfviewersettings.h \
|
||||||
pdfviewersettingsdialog.h
|
pdfviewersettingsdialog.h
|
||||||
|
@ -44,5 +44,7 @@
|
|||||||
<file>resources/screenshot-tool.svg</file>
|
<file>resources/screenshot-tool.svg</file>
|
||||||
<file>resources/extract-image.svg</file>
|
<file>resources/extract-image.svg</file>
|
||||||
<file>resources/form-settings.svg</file>
|
<file>resources/form-settings.svg</file>
|
||||||
|
<file>resources/undo.svg</file>
|
||||||
|
<file>resources/redo.svg</file>
|
||||||
</qresource>
|
</qresource>
|
||||||
</RCC>
|
</RCC>
|
||||||
|
92
PdfForQtViewer/pdfundoredomanager.cpp
Normal file
92
PdfForQtViewer/pdfundoredomanager.cpp
Normal file
@ -0,0 +1,92 @@
|
|||||||
|
// Copyright (C) 2020 Jakub Melka
|
||||||
|
//
|
||||||
|
// This file is part of PdfForQt.
|
||||||
|
//
|
||||||
|
// PdfForQt is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// PdfForQt is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Lesser General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Lesser General Public License
|
||||||
|
// along with PDFForQt. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
#include "pdfundoredomanager.h"
|
||||||
|
|
||||||
|
namespace pdfviewer
|
||||||
|
{
|
||||||
|
|
||||||
|
PDFUndoRedoManager::PDFUndoRedoManager(QObject* parent) :
|
||||||
|
BaseClass(parent)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
PDFUndoRedoManager::~PDFUndoRedoManager()
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void PDFUndoRedoManager::doUndo()
|
||||||
|
{
|
||||||
|
if (!canUndo())
|
||||||
|
{
|
||||||
|
// Undo operation can't be performed
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
UndoRedoItem item = m_undoSteps.back();
|
||||||
|
m_undoSteps.pop_back();
|
||||||
|
m_redoSteps.insert(m_redoSteps.begin(), item);
|
||||||
|
clampUndoRedoSteps();
|
||||||
|
|
||||||
|
emit undoRedoStateChanged();
|
||||||
|
emit documentChangeRequest(item.document, item.flags);
|
||||||
|
}
|
||||||
|
|
||||||
|
void PDFUndoRedoManager::doRedo()
|
||||||
|
{
|
||||||
|
if (!canRedo())
|
||||||
|
{
|
||||||
|
// Redo operation can't be performed
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
UndoRedoItem item = m_redoSteps.front();
|
||||||
|
m_redoSteps.erase(m_redoSteps.begin());
|
||||||
|
m_undoSteps.push_back(item);
|
||||||
|
clampUndoRedoSteps();
|
||||||
|
|
||||||
|
emit undoRedoStateChanged();
|
||||||
|
emit documentChangeRequest(item.document, item.flags);
|
||||||
|
}
|
||||||
|
|
||||||
|
void PDFUndoRedoManager::clear()
|
||||||
|
{
|
||||||
|
if (canUndo() || canRedo())
|
||||||
|
{
|
||||||
|
m_undoSteps.clear();
|
||||||
|
m_redoSteps.clear();
|
||||||
|
emit undoRedoStateChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void PDFUndoRedoManager::clampUndoRedoSteps()
|
||||||
|
{
|
||||||
|
if (m_undoSteps.size() > m_undoLimit)
|
||||||
|
{
|
||||||
|
// We erase from oldest steps to newest
|
||||||
|
m_undoSteps.erase(m_undoSteps.begin(), std::next(m_undoSteps.begin(), m_undoSteps.size() - m_undoLimit));
|
||||||
|
}
|
||||||
|
if (m_redoSteps.size() > m_redoLimit)
|
||||||
|
{
|
||||||
|
// Newest steps are erased
|
||||||
|
m_redoSteps.resize(m_redoLimit);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace pdfviewer
|
85
PdfForQtViewer/pdfundoredomanager.h
Normal file
85
PdfForQtViewer/pdfundoredomanager.h
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
// Copyright (C) 2020 Jakub Melka
|
||||||
|
//
|
||||||
|
// This file is part of PdfForQt.
|
||||||
|
//
|
||||||
|
// PdfForQt is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// PdfForQt is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Lesser General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Lesser General Public License
|
||||||
|
// along with PDFForQt. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
#ifndef PDFUNDOREDOMANAGER_H
|
||||||
|
#define PDFUNDOREDOMANAGER_H
|
||||||
|
|
||||||
|
#include "pdfdocument.h"
|
||||||
|
|
||||||
|
#include <QObject>
|
||||||
|
|
||||||
|
namespace pdfviewer
|
||||||
|
{
|
||||||
|
|
||||||
|
/// Undo/Redo document manager, it is managing undo and redo steps,
|
||||||
|
/// when document is modified.
|
||||||
|
class PDFUndoRedoManager : public QObject
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
private:
|
||||||
|
using BaseClass = QObject;
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit PDFUndoRedoManager(QObject* parent);
|
||||||
|
virtual ~PDFUndoRedoManager() override;
|
||||||
|
|
||||||
|
bool canUndo() const { return !m_undoSteps.empty(); }
|
||||||
|
bool canRedo() const { return !m_redoSteps.empty(); }
|
||||||
|
|
||||||
|
/// Performs single undo step. If Undo action can't be performed,
|
||||||
|
/// then nothing happens.
|
||||||
|
void doUndo();
|
||||||
|
|
||||||
|
/// Performs single redo step. If Redo action can't be performed,
|
||||||
|
/// then nothing happens.
|
||||||
|
void doRedo();
|
||||||
|
|
||||||
|
/// Clears all undo/redo steps
|
||||||
|
void clear();
|
||||||
|
|
||||||
|
signals:
|
||||||
|
/// This signals are emitted, when undo/redo action availability has
|
||||||
|
/// been changed (for example, user pressed undo/redo action)
|
||||||
|
void undoRedoStateChanged();
|
||||||
|
|
||||||
|
/// This signal is being emitted, when user performs undo/redo action.
|
||||||
|
/// Before signal is emitted, this object is in corrected state, as action
|
||||||
|
/// is performed.
|
||||||
|
/// \param document Active document
|
||||||
|
/// \param flags Change flags
|
||||||
|
void documentChangeRequest(pdf::PDFDocumentPointer document, pdf::PDFModifiedDocument::ModificationFlags flags);
|
||||||
|
|
||||||
|
private:
|
||||||
|
/// Clamps undo/redo steps so they fit the limits
|
||||||
|
void clampUndoRedoSteps();
|
||||||
|
|
||||||
|
struct UndoRedoItem
|
||||||
|
{
|
||||||
|
pdf::PDFDocumentPointer document;
|
||||||
|
pdf::PDFModifiedDocument::ModificationFlags flags = pdf::PDFModifiedDocument::None;
|
||||||
|
};
|
||||||
|
|
||||||
|
size_t m_undoLimit = 0;
|
||||||
|
size_t m_redoLimit = 0;
|
||||||
|
std::vector<UndoRedoItem> m_undoSteps;
|
||||||
|
std::vector<UndoRedoItem> m_redoSteps;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace pdfviewer
|
||||||
|
|
||||||
|
#endif // PDFUNDOREDOMANAGER_H
|
@ -117,6 +117,8 @@ PDFViewerMainWindow::PDFViewerMainWindow(QWidget* parent) :
|
|||||||
ui->actionRotateRight->setShortcut(QKeySequence("Ctrl+Shift++"));
|
ui->actionRotateRight->setShortcut(QKeySequence("Ctrl+Shift++"));
|
||||||
ui->actionRotateLeft->setShortcut(QKeySequence("Ctrl+Shift+-"));
|
ui->actionRotateLeft->setShortcut(QKeySequence("Ctrl+Shift+-"));
|
||||||
ui->actionPrint->setShortcut(QKeySequence::Print);
|
ui->actionPrint->setShortcut(QKeySequence::Print);
|
||||||
|
ui->actionUndo->setShortcut(QKeySequence::Undo);
|
||||||
|
ui->actionRedo->setShortcut(QKeySequence::Redo);
|
||||||
|
|
||||||
for (QAction* action : m_recentFileManager->getActions())
|
for (QAction* action : m_recentFileManager->getActions())
|
||||||
{
|
{
|
||||||
|
@ -34,7 +34,6 @@
|
|||||||
#include <QFuture>
|
#include <QFuture>
|
||||||
#include <QTreeView>
|
#include <QTreeView>
|
||||||
#include <QMainWindow>
|
#include <QMainWindow>
|
||||||
#include <QSharedPointer>
|
|
||||||
#include <QWinTaskbarButton>
|
#include <QWinTaskbarButton>
|
||||||
#include <QWinTaskbarProgress>
|
#include <QWinTaskbarProgress>
|
||||||
#include <QFutureWatcher>
|
#include <QFutureWatcher>
|
||||||
@ -147,7 +146,7 @@ private:
|
|||||||
|
|
||||||
struct AsyncReadingResult
|
struct AsyncReadingResult
|
||||||
{
|
{
|
||||||
QSharedPointer<pdf::PDFDocument> document;
|
pdf::PDFDocumentPointer document;
|
||||||
QString errorMessage;
|
QString errorMessage;
|
||||||
pdf::PDFDocumentReader::Result result = pdf::PDFDocumentReader::Result::Cancelled;
|
pdf::PDFDocumentReader::Result result = pdf::PDFDocumentReader::Result::Cancelled;
|
||||||
};
|
};
|
||||||
@ -157,7 +156,7 @@ private:
|
|||||||
PDFRecentFileManager* m_recentFileManager;
|
PDFRecentFileManager* m_recentFileManager;
|
||||||
PDFViewerSettings* m_settings;
|
PDFViewerSettings* m_settings;
|
||||||
pdf::PDFWidget* m_pdfWidget;
|
pdf::PDFWidget* m_pdfWidget;
|
||||||
QSharedPointer<pdf::PDFDocument> m_pdfDocument;
|
pdf::PDFDocumentPointer m_pdfDocument;
|
||||||
QString m_currentFile;
|
QString m_currentFile;
|
||||||
PDFSidebarWidget* m_sidebarWidget;
|
PDFSidebarWidget* m_sidebarWidget;
|
||||||
QDockWidget* m_sidebarDockWidget;
|
QDockWidget* m_sidebarDockWidget;
|
||||||
|
@ -112,6 +112,9 @@
|
|||||||
<property name="title">
|
<property name="title">
|
||||||
<string>Edit</string>
|
<string>Edit</string>
|
||||||
</property>
|
</property>
|
||||||
|
<addaction name="actionUndo"/>
|
||||||
|
<addaction name="actionRedo"/>
|
||||||
|
<addaction name="separator"/>
|
||||||
<addaction name="actionFind"/>
|
<addaction name="actionFind"/>
|
||||||
<addaction name="actionFindPrevious"/>
|
<addaction name="actionFindPrevious"/>
|
||||||
<addaction name="actionFindNext"/>
|
<addaction name="actionFindNext"/>
|
||||||
@ -508,6 +511,24 @@
|
|||||||
<string>Display Annotations</string>
|
<string>Display Annotations</string>
|
||||||
</property>
|
</property>
|
||||||
</action>
|
</action>
|
||||||
|
<action name="actionUndo">
|
||||||
|
<property name="icon">
|
||||||
|
<iconset resource="pdfforqtviewer.qrc">
|
||||||
|
<normaloff>:/resources/undo.svg</normaloff>:/resources/undo.svg</iconset>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>Undo</string>
|
||||||
|
</property>
|
||||||
|
</action>
|
||||||
|
<action name="actionRedo">
|
||||||
|
<property name="icon">
|
||||||
|
<iconset resource="pdfforqtviewer.qrc">
|
||||||
|
<normaloff>:/resources/redo.svg</normaloff>:/resources/redo.svg</iconset>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>Redo</string>
|
||||||
|
</property>
|
||||||
|
</action>
|
||||||
</widget>
|
</widget>
|
||||||
<layoutdefault spacing="6" margin="11"/>
|
<layoutdefault spacing="6" margin="11"/>
|
||||||
<resources>
|
<resources>
|
||||||
|
108
PdfForQtViewer/resources/redo.svg
Normal file
108
PdfForQtViewer/resources/redo.svg
Normal file
@ -0,0 +1,108 @@
|
|||||||
|
<?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="redo.svg">
|
||||||
|
<defs
|
||||||
|
id="defs5285">
|
||||||
|
<inkscape:perspective
|
||||||
|
sodipodi:type="inkscape:persp3d"
|
||||||
|
inkscape:vp_x="0 : 15 : 1"
|
||||||
|
inkscape:vp_y="0 : 1000 : 0"
|
||||||
|
inkscape:vp_z="30 : 15 : 1"
|
||||||
|
inkscape:persp3d-origin="15 : 10 : 1"
|
||||||
|
id="perspective5921" />
|
||||||
|
</defs>
|
||||||
|
<sodipodi:namedview
|
||||||
|
id="base"
|
||||||
|
pagecolor="#ffffff"
|
||||||
|
bordercolor="#666666"
|
||||||
|
borderopacity="1.0"
|
||||||
|
inkscape:pageopacity="0.0"
|
||||||
|
inkscape:pageshadow="2"
|
||||||
|
inkscape:zoom="11.313708"
|
||||||
|
inkscape:cx="82.017664"
|
||||||
|
inkscape:cy="53.86011"
|
||||||
|
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)">
|
||||||
|
<flowRoot
|
||||||
|
xml:space="preserve"
|
||||||
|
id="flowRoot5913"
|
||||||
|
style="font-style:normal;font-weight:normal;font-size:40px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none"><flowRegion
|
||||||
|
id="flowRegion5915"><rect
|
||||||
|
id="rect5917"
|
||||||
|
width="129.22377"
|
||||||
|
height="91.747108"
|
||||||
|
x="-13.788582"
|
||||||
|
y="-33.515606" /></flowRegion><flowPara
|
||||||
|
id="flowPara5919" /></flowRoot> <g
|
||||||
|
aria-label="⎌"
|
||||||
|
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:38.09999847px;line-height:1.25;font-family:sans-serif;-inkscape-font-specification:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.39687496"
|
||||||
|
id="text847"
|
||||||
|
transform="matrix(-1,0,0,1,29.656982,0)">
|
||||||
|
<path
|
||||||
|
d="m 11.014771,277.24364 -4.7252931,3.75791 -1.1348144,-5.93452 2.0835937,0.76274 q 1.1534179,-1.54409 2.8463378,-2.49287 2.195215,-1.24643 4.743896,-1.24643 2.548682,0 4.725293,1.22783 2.195215,1.20923 3.423047,3.36724 1.246435,2.158 1.246435,4.85551 v 0.11162 q 0.706934,0.14883 1.339453,0.5023 1.097608,0.61391 1.711524,1.69292 0.632519,1.079 0.632519,2.41846 0,1.33945 -0.613916,2.41845 -0.613916,1.0604 -1.711523,1.69292 -1.079004,0.61392 -2.38125,0.61392 -1.265039,0 -2.38125,-0.61392 -1.116211,-0.61391 -1.730127,-1.69292 -0.595312,-1.079 -0.595312,-2.41845 0,-1.30225 0.613916,-2.38125 0.632519,-1.09761 1.730127,-1.71153 0.799951,-0.46509 1.674316,-0.57671 v -0.0558 q 0,-2.26963 -1.023193,-4.05556 -1.00459,-1.78594 -2.771924,-2.73472 -1.767334,-0.96738 -3.888135,-0.96738 -2.046386,0 -3.81372,0.96738 -1.2092287,0.65112 -2.0649904,1.71152 z m -4.5578614,4.29741 q 1.2836425,0 2.3626464,0.61392 1.0976073,0.61391 1.711523,1.69292 0.63252,1.079 0.63252,2.41846 0,1.33945 -0.613916,2.41845 -0.6139161,1.0604 -1.7115235,1.69292 -1.0790039,0.61392 -2.3812499,0.61392 -1.265039,0 -2.3812499,-0.61392 -1.1162109,-0.61391 -1.7301269,-1.69292 -0.5953125,-1.079 -0.5953125,-2.41845 0,-1.30225 0.613916,-2.38125 0.6325195,-1.09761 1.7301269,-1.71153 1.0976074,-0.63252 2.3626464,-0.63252 z m 0,1.39527 q -0.8557617,0 -1.6185058,0.40927 -0.7627441,0.40928 -1.2092285,1.19063 -0.4278808,0.76274 -0.4278808,1.73013 0,0.93017 0.4092773,1.73012 0.4278808,0.79995 1.2092285,1.20923 0.7813476,0.40928 1.6371093,0.40928 0.9301757,0 1.6743163,-0.40928 0.7441406,-0.40928 1.190625,-1.20923 0.4464843,-0.79995 0.4464843,-1.73012 0,-0.96739 -0.4464843,-1.73013 -0.4464844,-0.78135 -1.2092285,-1.19063 -0.7441406,-0.40927 -1.6557128,-0.40927 z m 16.7431634,0 q -0.855762,0 -1.618506,0.40927 -0.762744,0.40928 -1.209228,1.19063 -0.427881,0.76274 -0.427881,1.73013 0,0.93017 0.409277,1.73012 0.427881,0.79995 1.209229,1.20923 0.781347,0.40928 1.637109,0.40928 0.930176,0 1.674316,-0.40928 0.744141,-0.40928 1.190625,-1.20923 0.446485,-0.79995 0.446485,-1.73012 0,-0.96739 -0.446485,-1.73013 -0.446484,-0.78135 -1.209228,-1.19063 -0.744141,-0.40927 -1.655713,-0.40927 z"
|
||||||
|
style="stroke-width:0.39687496"
|
||||||
|
id="path4552"
|
||||||
|
inkscape:connector-curvature="0" />
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 5.8 KiB |
107
PdfForQtViewer/resources/undo.svg
Normal file
107
PdfForQtViewer/resources/undo.svg
Normal file
@ -0,0 +1,107 @@
|
|||||||
|
<?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="undo.svg">
|
||||||
|
<defs
|
||||||
|
id="defs5285">
|
||||||
|
<inkscape:perspective
|
||||||
|
sodipodi:type="inkscape:persp3d"
|
||||||
|
inkscape:vp_x="0 : 15 : 1"
|
||||||
|
inkscape:vp_y="0 : 1000 : 0"
|
||||||
|
inkscape:vp_z="30 : 15 : 1"
|
||||||
|
inkscape:persp3d-origin="15 : 10 : 1"
|
||||||
|
id="perspective5921" />
|
||||||
|
</defs>
|
||||||
|
<sodipodi:namedview
|
||||||
|
id="base"
|
||||||
|
pagecolor="#ffffff"
|
||||||
|
bordercolor="#666666"
|
||||||
|
borderopacity="1.0"
|
||||||
|
inkscape:pageopacity="0.0"
|
||||||
|
inkscape:pageshadow="2"
|
||||||
|
inkscape:zoom="11.313708"
|
||||||
|
inkscape:cx="82.017664"
|
||||||
|
inkscape:cy="53.86011"
|
||||||
|
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)">
|
||||||
|
<flowRoot
|
||||||
|
xml:space="preserve"
|
||||||
|
id="flowRoot5913"
|
||||||
|
style="font-style:normal;font-weight:normal;font-size:40px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none"><flowRegion
|
||||||
|
id="flowRegion5915"><rect
|
||||||
|
id="rect5917"
|
||||||
|
width="129.22377"
|
||||||
|
height="91.747108"
|
||||||
|
x="-13.788582"
|
||||||
|
y="-33.515606" /></flowRegion><flowPara
|
||||||
|
id="flowPara5919" /></flowRoot> <g
|
||||||
|
aria-label="⎌"
|
||||||
|
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:38.09999847px;line-height:1.25;font-family:sans-serif;-inkscape-font-specification:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.39687496"
|
||||||
|
id="text847">
|
||||||
|
<path
|
||||||
|
d="m 11.014771,277.24364 -4.7252931,3.75791 -1.1348144,-5.93452 2.0835937,0.76274 q 1.1534179,-1.54409 2.8463378,-2.49287 2.195215,-1.24643 4.743896,-1.24643 2.548682,0 4.725293,1.22783 2.195215,1.20923 3.423047,3.36724 1.246435,2.158 1.246435,4.85551 v 0.11162 q 0.706934,0.14883 1.339453,0.5023 1.097608,0.61391 1.711524,1.69292 0.632519,1.079 0.632519,2.41846 0,1.33945 -0.613916,2.41845 -0.613916,1.0604 -1.711523,1.69292 -1.079004,0.61392 -2.38125,0.61392 -1.265039,0 -2.38125,-0.61392 -1.116211,-0.61391 -1.730127,-1.69292 -0.595312,-1.079 -0.595312,-2.41845 0,-1.30225 0.613916,-2.38125 0.632519,-1.09761 1.730127,-1.71153 0.799951,-0.46509 1.674316,-0.57671 v -0.0558 q 0,-2.26963 -1.023193,-4.05556 -1.00459,-1.78594 -2.771924,-2.73472 -1.767334,-0.96738 -3.888135,-0.96738 -2.046386,0 -3.81372,0.96738 -1.2092287,0.65112 -2.0649904,1.71152 z m -4.5578614,4.29741 q 1.2836425,0 2.3626464,0.61392 1.0976073,0.61391 1.711523,1.69292 0.63252,1.079 0.63252,2.41846 0,1.33945 -0.613916,2.41845 -0.6139161,1.0604 -1.7115235,1.69292 -1.0790039,0.61392 -2.3812499,0.61392 -1.265039,0 -2.3812499,-0.61392 -1.1162109,-0.61391 -1.7301269,-1.69292 -0.5953125,-1.079 -0.5953125,-2.41845 0,-1.30225 0.613916,-2.38125 0.6325195,-1.09761 1.7301269,-1.71153 1.0976074,-0.63252 2.3626464,-0.63252 z m 0,1.39527 q -0.8557617,0 -1.6185058,0.40927 -0.7627441,0.40928 -1.2092285,1.19063 -0.4278808,0.76274 -0.4278808,1.73013 0,0.93017 0.4092773,1.73012 0.4278808,0.79995 1.2092285,1.20923 0.7813476,0.40928 1.6371093,0.40928 0.9301757,0 1.6743163,-0.40928 0.7441406,-0.40928 1.190625,-1.20923 0.4464843,-0.79995 0.4464843,-1.73012 0,-0.96739 -0.4464843,-1.73013 -0.4464844,-0.78135 -1.2092285,-1.19063 -0.7441406,-0.40927 -1.6557128,-0.40927 z m 16.7431634,0 q -0.855762,0 -1.618506,0.40927 -0.762744,0.40928 -1.209228,1.19063 -0.427881,0.76274 -0.427881,1.73013 0,0.93017 0.409277,1.73012 0.427881,0.79995 1.209229,1.20923 0.781347,0.40928 1.637109,0.40928 0.930176,0 1.674316,-0.40928 0.744141,-0.40928 1.190625,-1.20923 0.446485,-0.79995 0.446485,-1.73012 0,-0.96739 -0.446485,-1.73013 -0.446484,-0.78135 -1.209228,-1.19063 -0.744141,-0.40927 -1.655713,-0.40927 z"
|
||||||
|
style="stroke-width:0.39687496"
|
||||||
|
id="path4552"
|
||||||
|
inkscape:connector-curvature="0" />
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 5.8 KiB |
Loading…
x
Reference in New Issue
Block a user