Undo/redo manager, fixing form bugs

This commit is contained in:
Jakub Melka 2020-04-25 18:15:12 +02:00
parent 57b3711210
commit ba74169eaa
12 changed files with 532 additions and 55 deletions

View File

@ -479,6 +479,8 @@ private:
PDFCatalog m_catalog;
};
using PDFDocumentPointer = QSharedPointer<PDFDocument>;
/// Helper class for document updates (for example, add/delete annotations,
/// fill form fields, do some other minor operations) and also for major
/// updates (document reset). It also contains modification flags, which are

View File

@ -22,19 +22,19 @@
namespace pdf
{
PDFForm PDFForm::parse(const PDFObjectStorage* storage, PDFObject object)
PDFForm PDFForm::parse(const PDFDocument* document, PDFObject object)
{
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");
form.m_formFields.reserve(fieldRoots.size());
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;
@ -51,11 +51,73 @@ PDFForm PDFForm::parse(const PDFObjectStorage* storage, PDFObject object)
// Jakub Melka: handle XFA form
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;
}
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)
{
for (const auto& childField : m_childFields)
@ -142,6 +204,34 @@ PDFFormFieldPointer PDFFormField::parse(const PDFObjectStorage* storage, PDFObje
}
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");
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)
{
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
{
return m_annotationManager;
@ -333,15 +396,13 @@ void PDFFormManager::setDocument(const PDFModifiedDocument& 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
{
// Clean the form
m_form = PDFForm();
}
updateWidgetToFormFieldMapping();
}
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

View File

@ -329,13 +329,23 @@ public:
const std::optional<PDFInteger>& getQuadding() const { return m_quadding; }
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
/// then empty form is returned, no exception is thrown.
/// \param storage Storage
/// \param document Document
/// \param reference Field reference
static PDFForm parse(const PDFObjectStorage* storage, PDFObject object);
static PDFForm parse(const PDFDocument* document, PDFObject object);
private:
void updateWidgetToFormFieldMapping();
FormType m_formType = FormType::None;
PDFFormFields m_formFields;
bool m_needAppearances = false;
@ -345,6 +355,7 @@ private:
std::optional<QByteArray> m_defaultAppearance;
std::optional<PDFInteger> m_quadding;
PDFObject m_xfa;
PDFWidgetToFormFieldMapping m_widgetToFormField;
};
/// 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,
/// then nullptr is returned.
/// \param widget Widget annotation
const PDFFormField* getFormFieldForWidget(PDFObjectReference widget) const;
const PDFFormField* getFormFieldForWidget(PDFObjectReference widget) const { return m_form.getFormFieldForWidget(widget); }
PDFAnnotationManager* getAnnotationManager() const;
void setAnnotationManager(PDFAnnotationManager* annotationManager);
@ -391,14 +402,12 @@ public:
private:
void updateFieldValues();
void updateWidgetToFormFieldMapping();
PDFDrawWidgetProxy* m_proxy;
PDFAnnotationManager* m_annotationManager;
const PDFDocument* m_document;
FormAppearanceFlags m_flags;
PDFForm m_form;
PDFWidgetToFormFieldMapping m_widgetToFormField;
};
} // namespace pdf

View File

@ -42,6 +42,7 @@ SOURCES += \
pdfsendmail.cpp \
pdfsidebarwidget.cpp \
pdftexttospeech.cpp \
pdfundoredomanager.cpp \
pdfviewermainwindow.cpp \
pdfviewersettings.cpp \
pdfviewersettingsdialog.cpp
@ -55,6 +56,7 @@ HEADERS += \
pdfsendmail.h \
pdfsidebarwidget.h \
pdftexttospeech.h \
pdfundoredomanager.h \
pdfviewermainwindow.h \
pdfviewersettings.h \
pdfviewersettingsdialog.h

View File

@ -44,5 +44,7 @@
<file>resources/screenshot-tool.svg</file>
<file>resources/extract-image.svg</file>
<file>resources/form-settings.svg</file>
<file>resources/undo.svg</file>
<file>resources/redo.svg</file>
</qresource>
</RCC>

View 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

View 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

View File

@ -117,6 +117,8 @@ PDFViewerMainWindow::PDFViewerMainWindow(QWidget* parent) :
ui->actionRotateRight->setShortcut(QKeySequence("Ctrl+Shift++"));
ui->actionRotateLeft->setShortcut(QKeySequence("Ctrl+Shift+-"));
ui->actionPrint->setShortcut(QKeySequence::Print);
ui->actionUndo->setShortcut(QKeySequence::Undo);
ui->actionRedo->setShortcut(QKeySequence::Redo);
for (QAction* action : m_recentFileManager->getActions())
{

View File

@ -34,7 +34,6 @@
#include <QFuture>
#include <QTreeView>
#include <QMainWindow>
#include <QSharedPointer>
#include <QWinTaskbarButton>
#include <QWinTaskbarProgress>
#include <QFutureWatcher>
@ -147,7 +146,7 @@ private:
struct AsyncReadingResult
{
QSharedPointer<pdf::PDFDocument> document;
pdf::PDFDocumentPointer document;
QString errorMessage;
pdf::PDFDocumentReader::Result result = pdf::PDFDocumentReader::Result::Cancelled;
};
@ -157,7 +156,7 @@ private:
PDFRecentFileManager* m_recentFileManager;
PDFViewerSettings* m_settings;
pdf::PDFWidget* m_pdfWidget;
QSharedPointer<pdf::PDFDocument> m_pdfDocument;
pdf::PDFDocumentPointer m_pdfDocument;
QString m_currentFile;
PDFSidebarWidget* m_sidebarWidget;
QDockWidget* m_sidebarDockWidget;

View File

@ -112,6 +112,9 @@
<property name="title">
<string>Edit</string>
</property>
<addaction name="actionUndo"/>
<addaction name="actionRedo"/>
<addaction name="separator"/>
<addaction name="actionFind"/>
<addaction name="actionFindPrevious"/>
<addaction name="actionFindNext"/>
@ -508,6 +511,24 @@
<string>Display Annotations</string>
</property>
</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>
<layoutdefault spacing="6" margin="11"/>
<resources>

View 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

View 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