Signature verification UI

This commit is contained in:
Jakub Melka 2020-06-20 18:20:57 +02:00
parent 3e6f4ada3d
commit 17d32fd0b8
12 changed files with 675 additions and 11 deletions

View File

@ -861,8 +861,8 @@ QDateTime pdf::PDFPublicKeySignatureHandler::getDateTimeFromASN(const ASN1_TIME*
tm internalTime = { };
if (ASN1_TIME_to_tm(time, &internalTime) > 0)
{
time_t localTime = mktime(&internalTime);
result = QDateTime::fromSecsSinceEpoch(localTime, Qt::LocalTime);
time_t localTime = _mkgmtime(&internalTime);
result = QDateTime::fromSecsSinceEpoch(localTime, Qt::UTC);
}
}

View File

@ -280,6 +280,8 @@ public:
Error_Signatures_Mask = Error_Signature_Invalid | Error_Signature_SourceCertificateMissing | Error_Signature_NoSignaturesFound |
Error_Signature_DigestFailure | Error_Signature_DataOther | Error_Signature_DataCoveredBySignatureMissing,
Warnings_Mask = Warning_Signature_NotCoveredBytes
};
Q_DECLARE_FLAGS(VerificationFlags, VerificationFlag)
@ -309,6 +311,7 @@ public:
bool isCertificateValid() const { return hasFlag(Certificate_OK); }
bool isSignatureValid() const { return hasFlag(Signature_OK); }
bool hasError() const { return !isValid(); }
bool hasWarning() const { return m_flags & Warnings_Mask; }
bool hasCertificateError() const { return m_flags & Error_Certificates_Mask; }
bool hasSignatureError() const { return m_flags & Error_Signatures_Mask; }
bool hasFlag(VerificationFlag flag) const { return m_flags.testFlag(flag); }
@ -318,6 +321,7 @@ public:
const QString& getSignatureFieldQualifiedName() const { return m_signatureFieldQualifiedName; }
const QStringList& getErrors() const { return m_errors; }
const QStringList& getWarnings() const { return m_warnings; }
const PDFCertificateInfos& getCertificateInfos() const { return m_certificateInfos; }
void setSignatureFieldQualifiedName(const QString& signatureFieldQualifiedName);
void setSignatureFieldReference(PDFObjectReference signatureFieldReference);

View File

@ -46,5 +46,9 @@
<file>resources/form-settings.svg</file>
<file>resources/undo.svg</file>
<file>resources/redo.svg</file>
<file>resources/result-error.svg</file>
<file>resources/result-information.svg</file>
<file>resources/result-ok.svg</file>
<file>resources/result-warning.svg</file>
</qresource>
</RCC>

View File

@ -24,6 +24,7 @@
#include "pdfdocument.h"
#include "pdfitemmodels.h"
#include "pdfexception.h"
#include "pdfsignaturehandler.h"
#include "pdfdrawspacecontroller.h"
#include <QMenu>
@ -97,6 +98,7 @@ PDFSidebarWidget::PDFSidebarWidget(pdf::PDFDrawWidgetProxy* proxy, PDFTextToSpee
m_pageInfo[Thumbnails] = { ui->thumbnailsButton, ui->thumbnailsPage };
m_pageInfo[Attachments] = { ui->attachmentsButton, ui->attachmentsPage };
m_pageInfo[Speech] = { ui->speechButton, ui->speechPage };
m_pageInfo[Signatures] = { ui->signaturesButton, ui->signaturesPage };
for (const auto& pageInfo : m_pageInfo)
{
@ -121,7 +123,7 @@ PDFSidebarWidget::~PDFSidebarWidget()
delete ui;
}
void PDFSidebarWidget::setDocument(const pdf::PDFModifiedDocument& document)
void PDFSidebarWidget::setDocument(const pdf::PDFModifiedDocument& document, const std::vector<pdf::PDFSignatureVerificationResult>& signatures)
{
m_document = document;
m_optionalContentActivity = document.getOptionalContentActivity();
@ -196,6 +198,7 @@ void PDFSidebarWidget::setDocument(const pdf::PDFModifiedDocument& document)
// Update GUI
updateGUI(preferred);
updateButtons();
updateSignatures(signatures);
}
bool PDFSidebarWidget::isEmpty() const
@ -233,6 +236,9 @@ bool PDFSidebarWidget::isEmpty(Page page) const
case Speech:
return !m_textToSpeech->isValid();
case Signatures:
return !m_signatures.empty();
default:
Q_ASSERT(false);
break;
@ -331,6 +337,201 @@ void PDFSidebarWidget::updateButtons()
}
}
void PDFSidebarWidget::updateSignatures(const std::vector<pdf::PDFSignatureVerificationResult>& signatures)
{
ui->signatureTreeWidget->setUpdatesEnabled(false);
ui->signatureTreeWidget->clear();
QIcon okIcon(":/resources/result-ok.svg");
QIcon errorIcon(":/resources/result-error.svg");
QIcon warningIcon(":/resources/result-warning.svg");
QIcon infoIcon(":/resources/result-information.svg");
for (const pdf::PDFSignatureVerificationResult& signature : signatures)
{
const pdf::PDFCertificateInfos& certificateInfos = signature.getCertificateInfos();
const pdf::PDFCertificateInfo* certificateInfo = !certificateInfos.empty() ? &certificateInfos.front() : nullptr;
QString text = tr("Signed by - %1").arg(certificateInfo ? certificateInfo->getName(pdf::PDFCertificateInfo::CommonName) : tr("Unknown"));
QTreeWidgetItem* rootItem = new QTreeWidgetItem(QStringList(text));
if (signature.hasError())
{
rootItem->setIcon(0, errorIcon);
}
else if (signature.hasWarning())
{
rootItem->setIcon(0, warningIcon);
}
else
{
rootItem->setIcon(0, okIcon);
}
if (signature.isCertificateValid())
{
QTreeWidgetItem* certificateItem = new QTreeWidgetItem(rootItem, QStringList(tr("Certificate is valid.")));
certificateItem->setIcon(0, okIcon);
}
if (signature.isSignatureValid())
{
QTreeWidgetItem* signatureItem = new QTreeWidgetItem(rootItem, QStringList(tr("Signature is valid.")));
signatureItem->setIcon(0, okIcon);
}
for (const QString& error : signature.getErrors())
{
QTreeWidgetItem* item = new QTreeWidgetItem(rootItem, QStringList(error));
item->setIcon(0, errorIcon);
}
for (const QString& error : signature.getWarnings())
{
QTreeWidgetItem* item = new QTreeWidgetItem(rootItem, QStringList(error));
item->setIcon(0, warningIcon);
}
if (certificateInfo)
{
QTreeWidgetItem* certChainRoot = new QTreeWidgetItem(rootItem, QStringList(tr("Certificate validation chain")));
certChainRoot->setIcon(0, infoIcon);
for (const pdf::PDFCertificateInfo& currentCertificateInfo : certificateInfos)
{
QTreeWidgetItem* certRoot = new QTreeWidgetItem(certChainRoot, QStringList(currentCertificateInfo.getName(pdf::PDFCertificateInfo::CommonName)));
certRoot->setIcon(0, infoIcon);
auto addName = [certRoot, &currentCertificateInfo, &infoIcon](pdf::PDFCertificateInfo::NameEntry nameEntry, QString caption)
{
QString text = currentCertificateInfo.getName(nameEntry);
if (!text.isEmpty())
{
QTreeWidgetItem* item = new QTreeWidgetItem(certRoot, QStringList(QString("%1: %2").arg(caption, text)));
item->setIcon(0, infoIcon);
}
};
QString publicKeyMethod;
switch (currentCertificateInfo.getPublicKey())
{
case pdf::PDFCertificateInfo::KeyRSA:
publicKeyMethod = tr("Protected by RSA method, %1-bit key").arg(currentCertificateInfo.getKeySize());
break;
case pdf::PDFCertificateInfo::KeyDSA:
publicKeyMethod = tr("Protected by DSA method, %1-bit key").arg(currentCertificateInfo.getKeySize());
break;
case pdf::PDFCertificateInfo::KeyEC:
publicKeyMethod = tr("Protected by EC method, %1-bit key").arg(currentCertificateInfo.getKeySize());
break;
case pdf::PDFCertificateInfo::KeyDH:
publicKeyMethod = tr("Protected by DH method, %1-bit key").arg(currentCertificateInfo.getKeySize());
break;
case pdf::PDFCertificateInfo::KeyUnknown:
publicKeyMethod = tr("Unknown protection method, %1-bit key").arg(currentCertificateInfo.getKeySize());
break;
default:
Q_ASSERT(false);
break;
}
addName(pdf::PDFCertificateInfo::CountryName, tr("Country"));
addName(pdf::PDFCertificateInfo::OrganizationName, tr("Organization"));
addName(pdf::PDFCertificateInfo::OrganizationalUnitName, tr("Org. unit"));
addName(pdf::PDFCertificateInfo::DistinguishedName, tr("Name"));
addName(pdf::PDFCertificateInfo::StateOrProvinceName, tr("State"));
addName(pdf::PDFCertificateInfo::SerialNumber, tr("Serial number"));
addName(pdf::PDFCertificateInfo::LocalityName, tr("Locality"));
addName(pdf::PDFCertificateInfo::Title, tr("Title"));
addName(pdf::PDFCertificateInfo::Surname, tr("Surname"));
addName(pdf::PDFCertificateInfo::GivenName, tr("Forename"));
addName(pdf::PDFCertificateInfo::Initials, tr("Initials"));
addName(pdf::PDFCertificateInfo::Pseudonym, tr("Pseudonym"));
addName(pdf::PDFCertificateInfo::GenerationalQualifier, tr("Qualifier"));
addName(pdf::PDFCertificateInfo::Email, tr("Email"));
QTreeWidgetItem* publicKeyItem = new QTreeWidgetItem(certRoot, QStringList(publicKeyMethod));
publicKeyItem->setIcon(0, infoIcon);
QDateTime notValidBefore = currentCertificateInfo.getNotValidBefore().toLocalTime();
QDateTime notValidAfter = currentCertificateInfo.getNotValidAfter().toLocalTime();
if (notValidBefore.isValid())
{
QTreeWidgetItem* item = new QTreeWidgetItem(certRoot, QStringList(QString("Valid from: %2").arg(notValidBefore.toString(Qt::DefaultLocaleShortDate))));
item->setIcon(0, infoIcon);
}
if (notValidAfter.isValid())
{
QTreeWidgetItem* item = new QTreeWidgetItem(certRoot, QStringList(QString("Valid to: %2").arg(notValidAfter.toString(Qt::DefaultLocaleShortDate))));
item->setIcon(0, infoIcon);
}
QStringList keyUsages;
pdf::PDFCertificateInfo::KeyUsageFlags keyUsageFlags = currentCertificateInfo.getKeyUsage();
if (keyUsageFlags.testFlag(pdf::PDFCertificateInfo::KeyUsageDigitalSignature))
{
keyUsages << tr("Digital signatures");
}
if (keyUsageFlags.testFlag(pdf::PDFCertificateInfo::KeyUsageNonRepudiation))
{
keyUsages << tr("Non-repudiation");
}
if (keyUsageFlags.testFlag(pdf::PDFCertificateInfo::KeyUsageKeyEncipherment))
{
keyUsages << tr("Key encipherement");
}
if (keyUsageFlags.testFlag(pdf::PDFCertificateInfo::KeyUsageDataEncipherment))
{
keyUsages << tr("Application data encipherement");
}
if (keyUsageFlags.testFlag(pdf::PDFCertificateInfo::KeyUsageAgreement))
{
keyUsages << tr("Key agreement");
}
if (keyUsageFlags.testFlag(pdf::PDFCertificateInfo::KeyUsageCertSign))
{
keyUsages << tr("Verify signatures on certificates");
}
if (keyUsageFlags.testFlag(pdf::PDFCertificateInfo::KeyUsageCrlSign))
{
keyUsages << tr("Verify signatures on revocation information");
}
if (keyUsageFlags.testFlag(pdf::PDFCertificateInfo::KeyUsageEncipherOnly))
{
keyUsages << tr("Encipher data during key agreement");
}
if (keyUsageFlags.testFlag(pdf::PDFCertificateInfo::KeyUsageDecipherOnly))
{
keyUsages << tr("Decipher data during key agreement");
}
if (!keyUsages.isEmpty())
{
QTreeWidgetItem* keyUsageRoot = new QTreeWidgetItem(certRoot, QStringList(tr("Key usages")));
keyUsageRoot->setIcon(0, infoIcon);
for (const QString& keyUsage : keyUsages)
{
QTreeWidgetItem* keyUsageItem = new QTreeWidgetItem(keyUsageRoot, QStringList(keyUsage));
keyUsageItem->setIcon(0, infoIcon);
}
}
}
}
ui->signatureTreeWidget->addTopLevelItem(rootItem);
}
ui->signatureTreeWidget->expandToDepth(1);
ui->signatureTreeWidget->setUpdatesEnabled(true);
}
void PDFSidebarWidget::onPageButtonClicked()
{
QObject* pushButton = sender();

View File

@ -37,10 +37,11 @@ class PDFAction;
class PDFDocument;
class PDFDrawWidgetProxy;
class PDFModifiedDocument;
class PDFOutlineTreeItemModel;
class PDFThumbnailsItemModel;
class PDFAttachmentsTreeItemModel;
class PDFOutlineTreeItemModel;
class PDFOptionalContentActivity;
class PDFAttachmentsTreeItemModel;
class PDFSignatureVerificationResult;
class PDFOptionalContentTreeItemModel;
}
@ -67,10 +68,11 @@ public:
OptionalContent,
Attachments,
Speech,
Signatures,
_END
};
void setDocument(const pdf::PDFModifiedDocument& document);
void setDocument(const pdf::PDFModifiedDocument& document, const std::vector<pdf::PDFSignatureVerificationResult>& signatures);
/// Returns true, if all items in sidebar are empty
bool isEmpty() const;
@ -93,6 +95,7 @@ signals:
private:
void updateGUI(Page preferredPage);
void updateButtons();
void updateSignatures(const std::vector<pdf::PDFSignatureVerificationResult>& signatures);
void onPageButtonClicked();
void onOutlineItemClicked(const QModelIndex& index);
@ -116,6 +119,7 @@ private:
pdf::PDFOptionalContentActivity* m_optionalContentActivity;
pdf::PDFAttachmentsTreeItemModel* m_attachmentsTreeModel;
std::map<Page, PageInfo> m_pageInfo;
std::vector<pdf::PDFSignatureVerificationResult> m_signatures;
};
} // namespace pdfviewer

View File

@ -93,6 +93,19 @@
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="signaturesButton">
<property name="text">
<string>Signatures</string>
</property>
<property name="checkable">
<bool>true</bool>
</property>
<property name="flat">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<spacer name="buttonSpacer">
<property name="orientation">
@ -111,7 +124,7 @@
<item>
<widget class="QStackedWidget" name="stackedWidget">
<property name="currentIndex">
<number>5</number>
<number>6</number>
</property>
<widget class="QWidget" name="emptyPage"/>
<widget class="QWidget" name="bookmarksPage">
@ -392,6 +405,37 @@
</item>
</layout>
</widget>
<widget class="QWidget" name="signaturesPage">
<layout class="QVBoxLayout" name="signaturesPageLayout">
<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="QTreeWidget" name="signatureTreeWidget">
<property name="wordWrap">
<bool>true</bool>
</property>
<property name="headerHidden">
<bool>true</bool>
</property>
<column>
<property name="text">
<string notr="true">1</string>
</property>
</column>
</widget>
</item>
</layout>
</widget>
</widget>
</item>
</layout>

View File

@ -967,8 +967,7 @@ void PDFViewerMainWindow::openDocument(const QString& fileName)
{
// Verify signatures
pdf::PDFForm form = pdf::PDFForm::parse(&document, document.getCatalog()->getFormObject());
std::vector<pdf::PDFSignatureVerificationResult> signaturesVerifications = pdf::PDFSignatureHandler::verifySignatures(form, reader.getSource());
result.signatures = pdf::PDFSignatureHandler::verifySignatures(form, reader.getSource());
result.document.reset(new pdf::PDFDocument(qMove(document)));
}
@ -1001,7 +1000,8 @@ void PDFViewerMainWindow::onDocumentReadingFinished()
// We add file to recent files only, if we have successfully read the document
m_recentFileManager->addRecentFile(m_fileInfo.originalFileName);
m_pdfDocument = result.document;
m_pdfDocument = qMove(result.document);
m_signatures = qMove(result.signatures);
pdf::PDFModifiedDocument document(m_pdfDocument.data(), m_optionalContentActivity);
setDocument(document);
@ -1072,7 +1072,7 @@ void PDFViewerMainWindow::setDocument(pdf::PDFModifiedDocument document)
m_toolManager->setDocument(document);
m_textToSpeech->setDocument(document);
m_pdfWidget->setDocument(document);
m_sidebarWidget->setDocument(document);
m_sidebarWidget->setDocument(document, m_signatures);
m_advancedFindWidget->setDocument(document);
if (m_sidebarWidget->isEmpty())
@ -1109,6 +1109,7 @@ void PDFViewerMainWindow::setDocument(pdf::PDFModifiedDocument document)
void PDFViewerMainWindow::closeDocument()
{
m_signatures.clear();
setDocument(pdf::PDFModifiedDocument());
m_pdfDocument.reset();
updateActionsAvailability();

View File

@ -158,6 +158,7 @@ private:
pdf::PDFDocumentPointer document;
QString errorMessage;
pdf::PDFDocumentReader::Result result = pdf::PDFDocumentReader::Result::Cancelled;
std::vector<pdf::PDFSignatureVerificationResult> signatures;
};
Ui::PDFViewerMainWindow* ui;
@ -179,6 +180,7 @@ private:
QWinTaskbarButton* m_taskbarButton;
QWinTaskbarProgress* m_progressTaskbarIndicator;
PDFFileInfo m_fileInfo;
std::vector<pdf::PDFSignatureVerificationResult> m_signatures;
QFuture<AsyncReadingResult> m_future;
QFutureWatcher<AsyncReadingResult>* m_futureWatcher;

View File

@ -0,0 +1,101 @@
<?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="svg8"
inkscape:version="0.92.4 (5da689c313, 2019-01-14)"
sodipodi:docname="result-error.svg">
<defs
id="defs2">
<inkscape:path-effect
effect="spiro"
id="path-effect831"
is_visible="true" />
</defs>
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="5.6"
inkscape:cx="102.28993"
inkscape:cy="113.44001"
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="metadata5">
<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 />
<cc:license
rdf:resource="http://creativecommons.org/licenses/by-sa/4.0/" />
<dc:creator>
<cc:Agent>
<dc:title>Jakub Melka</dc:title>
</cc:Agent>
</dc:creator>
</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)">
<ellipse
style="fill:#c80000;fill-opacity:1;stroke:none;stroke-width:1.13353121;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:stroke fill markers"
id="path837"
cx="14.991481"
cy="282.01321"
rx="14.859186"
ry="14.835566" />
<g
aria-label="❌"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:25.39999962px;line-height:1.25;font-family:sans-serif;-inkscape-font-specification:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.26458332"
id="text841">
<path
d="m 26.184298,273.67792 -8.458399,8.4708 8.458399,8.47081 -2.666504,2.6665 -8.470801,-8.4584 -8.4708004,8.4584 -2.6665038,-2.6665 8.4583982,-8.47081 -8.4583982,-8.4708 2.6665038,-2.6665 8.4708004,8.4584 8.470801,-8.4584 z"
style="font-size:25.39999962px;fill:#ffffff;fill-opacity:1;stroke-width:0.26458332"
id="path2020"
inkscape:connector-curvature="0" />
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.6 KiB

View File

@ -0,0 +1,101 @@
<?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="svg8"
inkscape:version="0.92.4 (5da689c313, 2019-01-14)"
sodipodi:docname="result-information.svg">
<defs
id="defs2">
<inkscape:path-effect
effect="spiro"
id="path-effect831"
is_visible="true" />
</defs>
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="5.6"
inkscape:cx="142.64708"
inkscape:cy="113.44001"
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="metadata5">
<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>
<cc:license
rdf:resource="http://creativecommons.org/licenses/by-sa/4.0/" />
<dc:creator>
<cc:Agent>
<dc:title>Jakub Melka</dc:title>
</cc:Agent>
</dc:creator>
</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)">
<ellipse
style="fill:#0064e6;fill-opacity:1;stroke:none;stroke-width:1.13353121;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:stroke fill markers"
id="path837"
cx="14.991481"
cy="282.01321"
rx="14.859186"
ry="14.835566" />
<g
aria-label=""
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:25.39999962px;line-height:1.25;font-family:sans-serif;-inkscape-font-specification:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.26458332"
id="text841">
<path
d="m 17.549903,291.45343 h -5.333008 v -13.30771 h 5.333008 z m 0.124023,-15.29209 h -5.581054 v -4.00596 h 5.581054 z"
style="font-size:25.39999962px;fill:#ffffff;fill-opacity:1;stroke-width:0.26458332"
id="path843"
inkscape:connector-curvature="0" />
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.5 KiB

View File

@ -0,0 +1,101 @@
<?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="svg8"
inkscape:version="0.92.4 (5da689c313, 2019-01-14)"
sodipodi:docname="result-ok.svg">
<defs
id="defs2">
<inkscape:path-effect
effect="spiro"
id="path-effect831"
is_visible="true" />
</defs>
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="5.6"
inkscape:cx="102.28993"
inkscape:cy="113.44001"
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="metadata5">
<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 />
<cc:license
rdf:resource="http://creativecommons.org/licenses/by-sa/4.0/" />
<dc:creator>
<cc:Agent>
<dc:title>Jakub Melka</dc:title>
</cc:Agent>
</dc:creator>
</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)">
<ellipse
style="fill:#00c800;fill-opacity:1;stroke:none;stroke-width:1.13353121;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:stroke fill markers"
id="path837"
cx="14.991481"
cy="282.01321"
rx="14.859186"
ry="14.835566" />
<g
aria-label="✓"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:25.39999962px;line-height:1.25;font-family:sans-serif;-inkscape-font-specification:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.26458332"
id="text841">
<path
d="m 6.6240342,286.2373 q 0,-0.39687 -0.099219,-0.69453 0,-0.89297 1.190625,-1.5875 0.79375,-0.39687 1.190625,-0.39687 0.4960937,0.0992 0.6945312,0.59531 0.5953126,1.38906 0.7937506,1.88516 0.198437,0.39687 0.396875,0.39687 0.198437,0 0.396875,-0.29765 3.373437,-5.95313 6.052343,-9.22735 2.083594,-2.48047 2.579688,-2.87734 1.5875,-1.19063 3.770312,-1.28985 l 0.09922,0.4961 q -1.686719,1.68672 -5.853906,7.54062 -4.067969,5.85391 -5.853906,9.22735 -0.396875,0.89297 -0.992188,1.0914 -0.297656,0.29766 -1.2898438,0.19844 -0.6945313,0.0992 -0.9921875,-0.19844 -0.2976563,-0.19843 -0.5953125,-0.79375 -0.9921875,-2.38125 -1.488282,-4.06797 z"
style="font-size:25.39999962px;fill:#ffffff;fill-opacity:1;stroke-width:0.26458332"
id="path2605"
inkscape:connector-curvature="0" />
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 4.0 KiB

View File

@ -0,0 +1,101 @@
<?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="svg8"
inkscape:version="0.92.4 (5da689c313, 2019-01-14)"
sodipodi:docname="result-warning.svg">
<defs
id="defs2">
<inkscape:path-effect
effect="spiro"
id="path-effect831"
is_visible="true" />
</defs>
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="5.6"
inkscape:cx="102.28993"
inkscape:cy="113.44001"
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="metadata5">
<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 />
<cc:license
rdf:resource="http://creativecommons.org/licenses/by-sa/4.0/" />
<dc:creator>
<cc:Agent>
<dc:title>Jakub Melka</dc:title>
</cc:Agent>
</dc:creator>
</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)">
<ellipse
style="fill:#dcaf00;fill-opacity:1;stroke:none;stroke-width:1.13353121;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:stroke fill markers"
id="path837"
cx="14.991481"
cy="282.01321"
rx="14.859186"
ry="14.835566" />
<g
aria-label="⚠"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:25.39999962px;line-height:1.25;font-family:sans-serif;-inkscape-font-specification:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.26458332"
id="text841">
<path
d="M 25.20215,290.10568 H 5.0359398 q -0.2108399,0 -0.3844727,-0.12403 -0.1612304,-0.12402 -0.2604492,-0.29765 -0.099219,-0.17364 -0.099219,-0.37207 0,-0.18604 0.099219,-0.38448 L 14.461721,271.04327 q 0.09922,-0.17363 0.272851,-0.26045 0.173633,-0.0868 0.372071,-0.0868 0.198437,0 0.37207,0.0868 0.173633,0.0868 0.272852,0.26045 l 10.095507,17.88418 q 0.09922,0.19844 0.09922,0.38448 0,0.19843 -0.09922,0.37207 -0.09922,0.17363 -0.272851,0.29765 -0.161231,0.12403 -0.372071,0.12403 z M 6.3257835,288.51818 H 23.887502 l -8.780859,-15.55254 z m 9.9342775,-4.51446 h -2.306836 v -6.8833 h 2.306836 z m 0.347265,2.25723 q 0,0.32246 -0.124023,0.59531 -0.111621,0.27285 -0.322461,0.48369 -0.198438,0.19844 -0.471289,0.32246 -0.272852,0.11163 -0.58291,0.11163 -0.310059,0 -0.58291,-0.11163 -0.272852,-0.12402 -0.471289,-0.32246 -0.198438,-0.21084 -0.310059,-0.48369 -0.111621,-0.27285 -0.111621,-0.59531 0,-0.31006 0.111621,-0.59531 0.111621,-0.28526 0.310059,-0.48369 0.198437,-0.21084 0.471289,-0.33487 0.272851,-0.12402 0.58291,-0.12402 0.310058,0 0.58291,0.12402 0.272851,0.12403 0.471289,0.33487 0.21084,0.19843 0.322461,0.48369 0.124023,0.28525 0.124023,0.59531 z"
style="font-size:25.39999962px;fill:#ffffff;fill-opacity:1;stroke-width:0.26458332"
id="path1445"
inkscape:connector-curvature="0" />
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 4.5 KiB