From c58f11c2e445f644644ec85540e1d09151039b76 Mon Sep 17 00:00:00 2001 From: Jakub Melka Date: Fri, 11 Feb 2022 19:15:57 +0100 Subject: [PATCH 01/39] Signature plugin --- Pdf4QtLib/Pdf4QtLib.pro | 4 + Pdf4QtLib/sources/pdfadvancedtools.cpp | 4 +- Pdf4QtLib/sources/pdfadvancedtools.h | 3 +- .../sources/pdfpagecontenteditortools.cpp | 120 ++++++++++ Pdf4QtLib/sources/pdfpagecontenteditortools.h | 73 ++++++ Pdf4QtLib/sources/pdfpagecontentelements.cpp | 222 ++++++++++++++++++ Pdf4QtLib/sources/pdfpagecontentelements.h | 147 ++++++++++++ Pdf4QtLib/sources/pdfwidgettool.cpp | 2 +- Pdf4QtLib/sources/pdfwidgettool.h | 4 +- .../DimensionsPlugin/DimensionsPlugin.qbs | 2 +- Pdf4QtViewerPlugins/Pdf4QtViewerPlugins.pro | 3 +- .../SignaturePlugin/SignaturePlugin.json | 7 + .../SignaturePlugin/SignaturePlugin.pro | 48 ++++ .../SignaturePlugin/SignaturePlugin.qbs | 15 ++ Pdf4QtViewerPlugins/SignaturePlugin/icons.qrc | 4 + .../SignaturePlugin/signatureplugin.cpp | 170 ++++++++++++++ .../SignaturePlugin/signatureplugin.h | 93 ++++++++ 17 files changed, 913 insertions(+), 8 deletions(-) create mode 100644 Pdf4QtLib/sources/pdfpagecontenteditortools.cpp create mode 100644 Pdf4QtLib/sources/pdfpagecontenteditortools.h create mode 100644 Pdf4QtLib/sources/pdfpagecontentelements.cpp create mode 100644 Pdf4QtLib/sources/pdfpagecontentelements.h create mode 100644 Pdf4QtViewerPlugins/SignaturePlugin/SignaturePlugin.json create mode 100644 Pdf4QtViewerPlugins/SignaturePlugin/SignaturePlugin.pro create mode 100644 Pdf4QtViewerPlugins/SignaturePlugin/SignaturePlugin.qbs create mode 100644 Pdf4QtViewerPlugins/SignaturePlugin/icons.qrc create mode 100644 Pdf4QtViewerPlugins/SignaturePlugin/signatureplugin.cpp create mode 100644 Pdf4QtViewerPlugins/SignaturePlugin/signatureplugin.h diff --git a/Pdf4QtLib/Pdf4QtLib.pro b/Pdf4QtLib/Pdf4QtLib.pro index 3ebfe48..6aaa329 100644 --- a/Pdf4QtLib/Pdf4QtLib.pro +++ b/Pdf4QtLib/Pdf4QtLib.pro @@ -71,6 +71,8 @@ SOURCES += \ sources/pdfoptimizer.cpp \ sources/pdfoptionalcontent.cpp \ sources/pdfoutline.cpp \ + sources/pdfpagecontenteditortools.cpp \ + sources/pdfpagecontentelements.cpp \ sources/pdfpagenavigation.cpp \ sources/pdfpagetransition.cpp \ sources/pdfpainterutils.cpp \ @@ -146,6 +148,8 @@ HEADERS += \ sources/pdfoptimizer.h \ sources/pdfoptionalcontent.h \ sources/pdfoutline.h \ + sources/pdfpagecontenteditortools.h \ + sources/pdfpagecontentelements.h \ sources/pdfpagenavigation.h \ sources/pdfpagetransition.h \ sources/pdfpainterutils.h \ diff --git a/Pdf4QtLib/sources/pdfadvancedtools.cpp b/Pdf4QtLib/sources/pdfadvancedtools.cpp index e69769a..895f93e 100644 --- a/Pdf4QtLib/sources/pdfadvancedtools.cpp +++ b/Pdf4QtLib/sources/pdfadvancedtools.cpp @@ -659,7 +659,7 @@ void PDFCreateFreehandCurveTool::mousePressEvent(QWidget* widget, QMouseEvent* e resetTool(); } - getProxy()->repaintNeeded(); + emit getProxy()->repaintNeeded(); } void PDFCreateFreehandCurveTool::mouseReleaseEvent(QWidget* widget, QMouseEvent* event) @@ -1063,7 +1063,7 @@ void PDFCreateHighlightTextTool::setSelection(PDFTextSelection&& textSelection) if (m_textSelection != textSelection) { m_textSelection = qMove(textSelection); - getProxy()->repaintNeeded(); + emit getProxy()->repaintNeeded(); } } diff --git a/Pdf4QtLib/sources/pdfadvancedtools.h b/Pdf4QtLib/sources/pdfadvancedtools.h index 888a7d3..16e69a0 100644 --- a/Pdf4QtLib/sources/pdfadvancedtools.h +++ b/Pdf4QtLib/sources/pdfadvancedtools.h @@ -166,7 +166,8 @@ private: public: explicit PDFCreateEllipseTool(PDFDrawWidgetProxy* proxy, PDFToolManager* toolManager, QAction* action, QObject* parent); - virtual void drawPage(QPainter* painter, PDFInteger pageIndex, + virtual void drawPage(QPainter* painter, + PDFInteger pageIndex, const PDFPrecompiledPage* compiledPage, PDFTextLayoutGetter& layoutGetter, const QMatrix& pagePointToDevicePointMatrix, diff --git a/Pdf4QtLib/sources/pdfpagecontenteditortools.cpp b/Pdf4QtLib/sources/pdfpagecontenteditortools.cpp new file mode 100644 index 0000000..f9de467 --- /dev/null +++ b/Pdf4QtLib/sources/pdfpagecontenteditortools.cpp @@ -0,0 +1,120 @@ +// Copyright (C) 2022 Jakub Melka +// +// This file is part of PDF4QT. +// +// PDF4QT 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 +// with the written consent of the copyright owner, any later version. +// +// PDF4QT 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 PDF4QT. If not, see . + +#include "pdfpagecontenteditortools.h" +#include "pdfpagecontentelements.h" + +#include + +namespace pdf +{ + +PDFCreatePCElementTool::PDFCreatePCElementTool(PDFDrawWidgetProxy* proxy, + PDFPageContentScene* scene, + QAction* action, + QObject* parent) : + PDFWidgetTool(proxy, action, parent), + m_scene(scene) +{ + +} + +PDFCreatePCElementRectangleTool::PDFCreatePCElementRectangleTool(PDFDrawWidgetProxy* proxy, + PDFPageContentScene* scene, + QAction* action, + bool isRounded, + QObject* parent) : + BaseClass(proxy, scene, action, parent), + m_pickTool(nullptr), + m_element(nullptr) +{ + m_pickTool = new PDFPickTool(proxy, PDFPickTool::Mode::Rectangles, this); + m_pickTool->setDrawSelectionRectangle(false); + addTool(m_pickTool); + connect(m_pickTool, &PDFPickTool::rectanglePicked, this, &PDFCreatePCElementRectangleTool::onRectanglePicked); + + QPen pen(Qt::SolidLine); + pen.setWidthF(1.0); + + m_element = new PDFPageContentElementRectangle(); + m_element->setBrush(Qt::NoBrush); + m_element->setPen(std::move(pen)); + m_element->setRounded(isRounded); + + updateActions(); +} + +PDFCreatePCElementRectangleTool::~PDFCreatePCElementRectangleTool() +{ + delete m_element; +} + +void PDFCreatePCElementRectangleTool::drawPage(QPainter* painter, + PDFInteger pageIndex, + const PDFPrecompiledPage* compiledPage, + PDFTextLayoutGetter& layoutGetter, + const QMatrix& pagePointToDevicePointMatrix, + QList& errors) const +{ + BaseClass::drawPage(painter, pageIndex, compiledPage, layoutGetter, pagePointToDevicePointMatrix, errors); + + if (pageIndex != m_pickTool->getPageIndex()) + { + return; + } + + const std::vector& points = m_pickTool->getPickedPoints(); + if (points.empty()) + { + return; + } + + m_element->setPageIndex(pageIndex); + + QPointF mousePoint = pagePointToDevicePointMatrix.inverted().map(m_pickTool->getSnappedPoint()); + QPointF point = points.front(); + qreal xMin = qMin(point.x(), mousePoint.x()); + qreal xMax = qMax(point.x(), mousePoint.x()); + qreal yMin = qMin(point.y(), mousePoint.y()); + qreal yMax = qMax(point.y(), mousePoint.y()); + qreal width = xMax - xMin; + qreal height = yMax - yMin; + + if (!qFuzzyIsNull(width) && !qFuzzyIsNull(height)) + { + QRectF rect(xMin, yMin, width, height); + m_element->setRectangle(rect); + } + + m_element->drawPage(painter, pageIndex, compiledPage, layoutGetter, pagePointToDevicePointMatrix, errors); +} + +void PDFCreatePCElementRectangleTool::onRectanglePicked(PDFInteger pageIndex, QRectF pageRectangle) +{ + if (pageRectangle.isEmpty()) + { + return; + } + + m_element->setPageIndex(pageIndex); + m_element->setRectangle(pageRectangle); + m_scene->addElement(m_element->clone()); + + setActive(false); +} + +} // namespace pdf diff --git a/Pdf4QtLib/sources/pdfpagecontenteditortools.h b/Pdf4QtLib/sources/pdfpagecontenteditortools.h new file mode 100644 index 0000000..1a0c452 --- /dev/null +++ b/Pdf4QtLib/sources/pdfpagecontenteditortools.h @@ -0,0 +1,73 @@ +// Copyright (C) 2022 Jakub Melka +// +// This file is part of PDF4QT. +// +// PDF4QT 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 +// with the written consent of the copyright owner, any later version. +// +// PDF4QT 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 PDF4QT. If not, see . + +#ifndef PDFPAGECONTENTEDITORTOOLS_H +#define PDFPAGECONTENTEDITORTOOLS_H + +#include "pdfwidgettool.h" + +namespace pdf +{ + +class PDFPageContentScene; +class PDFPageContentElementRectangle; + +class PDFCreatePCElementTool : public PDFWidgetTool +{ +public: + PDFCreatePCElementTool(PDFDrawWidgetProxy* proxy, + PDFPageContentScene* scene, + QAction* action, + QObject* parent); + +protected: + PDFPageContentScene* m_scene; +}; + +/// Tool that creates rectangle element. +class PDF4QTLIBSHARED_EXPORT PDFCreatePCElementRectangleTool : public PDFCreatePCElementTool +{ + Q_OBJECT + +private: + using BaseClass = PDFCreatePCElementTool; + +public: + explicit PDFCreatePCElementRectangleTool(PDFDrawWidgetProxy* proxy, + PDFPageContentScene* scene, + QAction* action, + bool isRounded, + QObject* parent); + virtual ~PDFCreatePCElementRectangleTool() override; + + virtual void drawPage(QPainter* painter, + PDFInteger pageIndex, + const PDFPrecompiledPage* compiledPage, + PDFTextLayoutGetter& layoutGetter, + const QMatrix& pagePointToDevicePointMatrix, + QList& errors) const override; + +private: + void onRectanglePicked(pdf::PDFInteger pageIndex, QRectF pageRectangle); + + PDFPickTool* m_pickTool; + PDFPageContentElementRectangle* m_element; +}; + +} // namespace pdf + +#endif // PDFPAGECONTENTEDITORTOOLS_H diff --git a/Pdf4QtLib/sources/pdfpagecontentelements.cpp b/Pdf4QtLib/sources/pdfpagecontentelements.cpp new file mode 100644 index 0000000..9e5d997 --- /dev/null +++ b/Pdf4QtLib/sources/pdfpagecontentelements.cpp @@ -0,0 +1,222 @@ +// Copyright (C) 2022 Jakub Melka +// +// This file is part of PDF4QT. +// +// PDF4QT 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 +// with the written consent of the copyright owner, any later version. +// +// PDF4QT 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 PDF4QT. If not, see . + +#include "pdfpagecontentelements.h" +#include "pdfpainterutils.h" + +#include +#include +#include +#include + +namespace pdf +{ + +PDFInteger PDFPageContentElement::getPageIndex() const +{ + return m_pageIndex; +} + +void PDFPageContentElement::setPageIndex(PDFInteger newPageIndex) +{ + m_pageIndex = newPageIndex; +} + +const QPen& PDFPageContentStyledElement::getPen() const +{ + return m_pen; +} + +void PDFPageContentStyledElement::setPen(const QPen& newPen) +{ + m_pen = newPen; +} + +const QBrush& PDFPageContentStyledElement::getBrush() const +{ + return m_brush; +} + +void PDFPageContentStyledElement::setBrush(const QBrush& newBrush) +{ + m_brush = newBrush; +} + +PDFPageContentElementRectangle* PDFPageContentElementRectangle::clone() const +{ + PDFPageContentElementRectangle* copy = new PDFPageContentElementRectangle(); + copy->setPageIndex(getPageIndex()); + copy->setPen(getPen()); + copy->setBrush(getBrush()); + copy->setRectangle(getRectangle()); + copy->setRounded(isRounded()); + return copy; +} + +bool PDFPageContentElementRectangle::isRounded() const +{ + return m_rounded; +} + +void PDFPageContentElementRectangle::setRounded(bool newRounded) +{ + m_rounded = newRounded; +} + +const QRectF& PDFPageContentElementRectangle::getRectangle() const +{ + return m_rectangle; +} + +void PDFPageContentElementRectangle::setRectangle(const QRectF& newRectangle) +{ + m_rectangle = newRectangle; +} + +void PDFPageContentElementRectangle::drawPage(QPainter* painter, + PDFInteger pageIndex, + const PDFPrecompiledPage* compiledPage, + PDFTextLayoutGetter& layoutGetter, + const QMatrix& pagePointToDevicePointMatrix, + QList& errors) const +{ + Q_UNUSED(compiledPage); + Q_UNUSED(layoutGetter); + Q_UNUSED(errors); + + if (pageIndex != getPageIndex()) + { + return; + } + + PDFPainterStateGuard guard(painter); + painter->setWorldMatrix(pagePointToDevicePointMatrix, true); + painter->setPen(getPen()); + painter->setBrush(getBrush()); + painter->setRenderHint(QPainter::Antialiasing); + + QRectF rect = getRectangle(); + if (isRounded()) + { + qreal radius = qMin(rect.width(), rect.height()) * 0.25; + painter->drawRoundedRect(rect, radius, radius, Qt::AbsoluteSize); + } + else + { + painter->drawRect(rect); + } +} + +PDFPageContentScene::PDFPageContentScene(QObject* parent) : + QObject(parent) +{ + +} + +PDFPageContentScene::~PDFPageContentScene() +{ + +} + +void PDFPageContentScene::addElement(PDFPageContentElement* element) +{ + m_elements.emplace_back(element); + emit sceneChanged(); +} + +void PDFPageContentScene::clear() +{ + if (!m_elements.empty()) + { + m_elements.clear(); + emit sceneChanged(); + } +} + +void PDFPageContentScene::shortcutOverrideEvent(QWidget* widget, QKeyEvent* event) +{ + Q_UNUSED(widget); + event->ignore(); +} + +void PDFPageContentScene::keyPressEvent(QWidget* widget, QKeyEvent* event) +{ + Q_UNUSED(widget); + event->ignore(); +} + +void PDFPageContentScene::keyReleaseEvent(QWidget* widget, QKeyEvent* event) +{ + Q_UNUSED(widget); + event->ignore(); +} + +void PDFPageContentScene::mousePressEvent(QWidget* widget, QMouseEvent* event) +{ + Q_UNUSED(widget); + event->ignore(); +} + +void PDFPageContentScene::mouseDoubleClickEvent(QWidget* widget, QMouseEvent* event) +{ + Q_UNUSED(widget); + event->ignore(); +} + +void PDFPageContentScene::mouseReleaseEvent(QWidget* widget, QMouseEvent* event) +{ + Q_UNUSED(widget); + event->ignore(); +} + +void PDFPageContentScene::mouseMoveEvent(QWidget* widget, QMouseEvent* event) +{ + Q_UNUSED(widget); + event->ignore(); +} + +void PDFPageContentScene::wheelEvent(QWidget* widget, QWheelEvent* event) +{ + Q_UNUSED(widget); + event->ignore(); +} + +QString PDFPageContentScene::getTooltip() const +{ + return QString(); +} + +const std::optional& PDFPageContentScene::getCursor() const +{ + return std::nullopt; +} + +int PDFPageContentScene::getInputPriority() const +{ + return ToolPriority + 1; +} + +void PDFPageContentScene::drawPage(QPainter* painter, + PDFInteger pageIndex, + const PDFPrecompiledPage* compiledPage, + PDFTextLayoutGetter& layoutGetter, + const QMatrix& pagePointToDevicePointMatrix, + QList& errors) const +{ +} + +} // namespace pdf diff --git a/Pdf4QtLib/sources/pdfpagecontentelements.h b/Pdf4QtLib/sources/pdfpagecontentelements.h new file mode 100644 index 0000000..04f6bba --- /dev/null +++ b/Pdf4QtLib/sources/pdfpagecontentelements.h @@ -0,0 +1,147 @@ +// Copyright (C) 2022 Jakub Melka +// +// This file is part of PDF4QT. +// +// PDF4QT 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 +// with the written consent of the copyright owner, any later version. +// +// PDF4QT 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 PDF4QT. If not, see . + +#ifndef PDFPAGECONTENTELEMENTS_H +#define PDFPAGECONTENTELEMENTS_H + +#include "pdfdocumentdrawinterface.h" + +#include +#include + +namespace pdf +{ + +class PDFPageContentElement +{ +public: + explicit PDFPageContentElement() = default; + virtual ~PDFPageContentElement() = default; + + virtual PDFPageContentElement* clone() const = 0; + + virtual void drawPage(QPainter* painter, + PDFInteger pageIndex, + const PDFPrecompiledPage* compiledPage, + PDFTextLayoutGetter& layoutGetter, + const QMatrix& pagePointToDevicePointMatrix, + QList& errors) const = 0; + + PDFInteger getPageIndex() const; + void setPageIndex(PDFInteger newPageIndex); + +protected: + PDFInteger m_pageIndex = -1; +}; + +class PDFPageContentStyledElement : public PDFPageContentElement +{ +public: + explicit PDFPageContentStyledElement() = default; + virtual ~PDFPageContentStyledElement() = default; + + const QPen& getPen() const; + void setPen(const QPen& newPen); + + const QBrush& getBrush() const; + void setBrush(const QBrush& newBrush); + +protected: + QPen m_pen; + QBrush m_brush; +}; + +class PDFPageContentElementRectangle : public PDFPageContentStyledElement +{ +public: + virtual ~PDFPageContentElementRectangle() = default; + + virtual PDFPageContentElementRectangle* clone() const override; + + bool isRounded() const; + void setRounded(bool newRounded); + + const QRectF& getRectangle() const; + void setRectangle(const QRectF& newRectangle); + + virtual void drawPage(QPainter* painter, + PDFInteger pageIndex, + const PDFPrecompiledPage* compiledPage, + PDFTextLayoutGetter& layoutGetter, + const QMatrix& pagePointToDevicePointMatrix, + QList& errors) const override; + +private: + bool m_rounded = false; + QRectF m_rectangle; +}; + +class PDF4QTLIBSHARED_EXPORT PDFPageContentScene : public QObject, + public IDocumentDrawInterface, + public IDrawWidgetInputInterface +{ + Q_OBJECT + +public: + explicit PDFPageContentScene(QObject* parent); + virtual ~PDFPageContentScene(); + + /// Add new element to page content scene, scene + /// takes ownership over the element. + /// \param element Element + void addElement(PDFPageContentElement* element); + + /// Clear whole scene - remove all page content elements + void clear(); + + /// Returns true, if scene is empty + bool isEmpty() const { return m_elements.empty(); } + + // IDrawWidgetInputInterface interface +public: + virtual void shortcutOverrideEvent(QWidget* widget, QKeyEvent* event) override; + virtual void keyPressEvent(QWidget* widget, QKeyEvent* event) override; + virtual void keyReleaseEvent(QWidget* widget, QKeyEvent* event) override; + virtual void mousePressEvent(QWidget* widget, QMouseEvent* event) override; + virtual void mouseDoubleClickEvent(QWidget* widget, QMouseEvent* event) override; + virtual void mouseReleaseEvent(QWidget* widget, QMouseEvent* event) override; + virtual void mouseMoveEvent(QWidget* widget, QMouseEvent* event) override; + virtual void wheelEvent(QWidget* widget, QWheelEvent* event) override; + virtual QString getTooltip() const override; + virtual const std::optional& getCursor() const override; + virtual int getInputPriority() const override; + + // IDocumentDrawInterface interface +public: + virtual void drawPage(QPainter* painter, + PDFInteger pageIndex, + const PDFPrecompiledPage* compiledPage, + PDFTextLayoutGetter& layoutGetter, + const QMatrix& pagePointToDevicePointMatrix, + QList& errors) const override; + +signals: + /// This signal is emitted when scene has changed (including graphics) + void sceneChanged(); + +private: + std::vector> m_elements; +}; + +} // namespace pdf + +#endif // PDFPAGECONTENTELEMENTS_H diff --git a/Pdf4QtLib/sources/pdfwidgettool.cpp b/Pdf4QtLib/sources/pdfwidgettool.cpp index 7f823b6..2c009b8 100644 --- a/Pdf4QtLib/sources/pdfwidgettool.cpp +++ b/Pdf4QtLib/sources/pdfwidgettool.cpp @@ -1233,7 +1233,7 @@ void PDFPickTool::resetTool() m_snapper.clearReferencePoint(); buildSnapData(); - getProxy()->repaintNeeded(); + emit getProxy()->repaintNeeded(); } void PDFPickTool::buildSnapData() diff --git a/Pdf4QtLib/sources/pdfwidgettool.h b/Pdf4QtLib/sources/pdfwidgettool.h index 52f7c68..01e334b 100644 --- a/Pdf4QtLib/sources/pdfwidgettool.h +++ b/Pdf4QtLib/sources/pdfwidgettool.h @@ -344,8 +344,8 @@ public: void setSelectionRectangleColor(QColor selectionRectangleColor); signals: - void pointPicked(PDFInteger pageIndex, QPointF pagePoint); - void rectanglePicked(PDFInteger pageIndex, QRectF pageRectangle); + void pointPicked(pdf::PDFInteger pageIndex, QPointF pagePoint); + void rectanglePicked(pdf::PDFInteger pageIndex, QRectF pageRectangle); void imagePicked(const QImage& image); protected: diff --git a/Pdf4QtViewerPlugins/DimensionsPlugin/DimensionsPlugin.qbs b/Pdf4QtViewerPlugins/DimensionsPlugin/DimensionsPlugin.qbs index 64b84b8..64e7f7f 100644 --- a/Pdf4QtViewerPlugins/DimensionsPlugin/DimensionsPlugin.qbs +++ b/Pdf4QtViewerPlugins/DimensionsPlugin/DimensionsPlugin.qbs @@ -10,6 +10,6 @@ Pdf4QtPlugin { ] Properties { condition: qbs.hostOS.contains("windows") - cpp.defines: "DIMENTIONPLUGIN_LIBRARY" + cpp.defines: "DIMENSIONPLUGIN_LIBRARY" } } diff --git a/Pdf4QtViewerPlugins/Pdf4QtViewerPlugins.pro b/Pdf4QtViewerPlugins/Pdf4QtViewerPlugins.pro index b9b11bb..0d1fd7b 100644 --- a/Pdf4QtViewerPlugins/Pdf4QtViewerPlugins.pro +++ b/Pdf4QtViewerPlugins/Pdf4QtViewerPlugins.pro @@ -24,6 +24,7 @@ SUBDIRS += \ RedactPlugin \ OutputPreviewPlugin \ ObjectInspectorPlugin \ - AudioBookPlugin + AudioBookPlugin \ + SignaturePlugin diff --git a/Pdf4QtViewerPlugins/SignaturePlugin/SignaturePlugin.json b/Pdf4QtViewerPlugins/SignaturePlugin/SignaturePlugin.json new file mode 100644 index 0000000..2c97fe9 --- /dev/null +++ b/Pdf4QtViewerPlugins/SignaturePlugin/SignaturePlugin.json @@ -0,0 +1,7 @@ +{ + "Name" : "Signature", + "Author" : "Jakub Melka", + "Version" : "1.0.0", + "License" : "LGPL v3", + "Description" : "Electronically or digitally sign PDF document." +} diff --git a/Pdf4QtViewerPlugins/SignaturePlugin/SignaturePlugin.pro b/Pdf4QtViewerPlugins/SignaturePlugin/SignaturePlugin.pro new file mode 100644 index 0000000..cdf7725 --- /dev/null +++ b/Pdf4QtViewerPlugins/SignaturePlugin/SignaturePlugin.pro @@ -0,0 +1,48 @@ +# Copyright (C) 2022 Jakub Melka +# +# This file is part of PDF4QT. +# +# PDF4QT 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 +# with the written consent of the copyright owner, any later version. +# +# PDF4QT 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 PDF4QT. If not, see . + +TEMPLATE = lib +DEFINES += SIGNATUREPLUGIN_LIBRARY + +QT += gui widgets + +LIBS += -L$$OUT_PWD/../.. + +LIBS += -lPdf4QtLib + +QMAKE_CXXFLAGS += /std:c++latest /utf-8 + +INCLUDEPATH += $$PWD/../../Pdf4QtLib/Sources + +DESTDIR = $$OUT_PWD/../../pdfplugins + +CONFIG += c++11 + +SOURCES += \ + signatureplugin.cpp + +HEADERS += \ + signatureplugin.h + +CONFIG += force_debug_info + +DISTFILES += \ + SignaturePlugin.json + +RESOURCES += \ + icons.qrc + diff --git a/Pdf4QtViewerPlugins/SignaturePlugin/SignaturePlugin.qbs b/Pdf4QtViewerPlugins/SignaturePlugin/SignaturePlugin.qbs new file mode 100644 index 0000000..4004868 --- /dev/null +++ b/Pdf4QtViewerPlugins/SignaturePlugin/SignaturePlugin.qbs @@ -0,0 +1,15 @@ +import qbs + +Pdf4QtPlugin { + name: "SignaturePlugin" + files: [ + "*.h", + "*.cpp", + "*.ui", + "icons.qrc", + ] + Properties { + condition: qbs.hostOS.contains("windows") + cpp.defines: "SIGNATUREPLUGIN_LIBRARY" + } +} diff --git a/Pdf4QtViewerPlugins/SignaturePlugin/icons.qrc b/Pdf4QtViewerPlugins/SignaturePlugin/icons.qrc new file mode 100644 index 0000000..c133fe1 --- /dev/null +++ b/Pdf4QtViewerPlugins/SignaturePlugin/icons.qrc @@ -0,0 +1,4 @@ + + + + diff --git a/Pdf4QtViewerPlugins/SignaturePlugin/signatureplugin.cpp b/Pdf4QtViewerPlugins/SignaturePlugin/signatureplugin.cpp new file mode 100644 index 0000000..6c58edd --- /dev/null +++ b/Pdf4QtViewerPlugins/SignaturePlugin/signatureplugin.cpp @@ -0,0 +1,170 @@ +// Copyright (C) 2022 Jakub Melka +// +// This file is part of PDF4QT. +// +// PDF4QT 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 +// with the written consent of the copyright owner, any later version. +// +// PDF4QT 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 PDF4QT. If not, see . + +#include "signatureplugin.h" +#include "pdfdrawwidget.h" + +#include + +namespace pdfplugin +{ + +SignaturePlugin::SignaturePlugin() : + pdf::PDFPlugin(nullptr), + m_actions({ }), + m_tools({ }), + m_scene(nullptr) +{ + +} + +void SignaturePlugin::setWidget(pdf::PDFWidget* widget) +{ + Q_ASSERT(!m_widget); + + BaseClass::setWidget(widget); + + QAction* createTextAction = new QAction(QIcon(":/pdfplugins/signaturetool/create-text.svg"), tr("Create Text Label"), this); + QAction* createAcceptMarkAction = new QAction(QIcon(":/pdfplugins/signaturetool/create-yes-mark.svg"), tr("Create Accept Mark"), this); + QAction* createRejectMarkAction = new QAction(QIcon(":/pdfplugins/signaturetool/create-no-mark.svg"), tr("Create Reject Mark"), this); + QAction* createRectangleAction = new QAction(QIcon(":/pdfplugins/signaturetool/create-rectangle.svg"), tr("Create Rectangle"), this); + QAction* createRoundedRectangleAction = new QAction(QIcon(":/pdfplugins/signaturetool/create-rounded-rectangle.svg"), tr("Create Rounded Rectangle"), this); + QAction* createHorizontalLineAction = new QAction(QIcon(":/pdfplugins/signaturetool/create-horizontal-line.svg"), tr("Create Horizontal Line"), this); + QAction* createVerticalLineAction = new QAction(QIcon(":/pdfplugins/signaturetool/create-vertical-line.svg"), tr("Create Vertical Line"), this); + QAction* createLineAction = new QAction(QIcon(":/pdfplugins/signaturetool/create-line.svg"), tr("Create Line"), this); + QAction* createDotAction = new QAction(QIcon(":/pdfplugins/signaturetool/create-dot.svg"), tr("Create Dot"), this); + QAction* createSvgImageAction = new QAction(QIcon(":/pdfplugins/signaturetool/create-svg-image.svg"), tr("Create SVG Image"), this); + QAction* clearAction = new QAction(QIcon(":/pdfplugins/signaturetool/clear.svg"), tr("Clear All Graphics"), this); + QAction* setColorAction = new QAction(QIcon(":/pdfplugins/signaturetool/set-color.svg"), tr("Set Color"), this); + QAction* setPenAction = new QAction(QIcon(":/pdfplugins/signaturetool/set-pen.svg"), tr("Set Pen"), this); + QAction* setBrushAction = new QAction(QIcon(":/pdfplugins/signaturetool/set-brush.svg"), tr("Set Brush"), this); + QAction* signElectronicallyAction = new QAction(QIcon(":/pdfplugins/signaturetool/sign-electronically.svg"), tr("Sign Electronically"), this); + QAction* signDigitallyAction = new QAction(QIcon(":/pdfplugins/signaturetool/sign-digitally.svg"), tr("Sign Digitally With Certificate"), this); + QAction* certificatesAction = new QAction(QIcon(":/pdfplugins/signaturetool/certificates.svg"), tr("Certificates Manager"), this); + + createTextAction->setObjectName("signaturetool_createTextAction"); + createAcceptMarkAction->setObjectName("signaturetool_createAcceptMarkAction"); + createRejectMarkAction->setObjectName("signaturetool_createRejectMarkAction"); + createRectangleAction->setObjectName("signaturetool_createRectangleAction"); + createRoundedRectangleAction->setObjectName("signaturetool_createRoundedRectangleAction"); + createHorizontalLineAction->setObjectName("signaturetool_createHorizontalLineAction"); + createVerticalLineAction->setObjectName("signaturetool_createVerticalLineAction"); + createLineAction->setObjectName("signaturetool_createLineAction"); + createDotAction->setObjectName("signaturetool_createDotAction"); + createSvgImageAction->setObjectName("signaturetool_createSvgImageAction"); + clearAction->setObjectName("signaturetool_clearAction"); + setColorAction->setObjectName("signaturetool_setColorAction"); + setPenAction->setObjectName("signaturetool_setPenAction"); + setBrushAction->setObjectName("signaturetool_setBrushAction"); + signElectronicallyAction->setObjectName("signaturetool_signElectronicallyAction"); + signDigitallyAction->setObjectName("signaturetool_signDigitallyAction"); + certificatesAction->setObjectName("signaturetool_certificatesAction"); + + createTextAction->setCheckable(true); + createAcceptMarkAction->setCheckable(true); + createRejectMarkAction->setCheckable(true); + createRectangleAction->setCheckable(true); + createRoundedRectangleAction->setCheckable(true); + createHorizontalLineAction->setCheckable(true); + createVerticalLineAction->setCheckable(true); + createLineAction->setCheckable(true); + createDotAction->setCheckable(true); + createSvgImageAction->setCheckable(true); + + m_actions[Text] = createTextAction; + m_actions[AcceptMark] = createAcceptMarkAction; + m_actions[RejectMark] = createRejectMarkAction; + m_actions[Rectangle] = createRectangleAction; + m_actions[RoundedRectangle] = createRoundedRectangleAction; + m_actions[HorizontalLine] = createHorizontalLineAction; + m_actions[VerticalLine] = createVerticalLineAction; + m_actions[Line] = createLineAction; + m_actions[Dot] = createDotAction; + m_actions[SvgImage] = createSvgImageAction; + m_actions[Clear] = clearAction; + m_actions[SetColor] = setColorAction; + m_actions[SetPen] = setPenAction; + m_actions[SetBrush] = setBrushAction; + m_actions[SignElectronically] = signElectronicallyAction; + m_actions[SignDigitally] = signDigitallyAction; + m_actions[Ceritificates] = certificatesAction; + + m_tools[RectangleTool] = new pdf::PDFCreatePCElementRectangleTool(widget->getDrawWidgetProxy(), &m_scene, createRectangleAction, false, this); + m_tools[RoundedRectangleTool] = new pdf::PDFCreatePCElementRectangleTool(widget->getDrawWidgetProxy(), &m_scene, createRoundedRectangleAction, true, this); + + pdf::PDFToolManager* toolManager = widget->getToolManager(); + for (pdf::PDFWidgetTool* tool : m_tools) + { + toolManager->addTool(tool); + } + + m_widget->getDrawWidgetProxy()->registerDrawInterface(&m_scene); + + updateActions(); +} + +void SignaturePlugin::setDocument(const pdf::PDFModifiedDocument& document) +{ + BaseClass::setDocument(document); + + if (document.hasReset()) + { + m_scene.clear(); + updateActions(); + } +} + +std::vector SignaturePlugin::getActions() const +{ + std::vector result; + + result.push_back(m_actions[Text]); + result.push_back(m_actions[AcceptMark]); + result.push_back(m_actions[RejectMark]); + result.push_back(m_actions[Rectangle]); + result.push_back(m_actions[RoundedRectangle]); + result.push_back(m_actions[HorizontalLine]); + result.push_back(m_actions[VerticalLine]); + result.push_back(m_actions[Line]); + result.push_back(m_actions[Dot]); + result.push_back(m_actions[Clear]); + result.push_back(nullptr); + result.push_back(m_actions[SetColor]); + result.push_back(m_actions[SetPen]); + result.push_back(m_actions[SetBrush]); + result.push_back(nullptr); + result.push_back(m_actions[SignElectronically]); + result.push_back(m_actions[SignDigitally]); + result.push_back(m_actions[Ceritificates]); + + return result; +} + +void SignaturePlugin::updateActions() +{ + +} + +void SignaturePlugin::updateGraphics() +{ + if (m_widget) + { + m_widget->getDrawWidget()->getWidget()->update(); + } +} + +} diff --git a/Pdf4QtViewerPlugins/SignaturePlugin/signatureplugin.h b/Pdf4QtViewerPlugins/SignaturePlugin/signatureplugin.h new file mode 100644 index 0000000..eae90b1 --- /dev/null +++ b/Pdf4QtViewerPlugins/SignaturePlugin/signatureplugin.h @@ -0,0 +1,93 @@ +// Copyright (C) 2022 Jakub Melka +// +// This file is part of PDF4QT. +// +// PDF4QT 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 +// with the written consent of the copyright owner, any later version. +// +// PDF4QT 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 PDF4QT. If not, see . + +#ifndef SIGNATURESPLUGIN_H +#define SIGNATURESPLUGIN_H + +#include "pdfplugin.h" +#include "pdfpagecontentelements.h" +#include "pdfpagecontenteditortools.h" + +#include + +namespace pdfplugin +{ + +class SignaturePlugin : public pdf::PDFPlugin +{ + Q_OBJECT + Q_PLUGIN_METADATA(IID "PDF4QT.SignaturePlugin" FILE "SignaturePlugin.json") + +private: + using BaseClass = pdf::PDFPlugin; + +public: + SignaturePlugin(); + + virtual void setWidget(pdf::PDFWidget* widget) override; + virtual void setDocument(const pdf::PDFModifiedDocument& document) override; + virtual std::vector getActions() const override; + +private: + + enum Action + { + // Create graphics actions + Text, + AcceptMark, + RejectMark, + Rectangle, + RoundedRectangle, + HorizontalLine, + VerticalLine, + Line, + Dot, + SvgImage, + Clear, + + // Settings actions + SetColor, + SetPen, + SetBrush, + + // Sign actions + SignElectronically, + SignDigitally, + Ceritificates, + + LastAction + }; + + enum Tools + { + RectangleTool, + RoundedRectangleTool, + LastTool + }; + + void updateActions(); + void updateGraphics(); + + std::array m_actions; + std::array m_tools; + + pdf::PDFPageContentScene m_scene; +}; + +} // namespace pdfplugin + +#endif // SIGNATURESPLUGIN_H From b61fcff27df860029c7fe26e2b2ea0590645d65d Mon Sep 17 00:00:00 2001 From: Jakub Melka Date: Sun, 13 Feb 2022 19:46:09 +0100 Subject: [PATCH 02/39] Signatures: line tool --- .../sources/pdfpagecontenteditortools.cpp | 104 ++++++++++++++++++ Pdf4QtLib/sources/pdfpagecontenteditortools.h | 34 ++++++ Pdf4QtLib/sources/pdfpagecontentelements.cpp | 88 +++++++++++++-- Pdf4QtLib/sources/pdfpagecontentelements.h | 34 ++++++ .../SignaturePlugin/signatureplugin.cpp | 6 +- .../SignaturePlugin/signatureplugin.h | 3 + qt.conf | 2 + 7 files changed, 263 insertions(+), 8 deletions(-) create mode 100644 qt.conf diff --git a/Pdf4QtLib/sources/pdfpagecontenteditortools.cpp b/Pdf4QtLib/sources/pdfpagecontenteditortools.cpp index f9de467..74a751a 100644 --- a/Pdf4QtLib/sources/pdfpagecontenteditortools.cpp +++ b/Pdf4QtLib/sources/pdfpagecontenteditortools.cpp @@ -117,4 +117,108 @@ void PDFCreatePCElementRectangleTool::onRectanglePicked(PDFInteger pageIndex, QR setActive(false); } +PDFCreatePCElementLineTool::PDFCreatePCElementLineTool(PDFDrawWidgetProxy* proxy, + PDFPageContentScene* scene, + QAction* action, + bool isHorizontal, + bool isVertical, + QObject* parent) : + BaseClass(proxy, scene, action, parent), + m_pickTool(nullptr), + m_element(nullptr) +{ + m_pickTool = new PDFPickTool(proxy, PDFPickTool::Mode::Points, this); + m_pickTool->setDrawSelectionRectangle(false); + addTool(m_pickTool); + connect(m_pickTool, &PDFPickTool::pointPicked, this, &PDFCreatePCElementLineTool::onPointPicked); + + QPen pen(Qt::SolidLine); + pen.setWidthF(2.0); + pen.setCapStyle(Qt::RoundCap); + + PDFPageContentElementLine::LineGeometry geometry = PDFPageContentElementLine::LineGeometry::General; + + if (isHorizontal) + { + geometry = PDFPageContentElementLine::LineGeometry::Horizontal; + } + + if (isVertical) + { + geometry = PDFPageContentElementLine::LineGeometry::Vertical; + } + + m_element = new PDFPageContentElementLine(); + m_element->setBrush(Qt::NoBrush); + m_element->setPen(std::move(pen)); + m_element->setGeometry(geometry); + + updateActions(); +} + +PDFCreatePCElementLineTool::~PDFCreatePCElementLineTool() +{ + delete m_element; +} + +void PDFCreatePCElementLineTool::drawPage(QPainter* painter, + PDFInteger pageIndex, + const PDFPrecompiledPage* compiledPage, + PDFTextLayoutGetter& layoutGetter, + const QMatrix& pagePointToDevicePointMatrix, + QList& errors) const +{ + BaseClass::drawPage(painter, pageIndex, compiledPage, layoutGetter, pagePointToDevicePointMatrix, errors); + + if (pageIndex != m_pickTool->getPageIndex() || !m_startPoint) + { + return; + } + + m_element->setPageIndex(pageIndex); + + QPointF startPoint = *m_startPoint; + QPointF endPoint = pagePointToDevicePointMatrix.inverted().map(m_pickTool->getSnappedPoint()); + QLineF line(startPoint, endPoint); + + if (!qFuzzyIsNull(line.length())) + { + m_element->setLine(line); + } + + m_element->drawPage(painter, pageIndex, compiledPage, layoutGetter, pagePointToDevicePointMatrix, errors); +} + +void PDFCreatePCElementLineTool::clear() +{ + m_startPoint = std::nullopt; +} + +void PDFCreatePCElementLineTool::onPointPicked(PDFInteger pageIndex, QPointF pagePoint) +{ + if (!m_startPoint || m_element->getPageIndex() != pageIndex) + { + m_startPoint = pagePoint; + m_element->setPageIndex(pageIndex); + m_element->setLine(QLineF(pagePoint, pagePoint)); + return; + } + + if (qFuzzyCompare(m_startPoint.value().x(), pagePoint.x()) && + qFuzzyCompare(m_startPoint.value().y(), pagePoint.y())) + { + // Jakub Melka: Point is same as the start point + clear(); + return; + } + + QLineF line = m_element->getLine(); + line.setP2(pagePoint); + m_element->setLine(line); + m_scene->addElement(m_element->clone()); + clear(); + + setActive(false); +} + } // namespace pdf diff --git a/Pdf4QtLib/sources/pdfpagecontenteditortools.h b/Pdf4QtLib/sources/pdfpagecontenteditortools.h index 1a0c452..759870a 100644 --- a/Pdf4QtLib/sources/pdfpagecontenteditortools.h +++ b/Pdf4QtLib/sources/pdfpagecontenteditortools.h @@ -24,6 +24,7 @@ namespace pdf { class PDFPageContentScene; +class PDFPageContentElementLine; class PDFPageContentElementRectangle; class PDFCreatePCElementTool : public PDFWidgetTool @@ -68,6 +69,39 @@ private: PDFPageContentElementRectangle* m_element; }; +/// Tool that creates line element. +class PDF4QTLIBSHARED_EXPORT PDFCreatePCElementLineTool : public PDFCreatePCElementTool +{ + Q_OBJECT + +private: + using BaseClass = PDFCreatePCElementTool; + +public: + explicit PDFCreatePCElementLineTool(PDFDrawWidgetProxy* proxy, + PDFPageContentScene* scene, + QAction* action, + bool isHorizontal, + bool isVertical, + QObject* parent); + virtual ~PDFCreatePCElementLineTool() override; + + virtual void drawPage(QPainter* painter, + PDFInteger pageIndex, + const PDFPrecompiledPage* compiledPage, + PDFTextLayoutGetter& layoutGetter, + const QMatrix& pagePointToDevicePointMatrix, + QList& errors) const override; + +private: + void clear(); + void onPointPicked(pdf::PDFInteger pageIndex, QPointF pagePoint); + + PDFPickTool* m_pickTool; + PDFPageContentElementLine* m_element; + std::optional m_startPoint; +}; + } // namespace pdf #endif // PDFPAGECONTENTEDITORTOOLS_H diff --git a/Pdf4QtLib/sources/pdfpagecontentelements.cpp b/Pdf4QtLib/sources/pdfpagecontentelements.cpp index 9e5d997..49f33d8 100644 --- a/Pdf4QtLib/sources/pdfpagecontentelements.cpp +++ b/Pdf4QtLib/sources/pdfpagecontentelements.cpp @@ -21,7 +21,6 @@ #include #include #include -#include namespace pdf { @@ -202,7 +201,7 @@ QString PDFPageContentScene::getTooltip() const const std::optional& PDFPageContentScene::getCursor() const { - return std::nullopt; + return m_cursor; } int PDFPageContentScene::getInputPriority() const @@ -211,12 +210,87 @@ int PDFPageContentScene::getInputPriority() const } void PDFPageContentScene::drawPage(QPainter* painter, - PDFInteger pageIndex, - const PDFPrecompiledPage* compiledPage, - PDFTextLayoutGetter& layoutGetter, - const QMatrix& pagePointToDevicePointMatrix, - QList& errors) const + PDFInteger pageIndex, + const PDFPrecompiledPage* compiledPage, + PDFTextLayoutGetter& layoutGetter, + const QMatrix& pagePointToDevicePointMatrix, + QList& errors) const { + for (const auto& element : m_elements) + { + if (element->getPageIndex() != pageIndex) + { + continue; + } + + element->drawPage(painter, pageIndex, compiledPage, layoutGetter, pagePointToDevicePointMatrix, errors); + } +} + +PDFPageContentElementLine* PDFPageContentElementLine::clone() const +{ + PDFPageContentElementLine* copy = new PDFPageContentElementLine(); + copy->setPageIndex(getPageIndex()); + copy->setPen(getPen()); + copy->setBrush(getBrush()); + copy->setGeometry(getGeometry()); + copy->setLine(getLine()); + return copy; +} + +void PDFPageContentElementLine::drawPage(QPainter* painter, + PDFInteger pageIndex, + const PDFPrecompiledPage* compiledPage, + PDFTextLayoutGetter& layoutGetter, + const QMatrix& pagePointToDevicePointMatrix, + QList& errors) const +{ + Q_UNUSED(compiledPage); + Q_UNUSED(layoutGetter); + Q_UNUSED(errors); + + if (pageIndex != getPageIndex()) + { + return; + } + + PDFPainterStateGuard guard(painter); + painter->setWorldMatrix(pagePointToDevicePointMatrix, true); + painter->setPen(getPen()); + painter->setBrush(getBrush()); + painter->setRenderHint(QPainter::Antialiasing); + + painter->drawLine(getLine()); +} + +PDFPageContentElementLine::LineGeometry PDFPageContentElementLine::getGeometry() const +{ + return m_geometry; +} + +void PDFPageContentElementLine::setGeometry(LineGeometry newGeometry) +{ + m_geometry = newGeometry; +} + +const QLineF& PDFPageContentElementLine::getLine() const +{ + return m_line; +} + +void PDFPageContentElementLine::setLine(const QLineF& newLine) +{ + m_line = newLine; + + if (m_geometry == LineGeometry::Horizontal) + { + m_line.setP2(QPointF(newLine.p2().x(), newLine.p1().y())); + } + + if (m_geometry == LineGeometry::Vertical) + { + m_line.setP2(QPointF(newLine.p1().x(), newLine.p2().y())); + } } } // namespace pdf diff --git a/Pdf4QtLib/sources/pdfpagecontentelements.h b/Pdf4QtLib/sources/pdfpagecontentelements.h index 04f6bba..419cdae 100644 --- a/Pdf4QtLib/sources/pdfpagecontentelements.h +++ b/Pdf4QtLib/sources/pdfpagecontentelements.h @@ -22,6 +22,7 @@ #include #include +#include namespace pdf { @@ -90,6 +91,38 @@ private: QRectF m_rectangle; }; +class PDFPageContentElementLine : public PDFPageContentStyledElement +{ +public: + virtual ~PDFPageContentElementLine() = default; + + virtual PDFPageContentElementLine* clone() const override; + + enum class LineGeometry + { + General, + Horizontal, + Vertical + }; + + virtual void drawPage(QPainter* painter, + PDFInteger pageIndex, + const PDFPrecompiledPage* compiledPage, + PDFTextLayoutGetter& layoutGetter, + const QMatrix& pagePointToDevicePointMatrix, + QList& errors) const override; + + LineGeometry getGeometry() const; + void setGeometry(LineGeometry newGeometry); + + const QLineF& getLine() const; + void setLine(const QLineF& newLine); + +private: + LineGeometry m_geometry = LineGeometry::General; + QLineF m_line; +}; + class PDF4QTLIBSHARED_EXPORT PDFPageContentScene : public QObject, public IDocumentDrawInterface, public IDrawWidgetInputInterface @@ -140,6 +173,7 @@ signals: private: std::vector> m_elements; + std::optional m_cursor; }; } // namespace pdf diff --git a/Pdf4QtViewerPlugins/SignaturePlugin/signatureplugin.cpp b/Pdf4QtViewerPlugins/SignaturePlugin/signatureplugin.cpp index 6c58edd..6e07350 100644 --- a/Pdf4QtViewerPlugins/SignaturePlugin/signatureplugin.cpp +++ b/Pdf4QtViewerPlugins/SignaturePlugin/signatureplugin.cpp @@ -105,6 +105,9 @@ void SignaturePlugin::setWidget(pdf::PDFWidget* widget) m_tools[RectangleTool] = new pdf::PDFCreatePCElementRectangleTool(widget->getDrawWidgetProxy(), &m_scene, createRectangleAction, false, this); m_tools[RoundedRectangleTool] = new pdf::PDFCreatePCElementRectangleTool(widget->getDrawWidgetProxy(), &m_scene, createRoundedRectangleAction, true, this); + m_tools[HorizontalLineTool] = new pdf::PDFCreatePCElementLineTool(widget->getDrawWidgetProxy(), &m_scene, createHorizontalLineAction, true, false, this); + m_tools[VerticalLineTool] = new pdf::PDFCreatePCElementLineTool(widget->getDrawWidgetProxy(), &m_scene, createVerticalLineAction, false, true, this); + m_tools[LineTool] = new pdf::PDFCreatePCElementLineTool(widget->getDrawWidgetProxy(), &m_scene, createLineAction, false, false, this); pdf::PDFToolManager* toolManager = widget->getToolManager(); for (pdf::PDFWidgetTool* tool : m_tools) @@ -156,7 +159,8 @@ std::vector SignaturePlugin::getActions() const void SignaturePlugin::updateActions() { - + QAction* clearAction = m_actions[Clear]; + clearAction->setEnabled(!m_scene.isEmpty()); } void SignaturePlugin::updateGraphics() diff --git a/Pdf4QtViewerPlugins/SignaturePlugin/signatureplugin.h b/Pdf4QtViewerPlugins/SignaturePlugin/signatureplugin.h index eae90b1..4a89083 100644 --- a/Pdf4QtViewerPlugins/SignaturePlugin/signatureplugin.h +++ b/Pdf4QtViewerPlugins/SignaturePlugin/signatureplugin.h @@ -76,6 +76,9 @@ private: { RectangleTool, RoundedRectangleTool, + HorizontalLineTool, + VerticalLineTool, + LineTool, LastTool }; diff --git a/qt.conf b/qt.conf new file mode 100644 index 0000000..5cfc332 --- /dev/null +++ b/qt.conf @@ -0,0 +1,2 @@ +[Platforms] +WindowsArguments = fontengine=freetype \ No newline at end of file From f4e00f2f03a6718fe1d58b3092e32241aa9c70a3 Mon Sep 17 00:00:00 2001 From: Jakub Melka Date: Sat, 19 Feb 2022 19:30:52 +0100 Subject: [PATCH 03/39] Signature plugin: Accept/reject mark --- Pdf4QtLib/Pdf4QtLib.pro | 2 +- .../sources/pdfpagecontenteditortools.cpp | 122 +++++++++++++++--- Pdf4QtLib/sources/pdfpagecontenteditortools.h | 33 +++++ Pdf4QtLib/sources/pdfpagecontentelements.cpp | 85 ++++++++++++ Pdf4QtLib/sources/pdfpagecontentelements.h | 37 +++++- .../SignaturePlugin/accept-mark.svg | 13 ++ Pdf4QtViewerPlugins/SignaturePlugin/icons.qrc | 2 + .../SignaturePlugin/reject-mark.svg | 16 +++ .../SignaturePlugin/signatureplugin.cpp | 18 +++ .../SignaturePlugin/signatureplugin.h | 2 + 10 files changed, 308 insertions(+), 22 deletions(-) create mode 100644 Pdf4QtViewerPlugins/SignaturePlugin/accept-mark.svg create mode 100644 Pdf4QtViewerPlugins/SignaturePlugin/reject-mark.svg diff --git a/Pdf4QtLib/Pdf4QtLib.pro b/Pdf4QtLib/Pdf4QtLib.pro index 6aaa329..442ce9a 100644 --- a/Pdf4QtLib/Pdf4QtLib.pro +++ b/Pdf4QtLib/Pdf4QtLib.pro @@ -15,7 +15,7 @@ # You should have received a copy of the GNU Lesser General Public License # along with PDF4QT. If not, see . -QT += gui widgets xml +QT += gui widgets xml svg TARGET = Pdf4QtLib TEMPLATE = lib diff --git a/Pdf4QtLib/sources/pdfpagecontenteditortools.cpp b/Pdf4QtLib/sources/pdfpagecontenteditortools.cpp index 74a751a..067125c 100644 --- a/Pdf4QtLib/sources/pdfpagecontenteditortools.cpp +++ b/Pdf4QtLib/sources/pdfpagecontenteditortools.cpp @@ -17,8 +17,10 @@ #include "pdfpagecontenteditortools.h" #include "pdfpagecontentelements.h" +#include "pdfpainterutils.h" #include +#include namespace pdf { @@ -33,6 +35,32 @@ PDFCreatePCElementTool::PDFCreatePCElementTool(PDFDrawWidgetProxy* proxy, } +QRectF PDFCreatePCElementTool::getRectangleFromPickTool(PDFPickTool* pickTool, + const QMatrix& pagePointToDevicePointMatrix) +{ + const std::vector& points = pickTool->getPickedPoints(); + if (points.empty()) + { + return QRectF(); + } + + QPointF mousePoint = pagePointToDevicePointMatrix.inverted().map(pickTool->getSnappedPoint()); + QPointF point = points.front(); + qreal xMin = qMin(point.x(), mousePoint.x()); + qreal xMax = qMax(point.x(), mousePoint.x()); + qreal yMin = qMin(point.y(), mousePoint.y()); + qreal yMax = qMax(point.y(), mousePoint.y()); + qreal width = xMax - xMin; + qreal height = yMax - yMin; + + if (!qFuzzyIsNull(width) && !qFuzzyIsNull(height)) + { + return QRectF(xMin, yMin, width, height); + } + + return QRectF(); +} + PDFCreatePCElementRectangleTool::PDFCreatePCElementRectangleTool(PDFDrawWidgetProxy* proxy, PDFPageContentScene* scene, QAction* action, @@ -77,28 +105,14 @@ void PDFCreatePCElementRectangleTool::drawPage(QPainter* painter, return; } - const std::vector& points = m_pickTool->getPickedPoints(); - if (points.empty()) + QRectF rectangle = getRectangleFromPickTool(m_pickTool, pagePointToDevicePointMatrix); + if (!rectangle.isValid()) { return; } m_element->setPageIndex(pageIndex); - - QPointF mousePoint = pagePointToDevicePointMatrix.inverted().map(m_pickTool->getSnappedPoint()); - QPointF point = points.front(); - qreal xMin = qMin(point.x(), mousePoint.x()); - qreal xMax = qMax(point.x(), mousePoint.x()); - qreal yMin = qMin(point.y(), mousePoint.y()); - qreal yMax = qMax(point.y(), mousePoint.y()); - qreal width = xMax - xMin; - qreal height = yMax - yMin; - - if (!qFuzzyIsNull(width) && !qFuzzyIsNull(height)) - { - QRectF rect(xMin, yMin, width, height); - m_element->setRectangle(rect); - } + m_element->setRectangle(rectangle); m_element->drawPage(painter, pageIndex, compiledPage, layoutGetter, pagePointToDevicePointMatrix, errors); } @@ -221,4 +235,78 @@ void PDFCreatePCElementLineTool::onPointPicked(PDFInteger pageIndex, QPointF pag setActive(false); } +PDFCreatePCElementSvgTool::PDFCreatePCElementSvgTool(PDFDrawWidgetProxy* proxy, + PDFPageContentScene* scene, + QAction* action, + QByteArray content, + QObject* parent) : + BaseClass(proxy, scene, action, parent), + m_pickTool(nullptr), + m_element(nullptr) +{ + m_pickTool = new PDFPickTool(proxy, PDFPickTool::Mode::Rectangles, this); + m_pickTool->setDrawSelectionRectangle(false); + addTool(m_pickTool); + connect(m_pickTool, &PDFPickTool::rectanglePicked, this, &PDFCreatePCElementSvgTool::onRectanglePicked); + + m_element = new PDFPageContentSvgElement(); + m_element->setContent(content); + + updateActions(); +} + +PDFCreatePCElementSvgTool::~PDFCreatePCElementSvgTool() +{ + delete m_element; +} + +void PDFCreatePCElementSvgTool::drawPage(QPainter* painter, + PDFInteger pageIndex, + const PDFPrecompiledPage* compiledPage, + PDFTextLayoutGetter& layoutGetter, + const QMatrix& pagePointToDevicePointMatrix, + QList& errors) const +{ + BaseClass::drawPage(painter, pageIndex, compiledPage, layoutGetter, pagePointToDevicePointMatrix, errors); + + if (pageIndex != m_pickTool->getPageIndex()) + { + return; + } + + QRectF rectangle = getRectangleFromPickTool(m_pickTool, pagePointToDevicePointMatrix); + if (!rectangle.isValid()) + { + return; + } + + m_element->setPageIndex(pageIndex); + m_element->setRectangle(rectangle); + + { + PDFPainterStateGuard guard(painter); + painter->setWorldMatrix(pagePointToDevicePointMatrix, true); + painter->setRenderHint(QPainter::Antialiasing); + painter->setPen(Qt::DotLine); + painter->setBrush(Qt::NoBrush); + painter->drawRect(rectangle); + } + + m_element->drawPage(painter, pageIndex, compiledPage, layoutGetter, pagePointToDevicePointMatrix, errors); +} + +void PDFCreatePCElementSvgTool::onRectanglePicked(PDFInteger pageIndex, QRectF pageRectangle) +{ + if (pageRectangle.isEmpty()) + { + return; + } + + m_element->setPageIndex(pageIndex); + m_element->setRectangle(pageRectangle); + m_scene->addElement(m_element->clone()); + + setActive(false); +} + } // namespace pdf diff --git a/Pdf4QtLib/sources/pdfpagecontenteditortools.h b/Pdf4QtLib/sources/pdfpagecontenteditortools.h index 759870a..f0f7598 100644 --- a/Pdf4QtLib/sources/pdfpagecontenteditortools.h +++ b/Pdf4QtLib/sources/pdfpagecontenteditortools.h @@ -24,6 +24,7 @@ namespace pdf { class PDFPageContentScene; +class PDFPageContentSvgElement; class PDFPageContentElementLine; class PDFPageContentElementRectangle; @@ -36,6 +37,8 @@ public: QObject* parent); protected: + static QRectF getRectangleFromPickTool(PDFPickTool* pickTool, const QMatrix& pagePointToDevicePointMatrix); + PDFPageContentScene* m_scene; }; @@ -69,6 +72,36 @@ private: PDFPageContentElementRectangle* m_element; }; +/// Tool that displays SVG image +class PDF4QTLIBSHARED_EXPORT PDFCreatePCElementSvgTool : public PDFCreatePCElementTool +{ + Q_OBJECT + +private: + using BaseClass = PDFCreatePCElementTool; + +public: + explicit PDFCreatePCElementSvgTool(PDFDrawWidgetProxy* proxy, + PDFPageContentScene* scene, + QAction* action, + QByteArray content, + QObject* parent); + virtual ~PDFCreatePCElementSvgTool() override; + + virtual void drawPage(QPainter* painter, + PDFInteger pageIndex, + const PDFPrecompiledPage* compiledPage, + PDFTextLayoutGetter& layoutGetter, + const QMatrix& pagePointToDevicePointMatrix, + QList& errors) const override; + +private: + void onRectanglePicked(pdf::PDFInteger pageIndex, QRectF pageRectangle); + + PDFPickTool* m_pickTool; + PDFPageContentSvgElement* m_element; +}; + /// Tool that creates line element. class PDF4QTLIBSHARED_EXPORT PDFCreatePCElementLineTool : public PDFCreatePCElementTool { diff --git a/Pdf4QtLib/sources/pdfpagecontentelements.cpp b/Pdf4QtLib/sources/pdfpagecontentelements.cpp index 49f33d8..08665aa 100644 --- a/Pdf4QtLib/sources/pdfpagecontentelements.cpp +++ b/Pdf4QtLib/sources/pdfpagecontentelements.cpp @@ -21,6 +21,7 @@ #include #include #include +#include namespace pdf { @@ -293,4 +294,88 @@ void PDFPageContentElementLine::setLine(const QLineF& newLine) } } +PDFPageContentSvgElement::PDFPageContentSvgElement() : + m_renderer(std::make_unique()) +{ + +} + +PDFPageContentSvgElement::~PDFPageContentSvgElement() +{ + +} + +PDFPageContentSvgElement* PDFPageContentSvgElement::clone() const +{ + PDFPageContentSvgElement* copy = new PDFPageContentSvgElement(); + copy->setPageIndex(getPageIndex()); + copy->setRectangle(getRectangle()); + copy->setContent(getContent()); + return copy; +} + +void PDFPageContentSvgElement::drawPage(QPainter* painter, + PDFInteger pageIndex, + const PDFPrecompiledPage* compiledPage, + PDFTextLayoutGetter& layoutGetter, + const QMatrix& pagePointToDevicePointMatrix, + QList& errors) const +{ + Q_UNUSED(compiledPage); + Q_UNUSED(layoutGetter); + Q_UNUSED(errors); + + if (pageIndex != getPageIndex() || !getRectangle().isValid()) + { + return; + } + + + PDFPainterStateGuard guard(painter); + painter->setWorldMatrix(pagePointToDevicePointMatrix, true); + painter->setRenderHint(QPainter::Antialiasing); + + QRectF viewBox = m_renderer->viewBoxF(); + if (!viewBox.isValid()) + { + return; + } + + QRectF renderBox = getRectangle(); + QSizeF viewBoxSize = viewBox.size(); + QSizeF renderBoxSize = viewBoxSize.scaled(renderBox.size(), Qt::KeepAspectRatio); + QRectF targetRenderBox = QRectF(QPointF(), renderBoxSize); + targetRenderBox.moveCenter(renderBox.center()); + + painter->translate(targetRenderBox.bottomLeft()); + painter->scale(1.0, -1.0); + targetRenderBox.moveTopLeft(QPointF(0, 0)); + + m_renderer->render(painter, targetRenderBox); +} + +const QByteArray& PDFPageContentSvgElement::getContent() const +{ + return m_content; +} + +void PDFPageContentSvgElement::setContent(const QByteArray& newContent) +{ + if (m_content != newContent) + { + m_content = newContent; + m_renderer->load(m_content); + } +} + +const QRectF& PDFPageContentSvgElement::getRectangle() const +{ + return m_rectangle; +} + +void PDFPageContentSvgElement::setRectangle(const QRectF& newRectangle) +{ + m_rectangle = newRectangle; +} + } // namespace pdf diff --git a/Pdf4QtLib/sources/pdfpagecontentelements.h b/Pdf4QtLib/sources/pdfpagecontentelements.h index 419cdae..54cfbeb 100644 --- a/Pdf4QtLib/sources/pdfpagecontentelements.h +++ b/Pdf4QtLib/sources/pdfpagecontentelements.h @@ -24,10 +24,12 @@ #include #include +class QSvgRenderer; + namespace pdf { -class PDFPageContentElement +class PDF4QTLIBSHARED_EXPORT PDFPageContentElement { public: explicit PDFPageContentElement() = default; @@ -49,7 +51,7 @@ protected: PDFInteger m_pageIndex = -1; }; -class PDFPageContentStyledElement : public PDFPageContentElement +class PDF4QTLIBSHARED_EXPORT PDFPageContentStyledElement : public PDFPageContentElement { public: explicit PDFPageContentStyledElement() = default; @@ -66,7 +68,7 @@ protected: QBrush m_brush; }; -class PDFPageContentElementRectangle : public PDFPageContentStyledElement +class PDF4QTLIBSHARED_EXPORT PDFPageContentElementRectangle : public PDFPageContentStyledElement { public: virtual ~PDFPageContentElementRectangle() = default; @@ -91,7 +93,7 @@ private: QRectF m_rectangle; }; -class PDFPageContentElementLine : public PDFPageContentStyledElement +class PDF4QTLIBSHARED_EXPORT PDFPageContentElementLine : public PDFPageContentStyledElement { public: virtual ~PDFPageContentElementLine() = default; @@ -123,6 +125,33 @@ private: QLineF m_line; }; +class PDF4QTLIBSHARED_EXPORT PDFPageContentSvgElement : public PDFPageContentElement +{ +public: + PDFPageContentSvgElement(); + virtual ~PDFPageContentSvgElement(); + + virtual PDFPageContentSvgElement* clone() const override; + + virtual void drawPage(QPainter* painter, + PDFInteger pageIndex, + const PDFPrecompiledPage* compiledPage, + PDFTextLayoutGetter& layoutGetter, + const QMatrix& pagePointToDevicePointMatrix, + QList& errors) const override; + + const QByteArray& getContent() const; + void setContent(const QByteArray& newContent); + + const QRectF& getRectangle() const; + void setRectangle(const QRectF& newRectangle); + +private: + QRectF m_rectangle; + QByteArray m_content; + std::unique_ptr m_renderer; +}; + class PDF4QTLIBSHARED_EXPORT PDFPageContentScene : public QObject, public IDocumentDrawInterface, public IDrawWidgetInputInterface diff --git a/Pdf4QtViewerPlugins/SignaturePlugin/accept-mark.svg b/Pdf4QtViewerPlugins/SignaturePlugin/accept-mark.svg new file mode 100644 index 0000000..d577d96 --- /dev/null +++ b/Pdf4QtViewerPlugins/SignaturePlugin/accept-mark.svg @@ -0,0 +1,13 @@ + + + + + + + + + diff --git a/Pdf4QtViewerPlugins/SignaturePlugin/icons.qrc b/Pdf4QtViewerPlugins/SignaturePlugin/icons.qrc index c133fe1..bed4028 100644 --- a/Pdf4QtViewerPlugins/SignaturePlugin/icons.qrc +++ b/Pdf4QtViewerPlugins/SignaturePlugin/icons.qrc @@ -1,4 +1,6 @@ + accept-mark.svg + reject-mark.svg diff --git a/Pdf4QtViewerPlugins/SignaturePlugin/reject-mark.svg b/Pdf4QtViewerPlugins/SignaturePlugin/reject-mark.svg new file mode 100644 index 0000000..017bbf4 --- /dev/null +++ b/Pdf4QtViewerPlugins/SignaturePlugin/reject-mark.svg @@ -0,0 +1,16 @@ + + + + + + + + + + + diff --git a/Pdf4QtViewerPlugins/SignaturePlugin/signatureplugin.cpp b/Pdf4QtViewerPlugins/SignaturePlugin/signatureplugin.cpp index 6e07350..c8ee796 100644 --- a/Pdf4QtViewerPlugins/SignaturePlugin/signatureplugin.cpp +++ b/Pdf4QtViewerPlugins/SignaturePlugin/signatureplugin.cpp @@ -103,6 +103,24 @@ void SignaturePlugin::setWidget(pdf::PDFWidget* widget) m_actions[SignDigitally] = signDigitallyAction; m_actions[Ceritificates] = certificatesAction; + QFile acceptMarkFile(":/pdfplugins/signatureplugin/accept-mark.svg"); + QByteArray acceptMarkContent; + if (acceptMarkFile.open(QFile::ReadOnly)) + { + acceptMarkContent = acceptMarkFile.readAll(); + acceptMarkFile.close(); + } + + QFile rejectMarkFile(":/pdfplugins/signatureplugin/reject-mark.svg"); + QByteArray rejectMarkContent; + if (rejectMarkFile.open(QFile::ReadOnly)) + { + rejectMarkContent = rejectMarkFile.readAll(); + rejectMarkFile.close(); + } + + m_tools[AcceptMarkTool] = new pdf::PDFCreatePCElementSvgTool(widget->getDrawWidgetProxy(), &m_scene, createAcceptMarkAction, acceptMarkContent, this); + m_tools[RejectMarkTool] = new pdf::PDFCreatePCElementSvgTool(widget->getDrawWidgetProxy(), &m_scene, createRejectMarkAction, rejectMarkContent, this); m_tools[RectangleTool] = new pdf::PDFCreatePCElementRectangleTool(widget->getDrawWidgetProxy(), &m_scene, createRectangleAction, false, this); m_tools[RoundedRectangleTool] = new pdf::PDFCreatePCElementRectangleTool(widget->getDrawWidgetProxy(), &m_scene, createRoundedRectangleAction, true, this); m_tools[HorizontalLineTool] = new pdf::PDFCreatePCElementLineTool(widget->getDrawWidgetProxy(), &m_scene, createHorizontalLineAction, true, false, this); diff --git a/Pdf4QtViewerPlugins/SignaturePlugin/signatureplugin.h b/Pdf4QtViewerPlugins/SignaturePlugin/signatureplugin.h index 4a89083..d7e52c7 100644 --- a/Pdf4QtViewerPlugins/SignaturePlugin/signatureplugin.h +++ b/Pdf4QtViewerPlugins/SignaturePlugin/signatureplugin.h @@ -74,6 +74,8 @@ private: enum Tools { + AcceptMarkTool, + RejectMarkTool, RectangleTool, RoundedRectangleTool, HorizontalLineTool, From 0f4d8d41d77c6264cdeb22d43af4521ff0a8fd05 Mon Sep 17 00:00:00 2001 From: Jakub Melka Date: Thu, 24 Feb 2022 20:25:18 +0100 Subject: [PATCH 04/39] Signature plugin: small refactoring --- Pdf4QtLib/Pdf4QtLib.pro | 3 + .../sources/pdfpagecontenteditorwidget.cpp | 36 ++++ .../sources/pdfpagecontenteditorwidget.h | 47 +++++ .../sources/pdfpagecontenteditorwidget.ui | 183 ++++++++++++++++++ Pdf4QtLib/sources/pdfpagecontentelements.cpp | 65 ++++++- Pdf4QtLib/sources/pdfpagecontentelements.h | 26 +++ .../SignaturePlugin/signatureplugin.cpp | 104 ++++++++-- .../SignaturePlugin/signatureplugin.h | 14 +- 8 files changed, 453 insertions(+), 25 deletions(-) create mode 100644 Pdf4QtLib/sources/pdfpagecontenteditorwidget.cpp create mode 100644 Pdf4QtLib/sources/pdfpagecontenteditorwidget.h create mode 100644 Pdf4QtLib/sources/pdfpagecontenteditorwidget.ui diff --git a/Pdf4QtLib/Pdf4QtLib.pro b/Pdf4QtLib/Pdf4QtLib.pro index 442ce9a..7963ea1 100644 --- a/Pdf4QtLib/Pdf4QtLib.pro +++ b/Pdf4QtLib/Pdf4QtLib.pro @@ -72,6 +72,7 @@ SOURCES += \ sources/pdfoptionalcontent.cpp \ sources/pdfoutline.cpp \ sources/pdfpagecontenteditortools.cpp \ + sources/pdfpagecontenteditorwidget.cpp \ sources/pdfpagecontentelements.cpp \ sources/pdfpagenavigation.cpp \ sources/pdfpagetransition.cpp \ @@ -149,6 +150,7 @@ HEADERS += \ sources/pdfoptionalcontent.h \ sources/pdfoutline.h \ sources/pdfpagecontenteditortools.h \ + sources/pdfpagecontenteditorwidget.h \ sources/pdfpagecontentelements.h \ sources/pdfpagenavigation.h \ sources/pdfpagetransition.h \ @@ -197,6 +199,7 @@ HEADERS += \ sources/pdfimage.h FORMS += \ + sources/pdfpagecontenteditorwidget.ui \ sources/pdfrenderingerrorswidget.ui \ sources/pdfselectpagesdialog.ui diff --git a/Pdf4QtLib/sources/pdfpagecontenteditorwidget.cpp b/Pdf4QtLib/sources/pdfpagecontenteditorwidget.cpp new file mode 100644 index 0000000..ca9f58b --- /dev/null +++ b/Pdf4QtLib/sources/pdfpagecontenteditorwidget.cpp @@ -0,0 +1,36 @@ +// Copyright (C) 2022 Jakub Melka +// +// This file is part of PDF4QT. +// +// PDF4QT 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 +// with the written consent of the copyright owner, any later version. +// +// PDF4QT 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 PDF4QT. If not, see . + +#include "pdfpagecontenteditorwidget.h" +#include "ui_pdfpagecontenteditorwidget.h" + +namespace pdf +{ + +PDFPageContentEditorWidget::PDFPageContentEditorWidget(QWidget *parent) : + QDockWidget(parent), + ui(new Ui::PDFPageContentEditorWidget) +{ + ui->setupUi(this); +} + +PDFPageContentEditorWidget::~PDFPageContentEditorWidget() +{ + delete ui; +} + +} // namespace pdf diff --git a/Pdf4QtLib/sources/pdfpagecontenteditorwidget.h b/Pdf4QtLib/sources/pdfpagecontenteditorwidget.h new file mode 100644 index 0000000..392930d --- /dev/null +++ b/Pdf4QtLib/sources/pdfpagecontenteditorwidget.h @@ -0,0 +1,47 @@ +// Copyright (C) 2022 Jakub Melka +// +// This file is part of PDF4QT. +// +// PDF4QT 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 +// with the written consent of the copyright owner, any later version. +// +// PDF4QT 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 PDF4QT. If not, see . + +#ifndef PDFPAGECONTENTEDITORWIDGET_H +#define PDFPAGECONTENTEDITORWIDGET_H + +#include "pdfglobal.h" + +#include + +namespace Ui +{ +class PDFPageContentEditorWidget; +} + +namespace pdf +{ + +class PDF4QTLIBSHARED_EXPORT PDFPageContentEditorWidget : public QDockWidget +{ + Q_OBJECT + +public: + explicit PDFPageContentEditorWidget(QWidget* parent); + virtual ~PDFPageContentEditorWidget() override; + +private: + Ui::PDFPageContentEditorWidget* ui; +}; + +} // namespace pdf + +#endif // PDFPAGECONTENTEDITORWIDGET_H diff --git a/Pdf4QtLib/sources/pdfpagecontenteditorwidget.ui b/Pdf4QtLib/sources/pdfpagecontenteditorwidget.ui new file mode 100644 index 0000000..2501476 --- /dev/null +++ b/Pdf4QtLib/sources/pdfpagecontenteditorwidget.ui @@ -0,0 +1,183 @@ + + + PDFPageContentEditorWidget + + + + 0 + 0 + 333 + 607 + + + + Qt::LeftDockWidgetArea|Qt::RightDockWidgetArea + + + Content editor + + + + + + + Toolbox + + + + + + + + Geometry Tools + + + + + + ... + + + + + + + ... + + + + + + + ... + + + + + + + ... + + + + + + + ... + + + + + + + ... + + + + + + + ... + + + + + + + ... + + + + + + + ... + + + + + + + ... + + + + + + + ... + + + + + + + ... + + + + + + + + + + Layout Tools + + + + + + ... + + + + + + + ... + + + + + + + ... + + + + + + + ... + + + + + + + + + + Appearance + + + + + + + Content + + + + + + + + + + + + + + diff --git a/Pdf4QtLib/sources/pdfpagecontentelements.cpp b/Pdf4QtLib/sources/pdfpagecontentelements.cpp index 08665aa..c9b6a64 100644 --- a/Pdf4QtLib/sources/pdfpagecontentelements.cpp +++ b/Pdf4QtLib/sources/pdfpagecontentelements.cpp @@ -122,7 +122,8 @@ void PDFPageContentElementRectangle::drawPage(QPainter* painter, } PDFPageContentScene::PDFPageContentScene(QObject* parent) : - QObject(parent) + QObject(parent), + m_isActive(false) { } @@ -217,6 +218,11 @@ void PDFPageContentScene::drawPage(QPainter* painter, const QMatrix& pagePointToDevicePointMatrix, QList& errors) const { + if (!m_isActive) + { + return; + } + for (const auto& element : m_elements) { if (element->getPageIndex() != pageIndex) @@ -228,6 +234,20 @@ void PDFPageContentScene::drawPage(QPainter* painter, } } +bool PDFPageContentScene::isActive() const +{ + return m_isActive; +} + +void PDFPageContentScene::setActive(bool newIsActive) +{ + if (m_isActive != newIsActive) + { + m_isActive = newIsActive; + emit sceneChanged(); + } +} + PDFPageContentElementLine* PDFPageContentElementLine::clone() const { PDFPageContentElementLine* copy = new PDFPageContentElementLine(); @@ -330,7 +350,6 @@ void PDFPageContentSvgElement::drawPage(QPainter* painter, return; } - PDFPainterStateGuard guard(painter); painter->setWorldMatrix(pagePointToDevicePointMatrix, true); painter->setRenderHint(QPainter::Antialiasing); @@ -378,4 +397,46 @@ void PDFPageContentSvgElement::setRectangle(const QRectF& newRectangle) m_rectangle = newRectangle; } +PDFPageContentElementDot* PDFPageContentElementDot::clone() const +{ + PDFPageContentElementDot* copy = new PDFPageContentElementDot(); + copy->setPageIndex(getPageIndex()); + copy->setPen(getPen()); + copy->setBrush(getBrush()); + copy->setPoint(getPoint()); + return copy; +} + +void PDFPageContentElementDot::drawPage(QPainter* painter, + PDFInteger pageIndex, + const PDFPrecompiledPage* compiledPage, + PDFTextLayoutGetter& layoutGetter, + const QMatrix& pagePointToDevicePointMatrix, + QList& errors) const +{ + Q_UNUSED(compiledPage); + Q_UNUSED(layoutGetter); + Q_UNUSED(errors); + + if (pageIndex != getPageIndex()) + { + return; + } + + PDFPainterStateGuard guard(painter); + painter->setWorldMatrix(pagePointToDevicePointMatrix, true); + painter->setRenderHint(QPainter::Antialiasing); + painter->drawPoint(m_point); +} + +QPointF PDFPageContentElementDot::getPoint() const +{ + return m_point; +} + +void PDFPageContentElementDot::setPoint(QPointF newPoint) +{ + m_point = newPoint; +} + } // namespace pdf diff --git a/Pdf4QtLib/sources/pdfpagecontentelements.h b/Pdf4QtLib/sources/pdfpagecontentelements.h index 54cfbeb..ea8ce90 100644 --- a/Pdf4QtLib/sources/pdfpagecontentelements.h +++ b/Pdf4QtLib/sources/pdfpagecontentelements.h @@ -125,6 +125,28 @@ private: QLineF m_line; }; +class PDF4QTLIBSHARED_EXPORT PDFPageContentElementDot : public PDFPageContentStyledElement +{ +public: + virtual ~PDFPageContentElementDot() = default; + + virtual PDFPageContentElementDot* clone() const override; + + virtual void drawPage(QPainter* painter, + PDFInteger pageIndex, + const PDFPrecompiledPage* compiledPage, + PDFTextLayoutGetter& layoutGetter, + const QMatrix& pagePointToDevicePointMatrix, + QList& errors) const override; + + + QPointF getPoint() const; + void setPoint(QPointF newPoint); + +private: + QPointF m_point; +}; + class PDF4QTLIBSHARED_EXPORT PDFPageContentSvgElement : public PDFPageContentElement { public: @@ -196,11 +218,15 @@ public: const QMatrix& pagePointToDevicePointMatrix, QList& errors) const override; + bool isActive() const; + void setActive(bool newIsActive); + signals: /// This signal is emitted when scene has changed (including graphics) void sceneChanged(); private: + bool m_isActive; std::vector> m_elements; std::optional m_cursor; }; diff --git a/Pdf4QtViewerPlugins/SignaturePlugin/signatureplugin.cpp b/Pdf4QtViewerPlugins/SignaturePlugin/signatureplugin.cpp index c8ee796..84ad0fc 100644 --- a/Pdf4QtViewerPlugins/SignaturePlugin/signatureplugin.cpp +++ b/Pdf4QtViewerPlugins/SignaturePlugin/signatureplugin.cpp @@ -38,7 +38,9 @@ void SignaturePlugin::setWidget(pdf::PDFWidget* widget) BaseClass::setWidget(widget); + QAction* activateAction = new QAction(QIcon(":/pdfplugins/signaturetool/activate.svg"), tr("Activate signature creator"), this); QAction* createTextAction = new QAction(QIcon(":/pdfplugins/signaturetool/create-text.svg"), tr("Create Text Label"), this); + QAction* createFreehandCurveAction = new QAction(QIcon(":/pdfplugins/signaturetool/create-freehand-curve.svg"), tr("Create Freehand Curve"), this); QAction* createAcceptMarkAction = new QAction(QIcon(":/pdfplugins/signaturetool/create-yes-mark.svg"), tr("Create Accept Mark"), this); QAction* createRejectMarkAction = new QAction(QIcon(":/pdfplugins/signaturetool/create-no-mark.svg"), tr("Create Reject Mark"), this); QAction* createRectangleAction = new QAction(QIcon(":/pdfplugins/signaturetool/create-rectangle.svg"), tr("Create Rectangle"), this); @@ -49,14 +51,13 @@ void SignaturePlugin::setWidget(pdf::PDFWidget* widget) QAction* createDotAction = new QAction(QIcon(":/pdfplugins/signaturetool/create-dot.svg"), tr("Create Dot"), this); QAction* createSvgImageAction = new QAction(QIcon(":/pdfplugins/signaturetool/create-svg-image.svg"), tr("Create SVG Image"), this); QAction* clearAction = new QAction(QIcon(":/pdfplugins/signaturetool/clear.svg"), tr("Clear All Graphics"), this); - QAction* setColorAction = new QAction(QIcon(":/pdfplugins/signaturetool/set-color.svg"), tr("Set Color"), this); - QAction* setPenAction = new QAction(QIcon(":/pdfplugins/signaturetool/set-pen.svg"), tr("Set Pen"), this); - QAction* setBrushAction = new QAction(QIcon(":/pdfplugins/signaturetool/set-brush.svg"), tr("Set Brush"), this); QAction* signElectronicallyAction = new QAction(QIcon(":/pdfplugins/signaturetool/sign-electronically.svg"), tr("Sign Electronically"), this); QAction* signDigitallyAction = new QAction(QIcon(":/pdfplugins/signaturetool/sign-digitally.svg"), tr("Sign Digitally With Certificate"), this); QAction* certificatesAction = new QAction(QIcon(":/pdfplugins/signaturetool/certificates.svg"), tr("Certificates Manager"), this); + activateAction->setObjectName("signaturetool_activateAction"); createTextAction->setObjectName("signaturetool_createTextAction"); + createFreehandCurveAction->setObjectName("signaturetool_createFreehandCurveAction"); createAcceptMarkAction->setObjectName("signaturetool_createAcceptMarkAction"); createRejectMarkAction->setObjectName("signaturetool_createRejectMarkAction"); createRectangleAction->setObjectName("signaturetool_createRectangleAction"); @@ -67,14 +68,13 @@ void SignaturePlugin::setWidget(pdf::PDFWidget* widget) createDotAction->setObjectName("signaturetool_createDotAction"); createSvgImageAction->setObjectName("signaturetool_createSvgImageAction"); clearAction->setObjectName("signaturetool_clearAction"); - setColorAction->setObjectName("signaturetool_setColorAction"); - setPenAction->setObjectName("signaturetool_setPenAction"); - setBrushAction->setObjectName("signaturetool_setBrushAction"); signElectronicallyAction->setObjectName("signaturetool_signElectronicallyAction"); signDigitallyAction->setObjectName("signaturetool_signDigitallyAction"); certificatesAction->setObjectName("signaturetool_certificatesAction"); + activateAction->setCheckable(true); createTextAction->setCheckable(true); + createFreehandCurveAction->setCheckable(true); createAcceptMarkAction->setCheckable(true); createRejectMarkAction->setCheckable(true); createRectangleAction->setCheckable(true); @@ -85,7 +85,9 @@ void SignaturePlugin::setWidget(pdf::PDFWidget* widget) createDotAction->setCheckable(true); createSvgImageAction->setCheckable(true); + m_actions[Activate] = activateAction; m_actions[Text] = createTextAction; + m_actions[FreehandCurve] = createFreehandCurveAction; m_actions[AcceptMark] = createAcceptMarkAction; m_actions[RejectMark] = createRejectMarkAction; m_actions[Rectangle] = createRectangleAction; @@ -96,12 +98,9 @@ void SignaturePlugin::setWidget(pdf::PDFWidget* widget) m_actions[Dot] = createDotAction; m_actions[SvgImage] = createSvgImageAction; m_actions[Clear] = clearAction; - m_actions[SetColor] = setColorAction; - m_actions[SetPen] = setPenAction; - m_actions[SetBrush] = setBrushAction; m_actions[SignElectronically] = signElectronicallyAction; m_actions[SignDigitally] = signDigitallyAction; - m_actions[Ceritificates] = certificatesAction; + m_actions[Certificates] = certificatesAction; QFile acceptMarkFile(":/pdfplugins/signatureplugin/accept-mark.svg"); QByteArray acceptMarkContent; @@ -134,6 +133,9 @@ void SignaturePlugin::setWidget(pdf::PDFWidget* widget) } m_widget->getDrawWidgetProxy()->registerDrawInterface(&m_scene); + connect(&m_scene, &pdf::PDFPageContentScene::sceneChanged, this, &SignaturePlugin::onSceneChanged); + connect(clearAction, &QAction::triggered, &m_scene, &pdf::PDFPageContentScene::clear); + connect(activateAction, &QAction::triggered, this, &SignaturePlugin::setActive); updateActions(); } @@ -144,7 +146,7 @@ void SignaturePlugin::setDocument(const pdf::PDFModifiedDocument& document) if (document.hasReset()) { - m_scene.clear(); + setActive(false); updateActions(); } } @@ -153,7 +155,9 @@ std::vector SignaturePlugin::getActions() const { std::vector result; + result.push_back(m_actions[Activate]); result.push_back(m_actions[Text]); + result.push_back(m_actions[FreehandCurve]); result.push_back(m_actions[AcceptMark]); result.push_back(m_actions[RejectMark]); result.push_back(m_actions[Rectangle]); @@ -164,21 +168,87 @@ std::vector SignaturePlugin::getActions() const result.push_back(m_actions[Dot]); result.push_back(m_actions[Clear]); result.push_back(nullptr); - result.push_back(m_actions[SetColor]); - result.push_back(m_actions[SetPen]); - result.push_back(m_actions[SetBrush]); - result.push_back(nullptr); result.push_back(m_actions[SignElectronically]); result.push_back(m_actions[SignDigitally]); - result.push_back(m_actions[Ceritificates]); + result.push_back(m_actions[Certificates]); return result; } +void SignaturePlugin::onSceneChanged() +{ + updateActions(); + updateGraphics(); +} + +void SignaturePlugin::setActive(bool active) +{ + if (m_scene.isActive() != active) + { + // Abort active tool, if we are deactivating the plugin + if (!active) + { + if (pdf::PDFWidgetTool* tool = m_widget->getToolManager()->getActiveTool()) + { + auto it = std::find(m_tools.cbegin(), m_tools.cend(), tool); + if (it == m_tools.cend()) + { + m_widget->getToolManager()->setActiveTool(nullptr); + } + } + } + + m_scene.setActive(active); + if (!active) + { + m_scene.clear(); + } + + m_actions[Activate]->setChecked(active); + updateActions(); + } +} + void SignaturePlugin::updateActions() { + m_actions[Activate]->setEnabled(m_document); + + if (!m_scene.isActive() || !m_document) + { + // Inactive scene - disable all except activate action and certificates + for (QAction* action : m_actions) + { + if (action == m_actions[Activate] || + action == m_actions[Certificates]) + { + continue; + } + + action->setEnabled(false); + } + + return; + } + + const bool isSceneNonempty = !m_scene.isEmpty(); + + // Tool actions + for (auto actionId : { Text, FreehandCurve, AcceptMark, RejectMark, + Rectangle, RoundedRectangle, HorizontalLine, + VerticalLine, Line, Dot, SvgImage }) + { + m_actions[actionId]->setEnabled(true); + } + + // Clear action QAction* clearAction = m_actions[Clear]; - clearAction->setEnabled(!m_scene.isEmpty()); + clearAction->setEnabled(isSceneNonempty); + + // Sign actions + QAction* signElectronicallyAction = m_actions[SignElectronically]; + signElectronicallyAction->setEnabled(isSceneNonempty); + QAction* signDigitallyAction = m_actions[SignDigitally]; + signDigitallyAction->setEnabled(isSceneNonempty); } void SignaturePlugin::updateGraphics() diff --git a/Pdf4QtViewerPlugins/SignaturePlugin/signatureplugin.h b/Pdf4QtViewerPlugins/SignaturePlugin/signatureplugin.h index d7e52c7..1493d3b 100644 --- a/Pdf4QtViewerPlugins/SignaturePlugin/signatureplugin.h +++ b/Pdf4QtViewerPlugins/SignaturePlugin/signatureplugin.h @@ -43,11 +43,16 @@ public: virtual std::vector getActions() const override; private: + void onSceneChanged(); enum Action { + // Activate action + Activate, + // Create graphics actions Text, + FreehandCurve, AcceptMark, RejectMark, Rectangle, @@ -59,15 +64,10 @@ private: SvgImage, Clear, - // Settings actions - SetColor, - SetPen, - SetBrush, - // Sign actions SignElectronically, SignDigitally, - Ceritificates, + Certificates, LastAction }; @@ -84,6 +84,8 @@ private: LastTool }; + void setActive(bool active); + void updateActions(); void updateGraphics(); From c642722faf6891bf838668f4db6637634ba8576a Mon Sep 17 00:00:00 2001 From: Jakub Melka Date: Sat, 26 Feb 2022 19:46:13 +0100 Subject: [PATCH 05/39] Signature plugin: icons and toolbox --- .../sources/pdfpagecontenteditorwidget.cpp | 65 ++++++++++++++++++- .../sources/pdfpagecontenteditorwidget.h | 9 +++ .../sources/pdfpagecontenteditorwidget.ui | 6 +- .../SignaturePlugin/activate.svg | 59 +++++++++++++++++ .../SignaturePlugin/certificates.svg | 59 +++++++++++++++++ Pdf4QtViewerPlugins/SignaturePlugin/clear.svg | 59 +++++++++++++++++ .../SignaturePlugin/create-dot.svg | 59 +++++++++++++++++ .../SignaturePlugin/create-freehand-curve.svg | 59 +++++++++++++++++ .../create-horizontal-line.svg | 59 +++++++++++++++++ .../SignaturePlugin/create-line.svg | 59 +++++++++++++++++ .../SignaturePlugin/create-no-mark.svg | 59 +++++++++++++++++ .../SignaturePlugin/create-rectangle.svg | 59 +++++++++++++++++ .../create-rounded-rectangle.svg | 59 +++++++++++++++++ .../SignaturePlugin/create-svg-image.svg | 59 +++++++++++++++++ .../SignaturePlugin/create-text.svg | 59 +++++++++++++++++ .../SignaturePlugin/create-vertical-line.svg | 59 +++++++++++++++++ .../SignaturePlugin/create-yes-mark.svg | 59 +++++++++++++++++ Pdf4QtViewerPlugins/SignaturePlugin/icons.qrc | 20 +++++- .../SignaturePlugin/sign-digitally.svg | 59 +++++++++++++++++ .../SignaturePlugin/sign-electronically.svg | 59 +++++++++++++++++ .../SignaturePlugin/signatureplugin.cpp | 57 +++++++++++----- .../SignaturePlugin/signatureplugin.h | 7 ++ 22 files changed, 1086 insertions(+), 22 deletions(-) create mode 100644 Pdf4QtViewerPlugins/SignaturePlugin/activate.svg create mode 100644 Pdf4QtViewerPlugins/SignaturePlugin/certificates.svg create mode 100644 Pdf4QtViewerPlugins/SignaturePlugin/clear.svg create mode 100644 Pdf4QtViewerPlugins/SignaturePlugin/create-dot.svg create mode 100644 Pdf4QtViewerPlugins/SignaturePlugin/create-freehand-curve.svg create mode 100644 Pdf4QtViewerPlugins/SignaturePlugin/create-horizontal-line.svg create mode 100644 Pdf4QtViewerPlugins/SignaturePlugin/create-line.svg create mode 100644 Pdf4QtViewerPlugins/SignaturePlugin/create-no-mark.svg create mode 100644 Pdf4QtViewerPlugins/SignaturePlugin/create-rectangle.svg create mode 100644 Pdf4QtViewerPlugins/SignaturePlugin/create-rounded-rectangle.svg create mode 100644 Pdf4QtViewerPlugins/SignaturePlugin/create-svg-image.svg create mode 100644 Pdf4QtViewerPlugins/SignaturePlugin/create-text.svg create mode 100644 Pdf4QtViewerPlugins/SignaturePlugin/create-vertical-line.svg create mode 100644 Pdf4QtViewerPlugins/SignaturePlugin/create-yes-mark.svg create mode 100644 Pdf4QtViewerPlugins/SignaturePlugin/sign-digitally.svg create mode 100644 Pdf4QtViewerPlugins/SignaturePlugin/sign-electronically.svg diff --git a/Pdf4QtLib/sources/pdfpagecontenteditorwidget.cpp b/Pdf4QtLib/sources/pdfpagecontenteditorwidget.cpp index ca9f58b..ca5cbbe 100644 --- a/Pdf4QtLib/sources/pdfpagecontenteditorwidget.cpp +++ b/Pdf4QtLib/sources/pdfpagecontenteditorwidget.cpp @@ -18,14 +18,20 @@ #include "pdfpagecontenteditorwidget.h" #include "ui_pdfpagecontenteditorwidget.h" +#include +#include + namespace pdf { PDFPageContentEditorWidget::PDFPageContentEditorWidget(QWidget *parent) : QDockWidget(parent), - ui(new Ui::PDFPageContentEditorWidget) + ui(new Ui::PDFPageContentEditorWidget), + m_toolBoxColumnCount(6) { ui->setupUi(this); + + connect(&m_actionMapper, &QSignalMapper::mappedObject, this, &PDFPageContentEditorWidget::onActionTriggerRequest); } PDFPageContentEditorWidget::~PDFPageContentEditorWidget() @@ -33,4 +39,61 @@ PDFPageContentEditorWidget::~PDFPageContentEditorWidget() delete ui; } +void PDFPageContentEditorWidget::addAction(QAction* action) +{ + // First, find position for our action + int row = 0; + int column = 0; + + while (true) + { + if (!ui->toolGroupBoxLayout->itemAtPosition(row, column)) + { + break; + } + + ++column; + + if (column == m_toolBoxColumnCount) + { + column = 0; + ++row; + } + } + + QToolButton* button = new QToolButton(this); + button->setIcon(action->icon()); + button->setText(action->text()); + button->setToolTip(action->toolTip()); + button->setCheckable(action->isCheckable()); + button->setChecked(action->isChecked()); + button->setEnabled(action->isEnabled()); + button->setShortcut(action->shortcut()); + m_actionMapper.setMapping(button, action); + connect(button, &QToolButton::clicked, &m_actionMapper, QOverload<>::of(&QSignalMapper::map)); + connect(action, &QAction::changed, this, &PDFPageContentEditorWidget::onActionChanged); + + ui->toolGroupBoxLayout->addWidget(button, row, column, Qt::AlignCenter); +} + +void PDFPageContentEditorWidget::onActionTriggerRequest(QObject* actionObject) +{ + QAction* action = qobject_cast(actionObject); + Q_ASSERT(action); + + action->trigger(); +} + +void PDFPageContentEditorWidget::onActionChanged() +{ + QAction* action = qobject_cast(sender()); + QToolButton* button = qobject_cast(m_actionMapper.mapping(action)); + + Q_ASSERT(action); + Q_ASSERT(button); + + button->setChecked(action->isChecked()); + button->setEnabled(action->isEnabled()); +} + } // namespace pdf diff --git a/Pdf4QtLib/sources/pdfpagecontenteditorwidget.h b/Pdf4QtLib/sources/pdfpagecontenteditorwidget.h index 392930d..2e2d7b8 100644 --- a/Pdf4QtLib/sources/pdfpagecontenteditorwidget.h +++ b/Pdf4QtLib/sources/pdfpagecontenteditorwidget.h @@ -21,6 +21,7 @@ #include "pdfglobal.h" #include +#include namespace Ui { @@ -38,8 +39,16 @@ public: explicit PDFPageContentEditorWidget(QWidget* parent); virtual ~PDFPageContentEditorWidget() override; + /// Adds external action to the tool box + void addAction(QAction* action); + private: + void onActionTriggerRequest(QObject* actionObject); + void onActionChanged(); + Ui::PDFPageContentEditorWidget* ui; + QSignalMapper m_actionMapper; + int m_toolBoxColumnCount; }; } // namespace pdf diff --git a/Pdf4QtLib/sources/pdfpagecontenteditorwidget.ui b/Pdf4QtLib/sources/pdfpagecontenteditorwidget.ui index 2501476..b74af84 100644 --- a/Pdf4QtLib/sources/pdfpagecontenteditorwidget.ui +++ b/Pdf4QtLib/sources/pdfpagecontenteditorwidget.ui @@ -23,7 +23,7 @@ Toolbox - + @@ -31,7 +31,7 @@ Geometry Tools - + @@ -124,7 +124,7 @@ Layout Tools - + diff --git a/Pdf4QtViewerPlugins/SignaturePlugin/activate.svg b/Pdf4QtViewerPlugins/SignaturePlugin/activate.svg new file mode 100644 index 0000000..a4f2e18 --- /dev/null +++ b/Pdf4QtViewerPlugins/SignaturePlugin/activate.svg @@ -0,0 +1,59 @@ + + + +image/svg+xml + + + +? + \ No newline at end of file diff --git a/Pdf4QtViewerPlugins/SignaturePlugin/certificates.svg b/Pdf4QtViewerPlugins/SignaturePlugin/certificates.svg new file mode 100644 index 0000000..a4f2e18 --- /dev/null +++ b/Pdf4QtViewerPlugins/SignaturePlugin/certificates.svg @@ -0,0 +1,59 @@ + + + +image/svg+xml + + + +? + \ No newline at end of file diff --git a/Pdf4QtViewerPlugins/SignaturePlugin/clear.svg b/Pdf4QtViewerPlugins/SignaturePlugin/clear.svg new file mode 100644 index 0000000..a4f2e18 --- /dev/null +++ b/Pdf4QtViewerPlugins/SignaturePlugin/clear.svg @@ -0,0 +1,59 @@ + + + +image/svg+xml + + + +? + \ No newline at end of file diff --git a/Pdf4QtViewerPlugins/SignaturePlugin/create-dot.svg b/Pdf4QtViewerPlugins/SignaturePlugin/create-dot.svg new file mode 100644 index 0000000..a4f2e18 --- /dev/null +++ b/Pdf4QtViewerPlugins/SignaturePlugin/create-dot.svg @@ -0,0 +1,59 @@ + + + +image/svg+xml + + + +? + \ No newline at end of file diff --git a/Pdf4QtViewerPlugins/SignaturePlugin/create-freehand-curve.svg b/Pdf4QtViewerPlugins/SignaturePlugin/create-freehand-curve.svg new file mode 100644 index 0000000..a4f2e18 --- /dev/null +++ b/Pdf4QtViewerPlugins/SignaturePlugin/create-freehand-curve.svg @@ -0,0 +1,59 @@ + + + +image/svg+xml + + + +? + \ No newline at end of file diff --git a/Pdf4QtViewerPlugins/SignaturePlugin/create-horizontal-line.svg b/Pdf4QtViewerPlugins/SignaturePlugin/create-horizontal-line.svg new file mode 100644 index 0000000..a4f2e18 --- /dev/null +++ b/Pdf4QtViewerPlugins/SignaturePlugin/create-horizontal-line.svg @@ -0,0 +1,59 @@ + + + +image/svg+xml + + + +? + \ No newline at end of file diff --git a/Pdf4QtViewerPlugins/SignaturePlugin/create-line.svg b/Pdf4QtViewerPlugins/SignaturePlugin/create-line.svg new file mode 100644 index 0000000..a4f2e18 --- /dev/null +++ b/Pdf4QtViewerPlugins/SignaturePlugin/create-line.svg @@ -0,0 +1,59 @@ + + + +image/svg+xml + + + +? + \ No newline at end of file diff --git a/Pdf4QtViewerPlugins/SignaturePlugin/create-no-mark.svg b/Pdf4QtViewerPlugins/SignaturePlugin/create-no-mark.svg new file mode 100644 index 0000000..a4f2e18 --- /dev/null +++ b/Pdf4QtViewerPlugins/SignaturePlugin/create-no-mark.svg @@ -0,0 +1,59 @@ + + + +image/svg+xml + + + +? + \ No newline at end of file diff --git a/Pdf4QtViewerPlugins/SignaturePlugin/create-rectangle.svg b/Pdf4QtViewerPlugins/SignaturePlugin/create-rectangle.svg new file mode 100644 index 0000000..a4f2e18 --- /dev/null +++ b/Pdf4QtViewerPlugins/SignaturePlugin/create-rectangle.svg @@ -0,0 +1,59 @@ + + + +image/svg+xml + + + +? + \ No newline at end of file diff --git a/Pdf4QtViewerPlugins/SignaturePlugin/create-rounded-rectangle.svg b/Pdf4QtViewerPlugins/SignaturePlugin/create-rounded-rectangle.svg new file mode 100644 index 0000000..a4f2e18 --- /dev/null +++ b/Pdf4QtViewerPlugins/SignaturePlugin/create-rounded-rectangle.svg @@ -0,0 +1,59 @@ + + + +image/svg+xml + + + +? + \ No newline at end of file diff --git a/Pdf4QtViewerPlugins/SignaturePlugin/create-svg-image.svg b/Pdf4QtViewerPlugins/SignaturePlugin/create-svg-image.svg new file mode 100644 index 0000000..a4f2e18 --- /dev/null +++ b/Pdf4QtViewerPlugins/SignaturePlugin/create-svg-image.svg @@ -0,0 +1,59 @@ + + + +image/svg+xml + + + +? + \ No newline at end of file diff --git a/Pdf4QtViewerPlugins/SignaturePlugin/create-text.svg b/Pdf4QtViewerPlugins/SignaturePlugin/create-text.svg new file mode 100644 index 0000000..a4f2e18 --- /dev/null +++ b/Pdf4QtViewerPlugins/SignaturePlugin/create-text.svg @@ -0,0 +1,59 @@ + + + +image/svg+xml + + + +? + \ No newline at end of file diff --git a/Pdf4QtViewerPlugins/SignaturePlugin/create-vertical-line.svg b/Pdf4QtViewerPlugins/SignaturePlugin/create-vertical-line.svg new file mode 100644 index 0000000..a4f2e18 --- /dev/null +++ b/Pdf4QtViewerPlugins/SignaturePlugin/create-vertical-line.svg @@ -0,0 +1,59 @@ + + + +image/svg+xml + + + +? + \ No newline at end of file diff --git a/Pdf4QtViewerPlugins/SignaturePlugin/create-yes-mark.svg b/Pdf4QtViewerPlugins/SignaturePlugin/create-yes-mark.svg new file mode 100644 index 0000000..a4f2e18 --- /dev/null +++ b/Pdf4QtViewerPlugins/SignaturePlugin/create-yes-mark.svg @@ -0,0 +1,59 @@ + + + +image/svg+xml + + + +? + \ No newline at end of file diff --git a/Pdf4QtViewerPlugins/SignaturePlugin/icons.qrc b/Pdf4QtViewerPlugins/SignaturePlugin/icons.qrc index bed4028..c53a915 100644 --- a/Pdf4QtViewerPlugins/SignaturePlugin/icons.qrc +++ b/Pdf4QtViewerPlugins/SignaturePlugin/icons.qrc @@ -1,6 +1,22 @@ - accept-mark.svg - reject-mark.svg + accept-mark.svg + reject-mark.svg + activate.svg + certificates.svg + clear.svg + create-dot.svg + create-freehand-curve.svg + create-horizontal-line.svg + create-line.svg + create-no-mark.svg + create-rectangle.svg + create-rounded-rectangle.svg + create-svg-image.svg + create-text.svg + create-vertical-line.svg + create-yes-mark.svg + sign-digitally.svg + sign-electronically.svg diff --git a/Pdf4QtViewerPlugins/SignaturePlugin/sign-digitally.svg b/Pdf4QtViewerPlugins/SignaturePlugin/sign-digitally.svg new file mode 100644 index 0000000..a4f2e18 --- /dev/null +++ b/Pdf4QtViewerPlugins/SignaturePlugin/sign-digitally.svg @@ -0,0 +1,59 @@ + + + +image/svg+xml + + + +? + \ No newline at end of file diff --git a/Pdf4QtViewerPlugins/SignaturePlugin/sign-electronically.svg b/Pdf4QtViewerPlugins/SignaturePlugin/sign-electronically.svg new file mode 100644 index 0000000..a4f2e18 --- /dev/null +++ b/Pdf4QtViewerPlugins/SignaturePlugin/sign-electronically.svg @@ -0,0 +1,59 @@ + + + +image/svg+xml + + + +? + \ No newline at end of file diff --git a/Pdf4QtViewerPlugins/SignaturePlugin/signatureplugin.cpp b/Pdf4QtViewerPlugins/SignaturePlugin/signatureplugin.cpp index 84ad0fc..da0cb29 100644 --- a/Pdf4QtViewerPlugins/SignaturePlugin/signatureplugin.cpp +++ b/Pdf4QtViewerPlugins/SignaturePlugin/signatureplugin.cpp @@ -17,8 +17,10 @@ #include "signatureplugin.h" #include "pdfdrawwidget.h" +#include "pdfpagecontenteditorwidget.h" #include +#include namespace pdfplugin { @@ -27,6 +29,7 @@ SignaturePlugin::SignaturePlugin() : pdf::PDFPlugin(nullptr), m_actions({ }), m_tools({ }), + m_editorWidget(nullptr), m_scene(nullptr) { @@ -38,22 +41,22 @@ void SignaturePlugin::setWidget(pdf::PDFWidget* widget) BaseClass::setWidget(widget); - QAction* activateAction = new QAction(QIcon(":/pdfplugins/signaturetool/activate.svg"), tr("Activate signature creator"), this); - QAction* createTextAction = new QAction(QIcon(":/pdfplugins/signaturetool/create-text.svg"), tr("Create Text Label"), this); - QAction* createFreehandCurveAction = new QAction(QIcon(":/pdfplugins/signaturetool/create-freehand-curve.svg"), tr("Create Freehand Curve"), this); - QAction* createAcceptMarkAction = new QAction(QIcon(":/pdfplugins/signaturetool/create-yes-mark.svg"), tr("Create Accept Mark"), this); - QAction* createRejectMarkAction = new QAction(QIcon(":/pdfplugins/signaturetool/create-no-mark.svg"), tr("Create Reject Mark"), this); - QAction* createRectangleAction = new QAction(QIcon(":/pdfplugins/signaturetool/create-rectangle.svg"), tr("Create Rectangle"), this); - QAction* createRoundedRectangleAction = new QAction(QIcon(":/pdfplugins/signaturetool/create-rounded-rectangle.svg"), tr("Create Rounded Rectangle"), this); - QAction* createHorizontalLineAction = new QAction(QIcon(":/pdfplugins/signaturetool/create-horizontal-line.svg"), tr("Create Horizontal Line"), this); - QAction* createVerticalLineAction = new QAction(QIcon(":/pdfplugins/signaturetool/create-vertical-line.svg"), tr("Create Vertical Line"), this); - QAction* createLineAction = new QAction(QIcon(":/pdfplugins/signaturetool/create-line.svg"), tr("Create Line"), this); - QAction* createDotAction = new QAction(QIcon(":/pdfplugins/signaturetool/create-dot.svg"), tr("Create Dot"), this); - QAction* createSvgImageAction = new QAction(QIcon(":/pdfplugins/signaturetool/create-svg-image.svg"), tr("Create SVG Image"), this); - QAction* clearAction = new QAction(QIcon(":/pdfplugins/signaturetool/clear.svg"), tr("Clear All Graphics"), this); - QAction* signElectronicallyAction = new QAction(QIcon(":/pdfplugins/signaturetool/sign-electronically.svg"), tr("Sign Electronically"), this); - QAction* signDigitallyAction = new QAction(QIcon(":/pdfplugins/signaturetool/sign-digitally.svg"), tr("Sign Digitally With Certificate"), this); - QAction* certificatesAction = new QAction(QIcon(":/pdfplugins/signaturetool/certificates.svg"), tr("Certificates Manager"), this); + QAction* activateAction = new QAction(QIcon(":/pdfplugins/signatureplugin/activate.svg"), tr("Activate signature creator"), this); + QAction* createTextAction = new QAction(QIcon(":/pdfplugins/signatureplugin/create-text.svg"), tr("Create Text Label"), this); + QAction* createFreehandCurveAction = new QAction(QIcon(":/pdfplugins/signatureplugin/create-freehand-curve.svg"), tr("Create Freehand Curve"), this); + QAction* createAcceptMarkAction = new QAction(QIcon(":/pdfplugins/signatureplugin/create-yes-mark.svg"), tr("Create Accept Mark"), this); + QAction* createRejectMarkAction = new QAction(QIcon(":/pdfplugins/signatureplugin/create-no-mark.svg"), tr("Create Reject Mark"), this); + QAction* createRectangleAction = new QAction(QIcon(":/pdfplugins/signatureplugin/create-rectangle.svg"), tr("Create Rectangle"), this); + QAction* createRoundedRectangleAction = new QAction(QIcon(":/pdfplugins/signatureplugin/create-rounded-rectangle.svg"), tr("Create Rounded Rectangle"), this); + QAction* createHorizontalLineAction = new QAction(QIcon(":/pdfplugins/signatureplugin/create-horizontal-line.svg"), tr("Create Horizontal Line"), this); + QAction* createVerticalLineAction = new QAction(QIcon(":/pdfplugins/signatureplugin/create-vertical-line.svg"), tr("Create Vertical Line"), this); + QAction* createLineAction = new QAction(QIcon(":/pdfplugins/signatureplugin/create-line.svg"), tr("Create Line"), this); + QAction* createDotAction = new QAction(QIcon(":/pdfplugins/signatureplugin/create-dot.svg"), tr("Create Dot"), this); + QAction* createSvgImageAction = new QAction(QIcon(":/pdfplugins/signatureplugin/create-svg-image.svg"), tr("Create SVG Image"), this); + QAction* clearAction = new QAction(QIcon(":/pdfplugins/signatureplugin/clear.svg"), tr("Clear All Graphics"), this); + QAction* signElectronicallyAction = new QAction(QIcon(":/pdfplugins/signatureplugin/sign-electronically.svg"), tr("Sign Electronically"), this); + QAction* signDigitallyAction = new QAction(QIcon(":/pdfplugins/signatureplugin/sign-digitally.svg"), tr("Sign Digitally With Certificate"), this); + QAction* certificatesAction = new QAction(QIcon(":/pdfplugins/signatureplugin/certificates.svg"), tr("Certificates Manager"), this); activateAction->setObjectName("signaturetool_activateAction"); createTextAction->setObjectName("signaturetool_createTextAction"); @@ -203,6 +206,10 @@ void SignaturePlugin::setActive(bool active) { m_scene.clear(); } + else + { + updateDockWidget(); + } m_actions[Activate]->setChecked(active); updateActions(); @@ -259,4 +266,22 @@ void SignaturePlugin::updateGraphics() } } +void SignaturePlugin::updateDockWidget() +{ + if (m_editorWidget) + { + return; + } + + m_editorWidget = new pdf::PDFPageContentEditorWidget(m_dataExchangeInterface->getMainWindow()); + m_editorWidget->setAllowedAreas(Qt::LeftDockWidgetArea | Qt::RightDockWidgetArea); + m_dataExchangeInterface->getMainWindow()->addDockWidget(Qt::RightDockWidgetArea, m_editorWidget, Qt::Vertical); + m_editorWidget->setFloating(false); + + for (QAction* action : m_actions) + { + m_editorWidget->addAction(action); + } +} + } diff --git a/Pdf4QtViewerPlugins/SignaturePlugin/signatureplugin.h b/Pdf4QtViewerPlugins/SignaturePlugin/signatureplugin.h index 1493d3b..d76a99b 100644 --- a/Pdf4QtViewerPlugins/SignaturePlugin/signatureplugin.h +++ b/Pdf4QtViewerPlugins/SignaturePlugin/signatureplugin.h @@ -24,6 +24,11 @@ #include +namespace pdf +{ +class PDFPageContentEditorWidget; +} + namespace pdfplugin { @@ -88,9 +93,11 @@ private: void updateActions(); void updateGraphics(); + void updateDockWidget(); std::array m_actions; std::array m_tools; + pdf::PDFPageContentEditorWidget* m_editorWidget; pdf::PDFPageContentScene m_scene; }; From 7d84f264765420b4616e4f2462ee738d4da5b1dc Mon Sep 17 00:00:00 2001 From: Jakub Melka Date: Sun, 27 Feb 2022 17:08:24 +0100 Subject: [PATCH 06/39] Signature plugin: dot plugin --- .../sources/pdfpagecontenteditortools.cpp | 59 +++++++++++++++++++ Pdf4QtLib/sources/pdfpagecontenteditortools.h | 30 ++++++++++ .../sources/pdfpagecontenteditorwidget.cpp | 9 +++ .../sources/pdfpagecontenteditorwidget.h | 1 + Pdf4QtLib/sources/pdfpagecontentelements.cpp | 2 + .../SignaturePlugin/signatureplugin.cpp | 14 +---- .../SignaturePlugin/signatureplugin.h | 1 + 7 files changed, 104 insertions(+), 12 deletions(-) diff --git a/Pdf4QtLib/sources/pdfpagecontenteditortools.cpp b/Pdf4QtLib/sources/pdfpagecontenteditortools.cpp index 067125c..25fded7 100644 --- a/Pdf4QtLib/sources/pdfpagecontenteditortools.cpp +++ b/Pdf4QtLib/sources/pdfpagecontenteditortools.cpp @@ -309,4 +309,63 @@ void PDFCreatePCElementSvgTool::onRectanglePicked(PDFInteger pageIndex, QRectF p setActive(false); } +PDFCreatePCElementDotTool::PDFCreatePCElementDotTool(PDFDrawWidgetProxy* proxy, + PDFPageContentScene* scene, + QAction* action, + QObject* parent) : + BaseClass(proxy, scene, action, parent), + m_pickTool(nullptr), + m_element(nullptr) +{ + m_pickTool = new PDFPickTool(proxy, PDFPickTool::Mode::Points, this); + m_pickTool->setDrawSelectionRectangle(false); + addTool(m_pickTool); + connect(m_pickTool, &PDFPickTool::pointPicked, this, &PDFCreatePCElementDotTool::onPointPicked); + + QPen pen(Qt::SolidLine); + pen.setWidthF(5.0); + pen.setCapStyle(Qt::RoundCap); + + m_element = new PDFPageContentElementDot(); + m_element->setBrush(Qt::NoBrush); + m_element->setPen(std::move(pen)); + + updateActions(); +} + +PDFCreatePCElementDotTool::~PDFCreatePCElementDotTool() +{ + delete m_element; +} + +void PDFCreatePCElementDotTool::drawPage(QPainter* painter, + PDFInteger pageIndex, + const PDFPrecompiledPage* compiledPage, + PDFTextLayoutGetter& layoutGetter, + const QMatrix& pagePointToDevicePointMatrix, + QList& errors) const +{ + BaseClass::drawPage(painter, pageIndex, compiledPage, layoutGetter, pagePointToDevicePointMatrix, errors); + + QPointF point = pagePointToDevicePointMatrix.inverted().map(m_pickTool->getSnappedPoint()); + + PDFPainterStateGuard guard(painter); + painter->setWorldMatrix(pagePointToDevicePointMatrix, true); + painter->setRenderHint(QPainter::Antialiasing); + painter->setPen(m_element->getPen()); + painter->setBrush(m_element->getBrush()); + painter->drawPoint(point); +} + +void PDFCreatePCElementDotTool::onPointPicked(PDFInteger pageIndex, QPointF pagePoint) +{ + m_element->setPageIndex(pageIndex); + m_element->setPoint(pagePoint); + + m_scene->addElement(m_element->clone()); + m_element->setPageIndex(-1); + + setActive(false); +} + } // namespace pdf diff --git a/Pdf4QtLib/sources/pdfpagecontenteditortools.h b/Pdf4QtLib/sources/pdfpagecontenteditortools.h index f0f7598..b03e9d0 100644 --- a/Pdf4QtLib/sources/pdfpagecontenteditortools.h +++ b/Pdf4QtLib/sources/pdfpagecontenteditortools.h @@ -25,6 +25,7 @@ namespace pdf class PDFPageContentScene; class PDFPageContentSvgElement; +class PDFPageContentElementDot; class PDFPageContentElementLine; class PDFPageContentElementRectangle; @@ -135,6 +136,35 @@ private: std::optional m_startPoint; }; +/// Tool that creates dot element. +class PDF4QTLIBSHARED_EXPORT PDFCreatePCElementDotTool : public PDFCreatePCElementTool +{ + Q_OBJECT + +private: + using BaseClass = PDFCreatePCElementTool; + +public: + explicit PDFCreatePCElementDotTool(PDFDrawWidgetProxy* proxy, + PDFPageContentScene* scene, + QAction* action, + QObject* parent); + virtual ~PDFCreatePCElementDotTool() override; + + virtual void drawPage(QPainter* painter, + PDFInteger pageIndex, + const PDFPrecompiledPage* compiledPage, + PDFTextLayoutGetter& layoutGetter, + const QMatrix& pagePointToDevicePointMatrix, + QList& errors) const override; + +private: + void onPointPicked(pdf::PDFInteger pageIndex, QPointF pagePoint); + + PDFPickTool* m_pickTool; + PDFPageContentElementDot* m_element; +}; + } // namespace pdf #endif // PDFPAGECONTENTEDITORTOOLS_H diff --git a/Pdf4QtLib/sources/pdfpagecontenteditorwidget.cpp b/Pdf4QtLib/sources/pdfpagecontenteditorwidget.cpp index ca5cbbe..f46a1ff 100644 --- a/Pdf4QtLib/sources/pdfpagecontenteditorwidget.cpp +++ b/Pdf4QtLib/sources/pdfpagecontenteditorwidget.cpp @@ -17,6 +17,7 @@ #include "pdfpagecontenteditorwidget.h" #include "ui_pdfpagecontenteditorwidget.h" +#include "pdfwidgetutils.h" #include #include @@ -31,6 +32,13 @@ PDFPageContentEditorWidget::PDFPageContentEditorWidget(QWidget *parent) : { ui->setupUi(this); + m_toolButtonIconSize = PDFWidgetUtils::scaleDPI(this, QSize(32, 32)); + + for (QToolButton* button : findChildren()) + { + button->setIconSize(m_toolButtonIconSize); + } + connect(&m_actionMapper, &QSignalMapper::mappedObject, this, &PDFPageContentEditorWidget::onActionTriggerRequest); } @@ -69,6 +77,7 @@ void PDFPageContentEditorWidget::addAction(QAction* action) button->setChecked(action->isChecked()); button->setEnabled(action->isEnabled()); button->setShortcut(action->shortcut()); + button->setIconSize(m_toolButtonIconSize); m_actionMapper.setMapping(button, action); connect(button, &QToolButton::clicked, &m_actionMapper, QOverload<>::of(&QSignalMapper::map)); connect(action, &QAction::changed, this, &PDFPageContentEditorWidget::onActionChanged); diff --git a/Pdf4QtLib/sources/pdfpagecontenteditorwidget.h b/Pdf4QtLib/sources/pdfpagecontenteditorwidget.h index 2e2d7b8..b2dbbd6 100644 --- a/Pdf4QtLib/sources/pdfpagecontenteditorwidget.h +++ b/Pdf4QtLib/sources/pdfpagecontenteditorwidget.h @@ -49,6 +49,7 @@ private: Ui::PDFPageContentEditorWidget* ui; QSignalMapper m_actionMapper; int m_toolBoxColumnCount; + QSize m_toolButtonIconSize; }; } // namespace pdf diff --git a/Pdf4QtLib/sources/pdfpagecontentelements.cpp b/Pdf4QtLib/sources/pdfpagecontentelements.cpp index c9b6a64..1c0ce2c 100644 --- a/Pdf4QtLib/sources/pdfpagecontentelements.cpp +++ b/Pdf4QtLib/sources/pdfpagecontentelements.cpp @@ -426,6 +426,8 @@ void PDFPageContentElementDot::drawPage(QPainter* painter, PDFPainterStateGuard guard(painter); painter->setWorldMatrix(pagePointToDevicePointMatrix, true); painter->setRenderHint(QPainter::Antialiasing); + painter->setPen(getPen()); + painter->setBrush(getBrush()); painter->drawPoint(m_point); } diff --git a/Pdf4QtViewerPlugins/SignaturePlugin/signatureplugin.cpp b/Pdf4QtViewerPlugins/SignaturePlugin/signatureplugin.cpp index da0cb29..0bc6943 100644 --- a/Pdf4QtViewerPlugins/SignaturePlugin/signatureplugin.cpp +++ b/Pdf4QtViewerPlugins/SignaturePlugin/signatureplugin.cpp @@ -128,6 +128,7 @@ void SignaturePlugin::setWidget(pdf::PDFWidget* widget) m_tools[HorizontalLineTool] = new pdf::PDFCreatePCElementLineTool(widget->getDrawWidgetProxy(), &m_scene, createHorizontalLineAction, true, false, this); m_tools[VerticalLineTool] = new pdf::PDFCreatePCElementLineTool(widget->getDrawWidgetProxy(), &m_scene, createVerticalLineAction, false, true, this); m_tools[LineTool] = new pdf::PDFCreatePCElementLineTool(widget->getDrawWidgetProxy(), &m_scene, createLineAction, false, false, this); + m_tools[DotTool] = new pdf::PDFCreatePCElementDotTool(widget->getDrawWidgetProxy(), &m_scene, createDotAction, this); pdf::PDFToolManager* toolManager = widget->getToolManager(); for (pdf::PDFWidgetTool* tool : m_tools) @@ -159,18 +160,6 @@ std::vector SignaturePlugin::getActions() const std::vector result; result.push_back(m_actions[Activate]); - result.push_back(m_actions[Text]); - result.push_back(m_actions[FreehandCurve]); - result.push_back(m_actions[AcceptMark]); - result.push_back(m_actions[RejectMark]); - result.push_back(m_actions[Rectangle]); - result.push_back(m_actions[RoundedRectangle]); - result.push_back(m_actions[HorizontalLine]); - result.push_back(m_actions[VerticalLine]); - result.push_back(m_actions[Line]); - result.push_back(m_actions[Dot]); - result.push_back(m_actions[Clear]); - result.push_back(nullptr); result.push_back(m_actions[SignElectronically]); result.push_back(m_actions[SignDigitally]); result.push_back(m_actions[Certificates]); @@ -277,6 +266,7 @@ void SignaturePlugin::updateDockWidget() m_editorWidget->setAllowedAreas(Qt::LeftDockWidgetArea | Qt::RightDockWidgetArea); m_dataExchangeInterface->getMainWindow()->addDockWidget(Qt::RightDockWidgetArea, m_editorWidget, Qt::Vertical); m_editorWidget->setFloating(false); + m_editorWidget->setWindowTitle(tr("Signature Toolbox")); for (QAction* action : m_actions) { diff --git a/Pdf4QtViewerPlugins/SignaturePlugin/signatureplugin.h b/Pdf4QtViewerPlugins/SignaturePlugin/signatureplugin.h index d76a99b..a8cbf13 100644 --- a/Pdf4QtViewerPlugins/SignaturePlugin/signatureplugin.h +++ b/Pdf4QtViewerPlugins/SignaturePlugin/signatureplugin.h @@ -86,6 +86,7 @@ private: HorizontalLineTool, VerticalLineTool, LineTool, + DotTool, LastTool }; From f34f10ebb7e5db655d91d19c9fbe3fc399914ddc Mon Sep 17 00:00:00 2001 From: Jakub Melka Date: Sun, 27 Feb 2022 19:59:53 +0100 Subject: [PATCH 07/39] Signature plugin: Freehand curve --- .../sources/pdfpagecontenteditortools.cpp | 126 ++++++++++++++++++ Pdf4QtLib/sources/pdfpagecontenteditortools.h | 36 +++++ Pdf4QtLib/sources/pdfpagecontentelements.cpp | 61 +++++++++ Pdf4QtLib/sources/pdfpagecontentelements.h | 27 ++++ .../SignaturePlugin/signatureplugin.cpp | 1 + .../SignaturePlugin/signatureplugin.h | 1 + 6 files changed, 252 insertions(+) diff --git a/Pdf4QtLib/sources/pdfpagecontenteditortools.cpp b/Pdf4QtLib/sources/pdfpagecontenteditortools.cpp index 25fded7..f6fb545 100644 --- a/Pdf4QtLib/sources/pdfpagecontenteditortools.cpp +++ b/Pdf4QtLib/sources/pdfpagecontenteditortools.cpp @@ -21,6 +21,7 @@ #include #include +#include namespace pdf { @@ -368,4 +369,129 @@ void PDFCreatePCElementDotTool::onPointPicked(PDFInteger pageIndex, QPointF page setActive(false); } +PDFCreatePCElementFreehandCurveTool::PDFCreatePCElementFreehandCurveTool(PDFDrawWidgetProxy* proxy, + PDFPageContentScene* scene, + QAction* action, + QObject* parent) : + BaseClass(proxy, scene, action, parent), + m_element(nullptr) +{ + QPen pen(Qt::SolidLine); + pen.setWidthF(2.0); + pen.setCapStyle(Qt::RoundCap); + + m_element = new PDFPageContentElementFreehandCurve(); + m_element->setBrush(Qt::NoBrush); + m_element->setPen(std::move(pen)); +} + +PDFCreatePCElementFreehandCurveTool::~PDFCreatePCElementFreehandCurveTool() +{ + delete m_element; +} + +void PDFCreatePCElementFreehandCurveTool::drawPage(QPainter* painter, + PDFInteger pageIndex, + const PDFPrecompiledPage* compiledPage, + PDFTextLayoutGetter& layoutGetter, + const QMatrix& pagePointToDevicePointMatrix, + QList& errors) const +{ + BaseClass::drawPage(painter, pageIndex, compiledPage, layoutGetter, pagePointToDevicePointMatrix, errors); + + if (pageIndex != m_element->getPageIndex() || m_element->isEmpty()) + { + return; + } + + m_element->drawPage(painter, pageIndex, compiledPage, layoutGetter, pagePointToDevicePointMatrix, errors); +} + +void PDFCreatePCElementFreehandCurveTool::mousePressEvent(QWidget* widget, QMouseEvent* event) +{ + Q_UNUSED(widget); + event->accept(); + + if (event->button() == Qt::LeftButton) + { + // Try to perform pick point + QPointF pagePoint; + PDFInteger pageIndex = getProxy()->getPageUnderPoint(event->pos(), &pagePoint); + if (pageIndex != -1 && // We have picked some point on page + (m_element->getPageIndex() == -1 || m_element->getPageIndex() == pageIndex)) // We are under current page + { + m_element->setPageIndex(pageIndex); + m_element->addStartPoint(pagePoint); + } + } + else if (event->button() == Qt::RightButton) + { + resetTool(); + } + + emit getProxy()->repaintNeeded(); +} + +void PDFCreatePCElementFreehandCurveTool::mouseReleaseEvent(QWidget* widget, QMouseEvent* event) +{ + Q_UNUSED(widget); + event->accept(); + + if (event->button() == Qt::LeftButton) + { + // Try to perform pick point + QPointF pagePoint; + PDFInteger pageIndex = getProxy()->getPageUnderPoint(event->pos(), &pagePoint); + if (pageIndex != -1 && // We have picked some point on page + (m_element->getPageIndex() == pageIndex)) // We are under current page + { + m_element->setPageIndex(pageIndex); + m_element->addPoint(pagePoint); + + if (!m_element->isEmpty()) + { + m_scene->addElement(m_element->clone()); + } + } + + resetTool(); + } + + emit getProxy()->repaintNeeded(); +} + +void PDFCreatePCElementFreehandCurveTool::mouseMoveEvent(QWidget* widget, QMouseEvent* event) +{ + Q_UNUSED(widget); + event->accept(); + + if (event->buttons() & Qt::LeftButton && m_element->getPageIndex() != -1) + { + // Try to add point to the path + QPointF pagePoint; + PDFInteger pageIndex = getProxy()->getPageUnderPoint(event->pos(), &pagePoint); + if (pageIndex == m_element->getPageIndex()) + { + m_element->addPoint(pagePoint); + } + + emit getProxy()->repaintNeeded(); + } +} + +void PDFCreatePCElementFreehandCurveTool::setActiveImpl(bool active) +{ + BaseClass::setActiveImpl(active); + + if (!active) + { + resetTool(); + } +} + +void PDFCreatePCElementFreehandCurveTool::resetTool() +{ + m_element->clear(); +} + } // namespace pdf diff --git a/Pdf4QtLib/sources/pdfpagecontenteditortools.h b/Pdf4QtLib/sources/pdfpagecontenteditortools.h index b03e9d0..5dcb457 100644 --- a/Pdf4QtLib/sources/pdfpagecontenteditortools.h +++ b/Pdf4QtLib/sources/pdfpagecontenteditortools.h @@ -28,6 +28,7 @@ class PDFPageContentSvgElement; class PDFPageContentElementDot; class PDFPageContentElementLine; class PDFPageContentElementRectangle; +class PDFPageContentElementFreehandCurve; class PDFCreatePCElementTool : public PDFWidgetTool { @@ -165,6 +166,41 @@ private: PDFPageContentElementDot* m_element; }; +/// Tool that creates freehand curve element. +class PDF4QTLIBSHARED_EXPORT PDFCreatePCElementFreehandCurveTool : public PDFCreatePCElementTool +{ + Q_OBJECT + +private: + using BaseClass = PDFCreatePCElementTool; + +public: + explicit PDFCreatePCElementFreehandCurveTool(PDFDrawWidgetProxy* proxy, + PDFPageContentScene* scene, + QAction* action, + QObject* parent); + virtual ~PDFCreatePCElementFreehandCurveTool() override; + + virtual void drawPage(QPainter* painter, + PDFInteger pageIndex, + const PDFPrecompiledPage* compiledPage, + PDFTextLayoutGetter& layoutGetter, + const QMatrix& pagePointToDevicePointMatrix, + QList& errors) const override; + + virtual void mousePressEvent(QWidget* widget, QMouseEvent* event) override; + virtual void mouseReleaseEvent(QWidget* widget, QMouseEvent* event) override; + virtual void mouseMoveEvent(QWidget* widget, QMouseEvent* event) override; + +protected: + virtual void setActiveImpl(bool active); + +private: + void resetTool(); + + PDFPageContentElementFreehandCurve* m_element; +}; + } // namespace pdf #endif // PDFPAGECONTENTEDITORTOOLS_H diff --git a/Pdf4QtLib/sources/pdfpagecontentelements.cpp b/Pdf4QtLib/sources/pdfpagecontentelements.cpp index 1c0ce2c..0d40673 100644 --- a/Pdf4QtLib/sources/pdfpagecontentelements.cpp +++ b/Pdf4QtLib/sources/pdfpagecontentelements.cpp @@ -441,4 +441,65 @@ void PDFPageContentElementDot::setPoint(QPointF newPoint) m_point = newPoint; } +PDFPageContentElementFreehandCurve* PDFPageContentElementFreehandCurve::clone() const +{ + PDFPageContentElementFreehandCurve* copy = new PDFPageContentElementFreehandCurve(); + copy->setPageIndex(getPageIndex()); + copy->setPen(getPen()); + copy->setBrush(getBrush()); + copy->setCurve(getCurve()); + return copy; +} + +void PDFPageContentElementFreehandCurve::drawPage(QPainter* painter, + PDFInteger pageIndex, + const PDFPrecompiledPage* compiledPage, + PDFTextLayoutGetter& layoutGetter, + const QMatrix& pagePointToDevicePointMatrix, + QList& errors) const +{ + Q_UNUSED(compiledPage); + Q_UNUSED(layoutGetter); + Q_UNUSED(errors); + + if (pageIndex != getPageIndex()) + { + return; + } + + PDFPainterStateGuard guard(painter); + painter->setWorldMatrix(pagePointToDevicePointMatrix, true); + painter->setPen(getPen()); + painter->setBrush(getBrush()); + painter->setRenderHint(QPainter::Antialiasing); + + painter->drawPath(getCurve()); +} + +QPainterPath PDFPageContentElementFreehandCurve::getCurve() const +{ + return m_curve; +} + +void PDFPageContentElementFreehandCurve::setCurve(QPainterPath newCurve) +{ + m_curve = newCurve; +} + +void PDFPageContentElementFreehandCurve::addStartPoint(const QPointF& point) +{ + m_curve.moveTo(point); +} + +void PDFPageContentElementFreehandCurve::addPoint(const QPointF& point) +{ + m_curve.lineTo(point); +} + +void PDFPageContentElementFreehandCurve::clear() +{ + setPageIndex(-1); + m_curve = QPainterPath(); +} + } // namespace pdf diff --git a/Pdf4QtLib/sources/pdfpagecontentelements.h b/Pdf4QtLib/sources/pdfpagecontentelements.h index ea8ce90..567f43b 100644 --- a/Pdf4QtLib/sources/pdfpagecontentelements.h +++ b/Pdf4QtLib/sources/pdfpagecontentelements.h @@ -23,6 +23,7 @@ #include #include #include +#include class QSvgRenderer; @@ -147,6 +148,32 @@ private: QPointF m_point; }; +class PDF4QTLIBSHARED_EXPORT PDFPageContentElementFreehandCurve : public PDFPageContentStyledElement +{ +public: + virtual ~PDFPageContentElementFreehandCurve() = default; + + virtual PDFPageContentElementFreehandCurve* clone() const override; + + virtual void drawPage(QPainter* painter, + PDFInteger pageIndex, + const PDFPrecompiledPage* compiledPage, + PDFTextLayoutGetter& layoutGetter, + const QMatrix& pagePointToDevicePointMatrix, + QList& errors) const override; + + QPainterPath getCurve() const; + void setCurve(QPainterPath newCurve); + + bool isEmpty() const { return m_curve.isEmpty(); } + void addStartPoint(const QPointF& point); + void addPoint(const QPointF& point); + void clear(); + +private: + QPainterPath m_curve; +}; + class PDF4QTLIBSHARED_EXPORT PDFPageContentSvgElement : public PDFPageContentElement { public: diff --git a/Pdf4QtViewerPlugins/SignaturePlugin/signatureplugin.cpp b/Pdf4QtViewerPlugins/SignaturePlugin/signatureplugin.cpp index 0bc6943..70f5b14 100644 --- a/Pdf4QtViewerPlugins/SignaturePlugin/signatureplugin.cpp +++ b/Pdf4QtViewerPlugins/SignaturePlugin/signatureplugin.cpp @@ -121,6 +121,7 @@ void SignaturePlugin::setWidget(pdf::PDFWidget* widget) rejectMarkFile.close(); } + m_tools[FreehandCurveTool] = new pdf::PDFCreatePCElementFreehandCurveTool(widget->getDrawWidgetProxy(), &m_scene, createFreehandCurveAction, this); m_tools[AcceptMarkTool] = new pdf::PDFCreatePCElementSvgTool(widget->getDrawWidgetProxy(), &m_scene, createAcceptMarkAction, acceptMarkContent, this); m_tools[RejectMarkTool] = new pdf::PDFCreatePCElementSvgTool(widget->getDrawWidgetProxy(), &m_scene, createRejectMarkAction, rejectMarkContent, this); m_tools[RectangleTool] = new pdf::PDFCreatePCElementRectangleTool(widget->getDrawWidgetProxy(), &m_scene, createRectangleAction, false, this); diff --git a/Pdf4QtViewerPlugins/SignaturePlugin/signatureplugin.h b/Pdf4QtViewerPlugins/SignaturePlugin/signatureplugin.h index a8cbf13..af1c7ef 100644 --- a/Pdf4QtViewerPlugins/SignaturePlugin/signatureplugin.h +++ b/Pdf4QtViewerPlugins/SignaturePlugin/signatureplugin.h @@ -79,6 +79,7 @@ private: enum Tools { + FreehandCurveTool, AcceptMarkTool, RejectMarkTool, RectangleTool, From fe84cdb3f27c9b26aba9a8cd20b210658a290146 Mon Sep 17 00:00:00 2001 From: Jakub Melka Date: Fri, 4 Mar 2022 19:32:33 +0100 Subject: [PATCH 08/39] Signature plugin: Element manipulation --- Pdf4QtLib/sources/pdfpagecontentelements.cpp | 418 ++++++++++++++++++- Pdf4QtLib/sources/pdfpagecontentelements.h | 116 ++++- Pdf4QtViewer/pdfviewermainwindow.cpp | 1 + Pdf4QtViewer/pdfviewermainwindowlite.cpp | 1 + 4 files changed, 524 insertions(+), 12 deletions(-) diff --git a/Pdf4QtLib/sources/pdfpagecontentelements.cpp b/Pdf4QtLib/sources/pdfpagecontentelements.cpp index 0d40673..80ca7c0 100644 --- a/Pdf4QtLib/sources/pdfpagecontentelements.cpp +++ b/Pdf4QtLib/sources/pdfpagecontentelements.cpp @@ -17,11 +17,14 @@ #include "pdfpagecontentelements.h" #include "pdfpainterutils.h" +#include "pdfdrawwidget.h" +#include "pdfdrawspacecontroller.h" #include #include #include #include +#include namespace pdf { @@ -36,6 +39,16 @@ void PDFPageContentElement::setPageIndex(PDFInteger newPageIndex) m_pageIndex = newPageIndex; } +PDFInteger PDFPageContentElement::getElementId() const +{ + return m_elementId; +} + +void PDFPageContentElement::setElementId(PDFInteger newElementId) +{ + m_elementId = newElementId; +} + const QPen& PDFPageContentStyledElement::getPen() const { return m_pen; @@ -59,6 +72,7 @@ void PDFPageContentStyledElement::setBrush(const QBrush& newBrush) PDFPageContentElementRectangle* PDFPageContentElementRectangle::clone() const { PDFPageContentElementRectangle* copy = new PDFPageContentElementRectangle(); + copy->setElementId(getElementId()); copy->setPageIndex(getPageIndex()); copy->setPen(getPen()); copy->setBrush(getBrush()); @@ -123,7 +137,9 @@ void PDFPageContentElementRectangle::drawPage(QPainter* painter, PDFPageContentScene::PDFPageContentScene(QObject* parent) : QObject(parent), - m_isActive(false) + m_firstFreeId(1), + m_isActive(false), + m_manipulator(this, nullptr) { } @@ -135,10 +151,22 @@ PDFPageContentScene::~PDFPageContentScene() void PDFPageContentScene::addElement(PDFPageContentElement* element) { + element->setElementId(m_firstFreeId++); m_elements.emplace_back(element); emit sceneChanged(); } +PDFPageContentElement* PDFPageContentScene::getElementById(PDFInteger id) const +{ + auto it = std::find_if(m_elements.cbegin(), m_elements.cend(), [id](const auto& element) { return element->getElementId() == id; }); + if (it != m_elements.cend()) + { + return it->get(); + } + + return nullptr; +} + void PDFPageContentScene::clear() { if (!m_elements.empty()) @@ -168,32 +196,127 @@ void PDFPageContentScene::keyReleaseEvent(QWidget* widget, QKeyEvent* event) void PDFPageContentScene::mousePressEvent(QWidget* widget, QMouseEvent* event) { - Q_UNUSED(widget); - event->ignore(); + if (!isActive()) + { + return; + } + + MouseEventInfo info = getMouseEventInfo(widget, event->pos()); + if (info.isValid() || isMouseGrabbed()) + { + // We to handle selecting/deselecting the active + // item. After that, accept the item. + if (info.isValid() && event->button() == Qt::LeftButton) + { + info.widgetMouseStartPos = event->pos(); + info.timer.start(); + + if (!m_manipulator.isManipulationInProgress()) + { + Qt::KeyboardModifiers keyboardModifiers = QApplication::keyboardModifiers(); + const bool isCtrl = keyboardModifiers.testFlag(Qt::CTRL); + const bool isShift = keyboardModifiers.testFlag(Qt::SHIFT); + + if (isCtrl && !isShift) + { + m_manipulator.select(info.hoveredElementIds); + } + else if (!isCtrl && isShift) + { + m_manipulator.deselect(info.hoveredElementIds); + } + else + { + m_manipulator.selectNew(info.hoveredElementIds); + } + } + + event->accept(); + } + + grabMouse(info, event); + } } void PDFPageContentScene::mouseDoubleClickEvent(QWidget* widget, QMouseEvent* event) { Q_UNUSED(widget); - event->ignore(); + + if (!isActive()) + { + return; + } + + // If mouse is grabbed, then event is accepted always (because + // we get Press event, when we grabbed the mouse, then we will + // wait for corresponding release event while all mouse move events + // will be accepted, even if editor doesn't accept them. + if (isMouseGrabbed()) + { + event->accept(); + } } void PDFPageContentScene::mouseReleaseEvent(QWidget* widget, QMouseEvent* event) { Q_UNUSED(widget); - event->ignore(); + + if (!isActive()) + { + return; + } + + if (isMouseGrabbed()) + { + if (event->button() == Qt::LeftButton) + { + event->accept(); + m_manipulator.finishManipulation(); + } + + ungrabMouse(info, event); + } } void PDFPageContentScene::mouseMoveEvent(QWidget* widget, QMouseEvent* event) { - Q_UNUSED(widget); - event->ignore(); + if (!isActive()) + { + return; + } + + MouseEventInfo info = getMouseEventInfo(widget, event->pos()); + if (info.isValid()) + { + + + } + + // If mouse is grabbed, then event is accepted always (because + // we get Press event, when we grabbed the mouse, then we will + // wait for corresponding release event while all mouse move events + // will be accepted, even if editor doesn't accept them. + if (isMouseGrabbed()) + { + event->accept(); + } } void PDFPageContentScene::wheelEvent(QWidget* widget, QWheelEvent* event) { Q_UNUSED(widget); - event->ignore(); + + if (!isActive()) + { + return; + } + + // We will accept mouse wheel events, if we are grabbing the mouse. + // We do not want to zoom in/zoom out while grabbing. + if (isMouseGrabbed()) + { + event->accept(); + } } QString PDFPageContentScene::getTooltip() const @@ -234,6 +357,80 @@ void PDFPageContentScene::drawPage(QPainter* painter, } } +PDFPageContentScene::MouseEventInfo PDFPageContentScene::getMouseEventInfo(QWidget* widget, QPoint point) +{ + MouseEventInfo result; + + Q_ASSERT(isActive()); + + if (isMouseGrabbed()) + { + result = m_mouseGrabInfo.info; + result.widgetMouseCurrentPos = point; + return result; + } + + PDFWidgetSnapshot snapshot = m_proxy->getSnapshot(); + for (const PDFWidgetSnapshot::SnapshotItem& snapshotItem : snapshot.items) + { + + } + + return result; +} + +void PDFPageContentScene::grabMouse(const MouseEventInfo& info, QMouseEvent* event) +{ + Q_ASSERT(isActive()); + + if (event->type() == QEvent::MouseButtonDblClick) + { + // Double clicks doesn't grab the mouse + return; + } + + Q_ASSERT(event->type() == QEvent::MouseButtonPress); + + if (isMouseGrabbed()) + { + // If mouse is already grabbed, then when new mouse button is pressed, + // we just increase nesting level and accept the mouse event. We are + // accepting all mouse events, if mouse is grabbed. + ++m_mouseGrabInfo.mouseGrabNesting; + event->accept(); + } + else if (event->isAccepted()) + { + // Event is accepted and we are not grabbing the mouse. We must start + // grabbing the mouse. + Q_ASSERT(m_mouseGrabInfo.mouseGrabNesting == 0); + ++m_mouseGrabInfo.mouseGrabNesting; + m_mouseGrabInfo.info = info; + } +} + +void PDFPageContentScene::ungrabMouse(const MouseEventInfo& info, QMouseEvent* event) +{ + Q_UNUSED(info); + Q_ASSERT(isActive()); + Q_ASSERT(event->type() == QEvent::MouseButtonRelease); + + if (isMouseGrabbed()) + { + // Mouse is being grabbed, decrease nesting level. We must also accept + // mouse release event, because mouse is being grabbed. + --m_mouseGrabInfo.mouseGrabNesting; + event->accept(); + + if (!isMouseGrabbed()) + { + m_mouseGrabInfo.info = MouseEventInfo(); + } + } + + Q_ASSERT(m_mouseGrabInfo.mouseGrabNesting >= 0); +} + bool PDFPageContentScene::isActive() const { return m_isActive; @@ -244,6 +441,25 @@ void PDFPageContentScene::setActive(bool newIsActive) if (m_isActive != newIsActive) { m_isActive = newIsActive; + + if (!newIsActive) + { + m_mouseGrabInfo = MouseGrabInfo(); + m_manipulator.reset(); + } + + emit sceneChanged(); + } +} + +void PDFPageContentScene::removeElementsById(const std::set& selection) +{ + const size_t oldSize = m_elements.size(); + m_elements.erase(std::remove_if(m_elements.begin(), m_elements.end(), [&selection](const auto& element){ return selection.count(element->getElementId()); }), m_elements.end()); + const size_t newSize = m_elements.size(); + + if (newSize < oldSize) + { emit sceneChanged(); } } @@ -251,6 +467,7 @@ void PDFPageContentScene::setActive(bool newIsActive) PDFPageContentElementLine* PDFPageContentElementLine::clone() const { PDFPageContentElementLine* copy = new PDFPageContentElementLine(); + copy->setElementId(getElementId()); copy->setPageIndex(getPageIndex()); copy->setPen(getPen()); copy->setBrush(getBrush()); @@ -328,6 +545,7 @@ PDFPageContentSvgElement::~PDFPageContentSvgElement() PDFPageContentSvgElement* PDFPageContentSvgElement::clone() const { PDFPageContentSvgElement* copy = new PDFPageContentSvgElement(); + copy->setElementId(getElementId()); copy->setPageIndex(getPageIndex()); copy->setRectangle(getRectangle()); copy->setContent(getContent()); @@ -400,6 +618,7 @@ void PDFPageContentSvgElement::setRectangle(const QRectF& newRectangle) PDFPageContentElementDot* PDFPageContentElementDot::clone() const { PDFPageContentElementDot* copy = new PDFPageContentElementDot(); + copy->setElementId(getElementId()); copy->setPageIndex(getPageIndex()); copy->setPen(getPen()); copy->setBrush(getBrush()); @@ -444,6 +663,7 @@ void PDFPageContentElementDot::setPoint(QPointF newPoint) PDFPageContentElementFreehandCurve* PDFPageContentElementFreehandCurve::clone() const { PDFPageContentElementFreehandCurve* copy = new PDFPageContentElementFreehandCurve(); + copy->setElementId(getElementId()); copy->setPageIndex(getPageIndex()); copy->setPen(getPen()); copy->setBrush(getBrush()); @@ -502,4 +722,186 @@ void PDFPageContentElementFreehandCurve::clear() m_curve = QPainterPath(); } +PDFPageContentElementManipulator::PDFPageContentElementManipulator(PDFPageContentScene* scene, QObject* parent) : + QObject(parent), + m_scene(scene), + m_isManipulationInProgress(false) +{ + +} + +void PDFPageContentElementManipulator::reset() +{ + stopManipulation(); + deselectAll(); +} + +void PDFPageContentElementManipulator::update(PDFInteger id, SelectionModes modes) +{ + bool modified = false; + + if (modes.testFlag(Clear)) + { + modified = !m_selection.empty(); + m_selection.clear(); + } + + // Is id valid? + if (id > 0) + { + if (modes.testFlag(Select)) + { + if (!isSelected(id)) + { + modified = true; + m_selection.insert(id); + } + } + + if (modes.testFlag(Deselect)) + { + if (isSelected(id)) + { + modified = true; + m_selection.erase(id); + } + } + + if (modes.testFlag(Toggle)) + { + if (isSelected(id)) + { + m_selection.erase(id); + } + else + { + m_selection.insert(id); + } + + // When toggle is performed, selection is always changed + modified = true; + } + } + + if (modified) + { + emit selectionChanged(); + } +} + +void PDFPageContentElementManipulator::update(const std::set& ids, SelectionModes modes) +{ + bool modified = false; + + if (modes.testFlag(Clear)) + { + modified = !m_selection.empty(); + m_selection.clear(); + } + + // Is id valid? + if (!ids.empty()) + { + if (modes.testFlag(Select)) + { + for (auto id : ids) + { + if (!isSelected(id)) + { + modified = true; + m_selection.insert(id); + } + } + } + + if (modes.testFlag(Deselect)) + { + for (auto id : ids) + { + if (isSelected(id)) + { + modified = true; + m_selection.erase(id); + } + } + } + + if (modes.testFlag(Toggle)) + { + for (auto id : ids) + { + if (isSelected(id)) + { + m_selection.erase(id); + } + else + { + m_selection.insert(id); + } + + // When toggle is performed, selection is always changed + modified = true; + } + } + } + + if (modified) + { + emit selectionChanged(); + } +} + +void PDFPageContentElementManipulator::select(PDFInteger id) +{ + update(id, Select); +} + +void PDFPageContentElementManipulator::select(const std::set& ids) +{ + update(ids, Select); +} + +void PDFPageContentElementManipulator::selectNew(PDFInteger id) +{ + update(id, Select | Clear); +} + +void PDFPageContentElementManipulator::selectNew(const std::set& ids) +{ + update(ids, Select | Clear); +} + +void PDFPageContentElementManipulator::deselect(PDFInteger id) +{ + update(id, Deselect); +} + +void PDFPageContentElementManipulator::deselect(const std::set& ids) +{ + update(ids, Deselect); +} + +void PDFPageContentElementManipulator::deselectAll() +{ + update(-1, Clear); +} + +void PDFPageContentElementManipulator::manipulateDeleteSelection() +{ + stopManipulation(); + m_scene->removeElementsById(m_selection); + deselectAll(); +} + +void PDFPageContentElementManipulator::stopManipulation() +{ + if (m_isManipulationInProgress) + { + m_isManipulationInProgress = false; + m_manipulatedElements.clear(); + m_manipulationModes.clear(); + emit stateChanged(); + } +} + } // namespace pdf diff --git a/Pdf4QtLib/sources/pdfpagecontentelements.h b/Pdf4QtLib/sources/pdfpagecontentelements.h index 567f43b..745772e 100644 --- a/Pdf4QtLib/sources/pdfpagecontentelements.h +++ b/Pdf4QtLib/sources/pdfpagecontentelements.h @@ -25,10 +25,13 @@ #include #include +#include + class QSvgRenderer; namespace pdf { +class PDFPageContentScene; class PDF4QTLIBSHARED_EXPORT PDFPageContentElement { @@ -48,7 +51,11 @@ public: PDFInteger getPageIndex() const; void setPageIndex(PDFInteger newPageIndex); + PDFInteger getElementId() const; + void setElementId(PDFInteger newElementId); + protected: + PDFInteger m_elementId = -1; PDFInteger m_pageIndex = -1; }; @@ -201,6 +208,61 @@ private: std::unique_ptr m_renderer; }; +class PDF4QTLIBSHARED_EXPORT PDFPageContentElementManipulator : public QObject +{ + Q_OBJECT + +public: + PDFPageContentElementManipulator(PDFPageContentScene* scene, QObject* parent); + + enum SelectionMode + { + NoUpdate = 0x0000, + Clear = 0x0001, ///< Clears current selection + Select = 0x0002, ///< Selects item + Deselect = 0x0004, ///< Deselects item + Toggle = 0x0008, ///< Toggles selection of the item + }; + Q_DECLARE_FLAGS(SelectionModes, SelectionMode) + + /// Returns true, if element with given id is selected + /// \param id Element id + bool isSelected(PDFInteger id) const { return m_selection.count(id); } + + /// Returns true, if selection is empty + bool isSelectionEmpty() const { return m_selection.empty(); } + + /// Clear all selection, stop manipulation + void reset(); + + void update(PDFInteger id, SelectionModes modes); + void update(const std::set& ids, SelectionModes modes); + void select(PDFInteger id); + void select(const std::set& ids); + void selectNew(PDFInteger id); + void selectNew(const std::set& ids); + void deselect(PDFInteger id); + void deselect(const std::set& ids); + void deselectAll(); + + bool isManipulationInProgress() const { return m_isManipulationInProgress; } + + void manipulateDeleteSelection(); + +signals: + void selectionChanged(); + void stateChanged(); + +private: + void stopManipulation(); + + PDFPageContentScene* m_scene; + std::set m_selection; + bool m_isManipulationInProgress; + std::vector> m_manipulatedElements; + std::map m_manipulationModes; +}; + class PDF4QTLIBSHARED_EXPORT PDFPageContentScene : public QObject, public IDocumentDrawInterface, public IDrawWidgetInputInterface @@ -216,12 +278,23 @@ public: /// \param element Element void addElement(PDFPageContentElement* element); + /// Returns element by its id (identifier number) + /// \param id Element id + PDFPageContentElement* getElementById(PDFInteger id) const; + /// Clear whole scene - remove all page content elements void clear(); /// Returns true, if scene is empty bool isEmpty() const { return m_elements.empty(); } + bool isActive() const; + void setActive(bool newIsActive); + + /// Removes elements specified in selection + /// \param selection Items to be removed + void removeElementsById(const std::set& selection); + // IDrawWidgetInputInterface interface public: virtual void shortcutOverrideEvent(QWidget* widget, QKeyEvent* event) override; @@ -244,20 +317,55 @@ public: PDFTextLayoutGetter& layoutGetter, const QMatrix& pagePointToDevicePointMatrix, QList& errors) const override; - - bool isActive() const; - void setActive(bool newIsActive); - signals: /// This signal is emitted when scene has changed (including graphics) void sceneChanged(); private: + + struct MouseEventInfo + { + std::set hoveredElementIds; + QPoint widgetMouseStartPos; + QPoint widgetMouseCurrentPos; + QElapsedTimer timer; + + bool isValid() const { return !hoveredElementIds.empty(); } + }; + + MouseEventInfo getMouseEventInfo(QWidget* widget, QPoint point); + + struct MouseGrabInfo + { + MouseEventInfo info; + int mouseGrabNesting = 0; + + bool isMouseGrabbed() const { return mouseGrabNesting > 0; } + }; + + bool isMouseGrabbed() const { return m_mouseGrabInfo.isMouseGrabbed(); } + + /// Grabs mouse input, if mouse is already grabbed, or if event + /// is accepted. + /// \param info Mouse event info + /// \param event Mouse event + void grabMouse(const MouseEventInfo& info, QMouseEvent* event); + + /// Release mouse input + /// \param info Mouse event info + /// \param event Mouse event + void ungrabMouse(const MouseEventInfo& info, QMouseEvent* event); + + PDFInteger m_firstFreeId; bool m_isActive; std::vector> m_elements; std::optional m_cursor; + PDFPageContentElementManipulator m_manipulator; + MouseGrabInfo m_mouseGrabInfo; }; } // namespace pdf +Q_DECLARE_OPERATORS_FOR_FLAGS(pdf::PDFPageContentElementManipulator::SelectionModes) + #endif // PDFPAGECONTENTELEMENTS_H diff --git a/Pdf4QtViewer/pdfviewermainwindow.cpp b/Pdf4QtViewer/pdfviewermainwindow.cpp index 905a4fd..2a63550 100644 --- a/Pdf4QtViewer/pdfviewermainwindow.cpp +++ b/Pdf4QtViewer/pdfviewermainwindow.cpp @@ -104,6 +104,7 @@ PDFViewerMainWindow::PDFViewerMainWindow(QWidget* parent) : // Initialize toolbar icon size adjustToolbar(ui->mainToolBar); + ui->mainToolBar->setWindowTitle(tr("Standard")); // Initialize task bar progress #ifdef Q_OS_WIN diff --git a/Pdf4QtViewer/pdfviewermainwindowlite.cpp b/Pdf4QtViewer/pdfviewermainwindowlite.cpp index 68d0b8f..fb1638c 100644 --- a/Pdf4QtViewer/pdfviewermainwindowlite.cpp +++ b/Pdf4QtViewer/pdfviewermainwindowlite.cpp @@ -102,6 +102,7 @@ PDFViewerMainWindowLite::PDFViewerMainWindowLite(QWidget* parent) : // Initialize toolbar icon size adjustToolbar(ui->mainToolBar); + ui->mainToolBar->setWindowTitle(tr("Standard")); // Initialize task bar progress #ifdef Q_OS_WIN From 8a83d7ae85a4ad78674e1640b37986d9d4dc9425 Mon Sep 17 00:00:00 2001 From: Jakub Melka Date: Sat, 5 Mar 2022 19:24:35 +0100 Subject: [PATCH 09/39] Signature plugin: Element manipulation --- Pdf4QtLib/sources/pdfpagecontentelements.cpp | 473 +++++++++++++++++- Pdf4QtLib/sources/pdfpagecontentelements.h | 99 +++- .../SignaturePlugin/signatureplugin.cpp | 2 + 3 files changed, 555 insertions(+), 19 deletions(-) diff --git a/Pdf4QtLib/sources/pdfpagecontentelements.cpp b/Pdf4QtLib/sources/pdfpagecontentelements.cpp index 80ca7c0..6e9fbb6 100644 --- a/Pdf4QtLib/sources/pdfpagecontentelements.cpp +++ b/Pdf4QtLib/sources/pdfpagecontentelements.cpp @@ -19,6 +19,7 @@ #include "pdfpainterutils.h" #include "pdfdrawwidget.h" #include "pdfdrawspacecontroller.h" +#include "pdfwidgetutils.h" #include #include @@ -49,6 +50,121 @@ void PDFPageContentElement::setElementId(PDFInteger newElementId) m_elementId = newElementId; } +uint PDFPageContentElement::getRectangleManipulationMode(const QRectF& rectangle, + const QPointF& point, + PDFReal snapPointDistanceThreshold) const +{ + if ((rectangle.topLeft() - point).manhattanLength() < snapPointDistanceThreshold) + { + return TopLeft; + } + + if ((rectangle.topRight() - point).manhattanLength() < snapPointDistanceThreshold) + { + return TopRight; + } + + if ((rectangle.bottomLeft() - point).manhattanLength() < snapPointDistanceThreshold) + { + return BottomLeft; + } + + if ((rectangle.bottomRight() - point).manhattanLength() < snapPointDistanceThreshold) + { + return BottomRight; + } + + if (rectangle.left() <= point.x() && + point.x() <= rectangle.right() && + (qAbs(rectangle.top() - point.y()) < snapPointDistanceThreshold)) + { + return Top; + } + + if (rectangle.left() <= point.x() && + point.x() <= rectangle.right() && + (qAbs(rectangle.bottom() - point.y()) < snapPointDistanceThreshold)) + { + return Bottom; + } + + if (rectangle.top() <= point.y() && + point.y() <= rectangle.bottom() && + (qAbs(rectangle.left() - point.y()) < snapPointDistanceThreshold)) + { + return Left; + } + + if (rectangle.top() <= point.y() && + point.y() <= rectangle.bottom() && + (qAbs(rectangle.right() - point.y()) < snapPointDistanceThreshold)) + { + return Right; + } + + if (rectangle.contains(point)) + { + return Translate; + } + + return None; +} + +void PDFPageContentElement::performRectangleManipulation(QRectF& rectangle, + uint mode, + const QPointF& offset) +{ + switch (mode) + { + case None: + break; + + case Translate: + rectangle.translate(offset); + break; + + case Top: + rectangle.setTop(qMin(rectangle.bottom(), rectangle.top() + offset.y())); + break; + + case Left: + rectangle.setLeft(qMin(rectangle.right(), rectangle.left() + offset.x())); + break; + + case Right: + rectangle.setRight(qMax(rectangle.left(), rectangle.right() + offset.x())); + break; + + case Bottom: + rectangle.setBottom(qMax(rectangle.top(), rectangle.bottom() + offset.y())); + break; + + case TopLeft: + rectangle.setTop(qMin(rectangle.bottom(), rectangle.top() + offset.y())); + rectangle.setLeft(qMin(rectangle.right(), rectangle.left() + offset.x())); + break; + + case TopRight: + rectangle.setTop(qMin(rectangle.bottom(), rectangle.top() + offset.y())); + rectangle.setRight(qMax(rectangle.left(), rectangle.right() + offset.x())); + break; + + case BottomLeft: + rectangle.setBottom(qMax(rectangle.top(), rectangle.bottom() + offset.y())); + rectangle.setLeft(qMin(rectangle.right(), rectangle.left() + offset.x())); + break; + + case BottomRight: + rectangle.setBottom(qMax(rectangle.top(), rectangle.bottom() + offset.y())); + rectangle.setRight(qMax(rectangle.left(), rectangle.right() + offset.x())); + break; + + default: + Q_ASSERT(false); + break; + } +} + const QPen& PDFPageContentStyledElement::getPen() const { return m_pen; @@ -135,10 +251,22 @@ void PDFPageContentElementRectangle::drawPage(QPainter* painter, } } +uint PDFPageContentElementRectangle::getManipulationMode(const QPointF& point, + PDFReal snapPointDistanceThreshold) const +{ + return getRectangleManipulationMode(getRectangle(), point, snapPointDistanceThreshold); +} + +void PDFPageContentElementRectangle::performManipulation(uint mode, const QPointF& offset) +{ + performRectangleManipulation(m_rectangle, mode, offset); +} + PDFPageContentScene::PDFPageContentScene(QObject* parent) : QObject(parent), m_firstFreeId(1), m_isActive(false), + m_widget(nullptr), m_manipulator(this, nullptr) { @@ -156,6 +284,22 @@ void PDFPageContentScene::addElement(PDFPageContentElement* element) emit sceneChanged(); } +void PDFPageContentScene::replaceElement(PDFPageContentElement* element) +{ + std::unique_ptr elementPtr(element); + + for (size_t i = 0; i < m_elements.size(); ++i) + { + if (m_elements[i]->getElementId() == element->getElementId()) + { + m_elements[i] = std::move(elementPtr); + break; + } + } + + emit sceneChanged(); +} + PDFPageContentElement* PDFPageContentScene::getElementById(PDFInteger id) const { auto it = std::find_if(m_elements.cbegin(), m_elements.cend(), [id](const auto& element) { return element->getElementId() == id; }); @@ -214,8 +358,8 @@ void PDFPageContentScene::mousePressEvent(QWidget* widget, QMouseEvent* event) if (!m_manipulator.isManipulationInProgress()) { Qt::KeyboardModifiers keyboardModifiers = QApplication::keyboardModifiers(); - const bool isCtrl = keyboardModifiers.testFlag(Qt::CTRL); - const bool isShift = keyboardModifiers.testFlag(Qt::SHIFT); + const bool isCtrl = keyboardModifiers.testFlag(Qt::ControlModifier); + const bool isShift = keyboardModifiers.testFlag(Qt::ShiftModifier); if (isCtrl && !isShift) { @@ -236,6 +380,10 @@ void PDFPageContentScene::mousePressEvent(QWidget* widget, QMouseEvent* event) grabMouse(info, event); } + else if (event->button() == Qt::LeftButton) + { + m_manipulator.deselectAll(); + } } void PDFPageContentScene::mouseDoubleClickEvent(QWidget* widget, QMouseEvent* event) @@ -271,25 +419,55 @@ void PDFPageContentScene::mouseReleaseEvent(QWidget* widget, QMouseEvent* event) if (event->button() == Qt::LeftButton) { event->accept(); - m_manipulator.finishManipulation(); + + if (m_manipulator.isManipulationInProgress()) + { + QPointF pagePoint; + const PDFInteger pageIndex = m_widget->getDrawWidgetProxy()->getPageUnderPoint(event->pos(), &pagePoint); + m_mouseGrabInfo.info.widgetMouseCurrentPos = event->pos(); + bool isCopyCreated = QApplication::keyboardModifiers().testFlag(Qt::ControlModifier); + m_manipulator.finishManipulation(pageIndex, m_mouseGrabInfo.info.pagePos, pagePoint, isCopyCreated); + } } - ungrabMouse(info, event); + ungrabMouse(getMouseEventInfo(widget, event->pos()), event); } } void PDFPageContentScene::mouseMoveEvent(QWidget* widget, QMouseEvent* event) { + Q_UNUSED(widget); + if (!isActive()) { return; } - MouseEventInfo info = getMouseEventInfo(widget, event->pos()); - if (info.isValid()) + const PDFReal threshold = getSnapPointDistanceThreshold(); + + QPointF pagePoint; + const PDFInteger pageIndex = m_widget->getDrawWidgetProxy()->getPageUnderPoint(event->pos(), &pagePoint); + if (m_mouseGrabInfo.info.isValid() && + event->buttons().testFlag(Qt::LeftButton) && + m_mouseGrabInfo.info.pageIndex == pageIndex && + m_manipulator.isManipulationAllowed(pageIndex)) { + // Jakub Melka: Start grab? + m_mouseGrabInfo.info.widgetMouseCurrentPos = event->pos(); - + if (!m_manipulator.isManipulationInProgress()) + { + QPoint vector = m_mouseGrabInfo.info.widgetMouseCurrentPos - m_mouseGrabInfo.info.widgetMouseStartPos; + if (vector.manhattanLength() > QApplication::startDragDistance() || + m_mouseGrabInfo.info.timer.hasExpired(QApplication::startDragTime())) + { + m_manipulator.startManipulation(pageIndex, m_mouseGrabInfo.info.pagePos, pagePoint, threshold); + } + } + else + { + m_manipulator.updateManipulation(pageIndex, m_mouseGrabInfo.info.pagePos, pagePoint); + } } // If mouse is grabbed, then event is accepted always (because @@ -361,6 +539,7 @@ PDFPageContentScene::MouseEventInfo PDFPageContentScene::getMouseEventInfo(QWidg { MouseEventInfo result; + Q_UNUSED(widget); Q_ASSERT(isActive()); if (isMouseGrabbed()) @@ -370,15 +549,38 @@ PDFPageContentScene::MouseEventInfo PDFPageContentScene::getMouseEventInfo(QWidg return result; } - PDFWidgetSnapshot snapshot = m_proxy->getSnapshot(); - for (const PDFWidgetSnapshot::SnapshotItem& snapshotItem : snapshot.items) - { + result.widgetMouseStartPos = point; + result.widgetMouseCurrentPos = point; + result.timer = m_mouseGrabInfo.info.timer; + result.pageIndex = m_widget->getDrawWidgetProxy()->getPageUnderPoint(point, &result.pagePos); + const PDFReal threshold = getSnapPointDistanceThreshold(); + for (const auto& elementItem : m_elements) + { + PDFPageContentElement* element = elementItem.get(); + + if (element->getPageIndex() != result.pageIndex) + { + // Different page + continue; + } + + if (element->getManipulationMode(result.pagePos, threshold) != 0) + { + result.hoveredElementIds.insert(element->getElementId()); + } } return result; } +PDFReal PDFPageContentScene::getSnapPointDistanceThreshold() const +{ + const PDFReal snapPointDistanceThresholdPixels = PDFWidgetUtils::scaleDPI_x(m_widget, 6.0); + const PDFReal snapPointDistanceThreshold = m_widget->getDrawWidgetProxy()->transformPixelToDeviceSpace(snapPointDistanceThresholdPixels); + return snapPointDistanceThreshold; +} + void PDFPageContentScene::grabMouse(const MouseEventInfo& info, QMouseEvent* event) { Q_ASSERT(isActive()); @@ -431,6 +633,16 @@ void PDFPageContentScene::ungrabMouse(const MouseEventInfo& info, QMouseEvent* e Q_ASSERT(m_mouseGrabInfo.mouseGrabNesting >= 0); } +PDFWidget* PDFPageContentScene::widget() const +{ + return m_widget; +} + +void PDFPageContentScene::setWidget(PDFWidget* newWidget) +{ + m_widget = newWidget; +} + bool PDFPageContentScene::isActive() const { return m_isActive; @@ -501,6 +713,67 @@ void PDFPageContentElementLine::drawPage(QPainter* painter, painter->drawLine(getLine()); } +uint PDFPageContentElementLine::getManipulationMode(const QPointF& point, + PDFReal snapPointDistanceThreshold) const +{ + if ((m_line.p1() - point).manhattanLength() < snapPointDistanceThreshold) + { + return Pt1; + } + + if ((m_line.p2() - point).manhattanLength() < snapPointDistanceThreshold) + { + return Pt2; + } + + QPointF vl = m_line.p2() - m_line.p1(); + QPointF vp = point - m_line.p1(); + + const qreal lengthSquared = QPointF::dotProduct(vp, vp); + + if (qFuzzyIsNull(lengthSquared)) + { + return None; + } + + const qreal t = QPointF::dotProduct(vl, vp) / lengthSquared; + if (t >= 0.0 && t <= 1.0) + { + QPointF projectedPoint = m_line.p1() + t * vl; + if ((point - projectedPoint).manhattanLength() < snapPointDistanceThreshold) + { + return Translate; + } + } + + return None; +} + +void PDFPageContentElementLine::performManipulation(uint mode, const QPointF& offset) +{ + switch (mode) + { + case None: + break; + + case Pt1: + m_line.setP1(m_line.p1() + offset); + break; + + case Pt2: + m_line.setP2(m_line.p2() + offset); + break; + + case Translate: + m_line.translate(offset); + break; + + default: + Q_ASSERT(false); + break; + } +} + PDFPageContentElementLine::LineGeometry PDFPageContentElementLine::getGeometry() const { return m_geometry; @@ -591,6 +864,16 @@ void PDFPageContentSvgElement::drawPage(QPainter* painter, m_renderer->render(painter, targetRenderBox); } +uint PDFPageContentSvgElement::getManipulationMode(const QPointF& point, PDFReal snapPointDistanceThreshold) const +{ + return getRectangleManipulationMode(getRectangle(), point, snapPointDistanceThreshold); +} + +void PDFPageContentSvgElement::performManipulation(uint mode, const QPointF& offset) +{ + performRectangleManipulation(m_rectangle, mode, offset); +} + const QByteArray& PDFPageContentSvgElement::getContent() const { return m_content; @@ -650,6 +933,34 @@ void PDFPageContentElementDot::drawPage(QPainter* painter, painter->drawPoint(m_point); } +uint PDFPageContentElementDot::getManipulationMode(const QPointF& point, + PDFReal snapPointDistanceThreshold) const +{ + if ((m_point - point).manhattanLength() < snapPointDistanceThreshold) + { + return Translate; + } + + return None; +} + +void PDFPageContentElementDot::performManipulation(uint mode, const QPointF& offset) +{ + switch (mode) + { + case None: + break; + + case Translate: + m_point += offset; + break; + + default: + Q_ASSERT(false); + break; + } +} + QPointF PDFPageContentElementDot::getPoint() const { return m_point; @@ -696,6 +1007,40 @@ void PDFPageContentElementFreehandCurve::drawPage(QPainter* painter, painter->drawPath(getCurve()); } +uint PDFPageContentElementFreehandCurve::getManipulationMode(const QPointF& point, PDFReal snapPointDistanceThreshold) const +{ + Q_UNUSED(snapPointDistanceThreshold); + + if (m_curve.isEmpty()) + { + return None; + } + + if (m_curve.controlPointRect().contains(point)) + { + return Translate; + } + + return None; +} + +void PDFPageContentElementFreehandCurve::performManipulation(uint mode, const QPointF& offset) +{ + switch (mode) + { + case None: + break; + + case Translate: + m_curve.translate(offset); + break; + + default: + Q_ASSERT(false); + break; + } +} + QPainterPath PDFPageContentElementFreehandCurve::getCurve() const { return m_curve; @@ -732,7 +1077,7 @@ PDFPageContentElementManipulator::PDFPageContentElementManipulator(PDFPageConten void PDFPageContentElementManipulator::reset() { - stopManipulation(); + cancelManipulation(); deselectAll(); } @@ -863,12 +1208,12 @@ void PDFPageContentElementManipulator::select(const std::set& ids) void PDFPageContentElementManipulator::selectNew(PDFInteger id) { - update(id, Select | Clear); + update(id, SelectionModes(Select | Clear)); } void PDFPageContentElementManipulator::selectNew(const std::set& ids) { - update(ids, Select | Clear); + update(ids, SelectionModes(Select | Clear)); } void PDFPageContentElementManipulator::deselect(PDFInteger id) @@ -886,22 +1231,118 @@ void PDFPageContentElementManipulator::deselectAll() update(-1, Clear); } +bool PDFPageContentElementManipulator::isManipulationAllowed(PDFInteger pageIndex) const +{ + for (const PDFInteger id : m_selection) + { + if (const PDFPageContentElement* element = m_scene->getElementById(id)) + { + if (element->getPageIndex() == pageIndex) + { + return true; + } + } + } + + return false; +} + void PDFPageContentElementManipulator::manipulateDeleteSelection() { - stopManipulation(); + cancelManipulation(); m_scene->removeElementsById(m_selection); deselectAll(); } -void PDFPageContentElementManipulator::stopManipulation() +void PDFPageContentElementManipulator::startManipulation(PDFInteger pageIndex, + const QPointF& startPoint, + const QPointF& currentPoint, + PDFReal snapPointDistanceThreshold) { - if (m_isManipulationInProgress) + Q_ASSERT(!isManipulationInProgress()); + + // Collect elements to be manipulated + for (const PDFInteger id : m_selection) + { + const PDFPageContentElement* element = m_scene->getElementById(id); + + if (!element || element->getPageIndex() != pageIndex) + { + continue; + } + + const uint manipulationMode = element->getManipulationMode(startPoint, snapPointDistanceThreshold); + if (manipulationMode) + { + // Jakub Melka: yes, we can manipulate this element + m_manipulatedElements.emplace_back(element->clone()); + m_manipulationModes[id] = manipulationMode; + } + } + + m_lastUpdatedPoint = startPoint; + updateManipulation(pageIndex, startPoint, currentPoint); + emit stateChanged(); +} + +void PDFPageContentElementManipulator::updateManipulation(PDFInteger pageIndex, + const QPointF& startPoint, + const QPointF& currentPoint) +{ + Q_UNUSED(startPoint); + + QPointF offset = currentPoint - m_lastUpdatedPoint; + + for (const auto& element : m_manipulatedElements) + { + if (element->getElementId() == pageIndex) + { + element->performManipulation(m_manipulationModes[element->getElementId()], offset); + } + } + + emit stateChanged(); +} + +void PDFPageContentElementManipulator::finishManipulation(PDFInteger pageIndex, + const QPointF& startPoint, + const QPointF& currentPoint, + bool createCopy) +{ + Q_ASSERT(isManipulationInProgress()); + updateManipulation(pageIndex, startPoint, currentPoint); + + if (createCopy) + { + for (const auto& element : m_manipulatedElements) + { + m_scene->addElement(element->clone()); + } + } + else + { + for (const auto& element : m_manipulatedElements) + { + m_scene->replaceElement(element->clone()); + } + } + + cancelManipulation(); +} + +void PDFPageContentElementManipulator::cancelManipulation() +{ + if (isManipulationInProgress()) { m_isManipulationInProgress = false; m_manipulatedElements.clear(); m_manipulationModes.clear(); emit stateChanged(); } + + Q_ASSERT(!m_isManipulationInProgress); + Q_ASSERT(m_manipulatedElements.empty()); + Q_ASSERT(m_manipulationModes.empty()); } } // namespace pdf diff --git a/Pdf4QtLib/sources/pdfpagecontentelements.h b/Pdf4QtLib/sources/pdfpagecontentelements.h index 745772e..db6f30f 100644 --- a/Pdf4QtLib/sources/pdfpagecontentelements.h +++ b/Pdf4QtLib/sources/pdfpagecontentelements.h @@ -31,10 +31,11 @@ class QSvgRenderer; namespace pdf { +class PDFWidget; class PDFPageContentScene; class PDF4QTLIBSHARED_EXPORT PDFPageContentElement -{ +{ public: explicit PDFPageContentElement() = default; virtual ~PDFPageContentElement() = default; @@ -48,6 +49,19 @@ public: const QMatrix& pagePointToDevicePointMatrix, QList& errors) const = 0; + /// Returns manipulation mode. If manipulation mode is zero, then element + /// cannot be manipulated. If it is nonzero, then element can be manipulated + /// in some way. + /// \param point Point on page + /// \param snapPointDistanceTreshold Snap point threshold + virtual uint getManipulationMode(const QPointF& point, PDFReal snapPointDistanceThreshold) const = 0; + + /// Performs manipulation of given mode. Mode must be the one returned + /// from function getManipulationMode. \sa getManipulationMode + /// \param mode Mode + /// \param offset Offset + virtual void performManipulation(uint mode, const QPointF& offset) = 0; + PDFInteger getPageIndex() const; void setPageIndex(PDFInteger newPageIndex); @@ -55,6 +69,31 @@ public: void setElementId(PDFInteger newElementId); protected: + + enum ManipulationModes : uint + { + None = 0, + Translate, + Top, + Left, + Right, + Bottom, + TopLeft, + TopRight, + BottomLeft, + BottomRight, + Pt1, + Pt2 + }; + + uint getRectangleManipulationMode(const QRectF& rectangle, + const QPointF& point, + PDFReal snapPointDistanceThreshold) const; + + void performRectangleManipulation(QRectF& rectangle, + uint mode, + const QPointF& offset); + PDFInteger m_elementId = -1; PDFInteger m_pageIndex = -1; }; @@ -96,6 +135,11 @@ public: const QMatrix& pagePointToDevicePointMatrix, QList& errors) const override; + virtual uint getManipulationMode(const QPointF& point, + PDFReal snapPointDistanceThreshold) const override; + + virtual void performManipulation(uint mode, const QPointF& offset) override; + private: bool m_rounded = false; QRectF m_rectangle; @@ -122,6 +166,11 @@ public: const QMatrix& pagePointToDevicePointMatrix, QList& errors) const override; + virtual uint getManipulationMode(const QPointF& point, + PDFReal snapPointDistanceThreshold) const override; + + virtual void performManipulation(uint mode, const QPointF& offset) override; + LineGeometry getGeometry() const; void setGeometry(LineGeometry newGeometry); @@ -147,6 +196,11 @@ public: const QMatrix& pagePointToDevicePointMatrix, QList& errors) const override; + virtual uint getManipulationMode(const QPointF& point, + PDFReal snapPointDistanceThreshold) const override; + + virtual void performManipulation(uint mode, const QPointF& offset) override; + QPointF getPoint() const; void setPoint(QPointF newPoint); @@ -169,6 +223,11 @@ public: const QMatrix& pagePointToDevicePointMatrix, QList& errors) const override; + virtual uint getManipulationMode(const QPointF& point, + PDFReal snapPointDistanceThreshold) const override; + + virtual void performManipulation(uint mode, const QPointF& offset) override; + QPainterPath getCurve() const; void setCurve(QPainterPath newCurve); @@ -196,6 +255,11 @@ public: const QMatrix& pagePointToDevicePointMatrix, QList& errors) const override; + virtual uint getManipulationMode(const QPointF& point, + PDFReal snapPointDistanceThreshold) const override; + + virtual void performManipulation(uint mode, const QPointF& offset) override; + const QByteArray& getContent() const; void setContent(const QByteArray& newContent); @@ -245,22 +309,38 @@ public: void deselect(const std::set& ids); void deselectAll(); + bool isManipulationAllowed(PDFInteger pageIndex) const; bool isManipulationInProgress() const { return m_isManipulationInProgress; } void manipulateDeleteSelection(); + void startManipulation(PDFInteger pageIndex, + const QPointF& startPoint, + const QPointF& currentPoint, + PDFReal snapPointDistanceThreshold); + + void updateManipulation(PDFInteger pageIndex, + const QPointF& startPoint, + const QPointF& currentPoint); + + void finishManipulation(PDFInteger pageIndex, + const QPointF& startPoint, + const QPointF& currentPoint, + bool createCopy); + + void cancelManipulation(); + signals: void selectionChanged(); void stateChanged(); private: - void stopManipulation(); - PDFPageContentScene* m_scene; std::set m_selection; bool m_isManipulationInProgress; std::vector> m_manipulatedElements; std::map m_manipulationModes; + QPointF m_lastUpdatedPoint; }; class PDF4QTLIBSHARED_EXPORT PDFPageContentScene : public QObject, @@ -278,6 +358,11 @@ public: /// \param element Element void addElement(PDFPageContentElement* element); + /// Replaces element in the page content scene, scene + /// takes ownership over the element. + /// \param element Element + void replaceElement(PDFPageContentElement* element); + /// Returns element by its id (identifier number) /// \param id Element id PDFPageContentElement* getElementById(PDFInteger id) const; @@ -317,6 +402,9 @@ public: PDFTextLayoutGetter& layoutGetter, const QMatrix& pagePointToDevicePointMatrix, QList& errors) const override; + PDFWidget* widget() const; + void setWidget(PDFWidget* newWidget); + signals: /// This signal is emitted when scene has changed (including graphics) void sceneChanged(); @@ -329,6 +417,8 @@ private: QPoint widgetMouseStartPos; QPoint widgetMouseCurrentPos; QElapsedTimer timer; + PDFInteger pageIndex = -1; + QPointF pagePos; bool isValid() const { return !hoveredElementIds.empty(); } }; @@ -343,6 +433,8 @@ private: bool isMouseGrabbed() const { return mouseGrabNesting > 0; } }; + PDFReal getSnapPointDistanceThreshold() const; + bool isMouseGrabbed() const { return m_mouseGrabInfo.isMouseGrabbed(); } /// Grabs mouse input, if mouse is already grabbed, or if event @@ -358,6 +450,7 @@ private: PDFInteger m_firstFreeId; bool m_isActive; + PDFWidget* m_widget; std::vector> m_elements; std::optional m_cursor; PDFPageContentElementManipulator m_manipulator; diff --git a/Pdf4QtViewerPlugins/SignaturePlugin/signatureplugin.cpp b/Pdf4QtViewerPlugins/SignaturePlugin/signatureplugin.cpp index 70f5b14..d7c8e9e 100644 --- a/Pdf4QtViewerPlugins/SignaturePlugin/signatureplugin.cpp +++ b/Pdf4QtViewerPlugins/SignaturePlugin/signatureplugin.cpp @@ -137,7 +137,9 @@ void SignaturePlugin::setWidget(pdf::PDFWidget* widget) toolManager->addTool(tool); } + m_widget->addInputInterface(&m_scene); m_widget->getDrawWidgetProxy()->registerDrawInterface(&m_scene); + m_scene.setWidget(m_widget); connect(&m_scene, &pdf::PDFPageContentScene::sceneChanged, this, &SignaturePlugin::onSceneChanged); connect(clearAction, &QAction::triggered, &m_scene, &pdf::PDFPageContentScene::clear); connect(activateAction, &QAction::triggered, this, &SignaturePlugin::setActive); From 6037f77a226a65f4e8c8a9b0d66c03588aaf48bb Mon Sep 17 00:00:00 2001 From: Jakub Melka Date: Sun, 6 Mar 2022 19:00:46 +0100 Subject: [PATCH 10/39] Signature plugin: Element manipulation - bugfixing, graphics and cursor icons --- Pdf4QtLib/sources/pdfpagecontentelements.cpp | 143 ++++++++++++++++-- Pdf4QtLib/sources/pdfpagecontentelements.h | 18 ++- .../SignaturePlugin/signatureplugin.cpp | 8 +- .../SignaturePlugin/signatureplugin.h | 2 +- 4 files changed, 155 insertions(+), 16 deletions(-) diff --git a/Pdf4QtLib/sources/pdfpagecontentelements.cpp b/Pdf4QtLib/sources/pdfpagecontentelements.cpp index 6e9fbb6..2db90ab 100644 --- a/Pdf4QtLib/sources/pdfpagecontentelements.cpp +++ b/Pdf4QtLib/sources/pdfpagecontentelements.cpp @@ -50,6 +50,40 @@ void PDFPageContentElement::setElementId(PDFInteger newElementId) m_elementId = newElementId; } +Qt::CursorShape PDFPageContentElement::getCursorShapeForManipulationMode(uint mode) +{ + switch (mode) + { + case None: + case Pt1: + case Pt2: + case Translate: + return Qt::ArrowCursor; + + case Top: + case Bottom: + return Qt::SizeVerCursor; + + case Left: + case Right: + return Qt::SizeHorCursor; + + case TopLeft: + case BottomRight: + return Qt::SizeBDiagCursor; + + case TopRight: + case BottomLeft: + return Qt::SizeFDiagCursor; + + default: + Q_ASSERT(false); + break; + } + + return Qt::ArrowCursor; +} + uint PDFPageContentElement::getRectangleManipulationMode(const QRectF& rectangle, const QPointF& point, PDFReal snapPointDistanceThreshold) const @@ -90,14 +124,14 @@ uint PDFPageContentElement::getRectangleManipulationMode(const QRectF& rectangle if (rectangle.top() <= point.y() && point.y() <= rectangle.bottom() && - (qAbs(rectangle.left() - point.y()) < snapPointDistanceThreshold)) + (qAbs(rectangle.left() - point.x()) < snapPointDistanceThreshold)) { return Left; } if (rectangle.top() <= point.y() && point.y() <= rectangle.bottom() && - (qAbs(rectangle.right() - point.y()) < snapPointDistanceThreshold)) + (qAbs(rectangle.right() - point.x()) < snapPointDistanceThreshold)) { return Right; } @@ -281,7 +315,7 @@ void PDFPageContentScene::addElement(PDFPageContentElement* element) { element->setElementId(m_firstFreeId++); m_elements.emplace_back(element); - emit sceneChanged(); + emit sceneChanged(false); } void PDFPageContentScene::replaceElement(PDFPageContentElement* element) @@ -293,11 +327,10 @@ void PDFPageContentScene::replaceElement(PDFPageContentElement* element) if (m_elements[i]->getElementId() == element->getElementId()) { m_elements[i] = std::move(elementPtr); + emit sceneChanged(false); break; } } - - emit sceneChanged(); } PDFPageContentElement* PDFPageContentScene::getElementById(PDFInteger id) const @@ -316,7 +349,7 @@ void PDFPageContentScene::clear() if (!m_elements.empty()) { m_elements.clear(); - emit sceneChanged(); + emit sceneChanged(false); } } @@ -384,6 +417,8 @@ void PDFPageContentScene::mousePressEvent(QWidget* widget, QMouseEvent* event) { m_manipulator.deselectAll(); } + + updateMouseCursor(info, getSnapPointDistanceThreshold()); } void PDFPageContentScene::mouseDoubleClickEvent(QWidget* widget, QMouseEvent* event) @@ -432,6 +467,9 @@ void PDFPageContentScene::mouseReleaseEvent(QWidget* widget, QMouseEvent* event) ungrabMouse(getMouseEventInfo(widget, event->pos()), event); } + + MouseEventInfo info = getMouseEventInfo(widget, event->pos()); + updateMouseCursor(info, getSnapPointDistanceThreshold()); } void PDFPageContentScene::mouseMoveEvent(QWidget* widget, QMouseEvent* event) @@ -470,6 +508,9 @@ void PDFPageContentScene::mouseMoveEvent(QWidget* widget, QMouseEvent* event) } } + MouseEventInfo info = getMouseEventInfo(widget, event->pos()); + updateMouseCursor(info, threshold); + // If mouse is grabbed, then event is accepted always (because // we get Press event, when we grabbed the mouse, then we will // wait for corresponding release event while all mouse move events @@ -478,6 +519,11 @@ void PDFPageContentScene::mouseMoveEvent(QWidget* widget, QMouseEvent* event) { event->accept(); } + + if (m_manipulator.isManipulationInProgress()) + { + emit sceneChanged(true); + } } void PDFPageContentScene::wheelEvent(QWidget* widget, QWheelEvent* event) @@ -533,6 +579,8 @@ void PDFPageContentScene::drawPage(QPainter* painter, element->drawPage(painter, pageIndex, compiledPage, layoutGetter, pagePointToDevicePointMatrix, errors); } + + m_manipulator.drawPage(painter, pageIndex, compiledPage, layoutGetter, pagePointToDevicePointMatrix, errors); } PDFPageContentScene::MouseEventInfo PDFPageContentScene::getMouseEventInfo(QWidget* widget, QPoint point) @@ -633,6 +681,49 @@ void PDFPageContentScene::ungrabMouse(const MouseEventInfo& info, QMouseEvent* e Q_ASSERT(m_mouseGrabInfo.mouseGrabNesting >= 0); } +void PDFPageContentScene::updateMouseCursor(const MouseEventInfo& info, PDFReal snapPointDistanceThreshold) +{ + std::optional cursorShapeValue; + + for (const PDFInteger id : info.hoveredElementIds) + { + PDFPageContentElement* element = getElementById(id); + uint manipulationMode = element->getManipulationMode(info.pagePos, snapPointDistanceThreshold); + + if (manipulationMode > 0) + { + Qt::CursorShape cursorShape = PDFPageContentElement::getCursorShapeForManipulationMode(manipulationMode); + + if (!cursorShapeValue) + { + cursorShapeValue = cursorShape; + } + else if (cursorShapeValue.value() != cursorShape) + { + cursorShapeValue = Qt::ArrowCursor; + break; + } + } + } + + if (cursorShapeValue && cursorShapeValue.value() == Qt::ArrowCursor && + m_manipulator.isManipulationInProgress()) + { + const bool isCopy = QApplication::keyboardModifiers().testFlag(Qt::ControlModifier); + cursorShapeValue = isCopy ? Qt::DragCopyCursor : Qt::DragMoveCursor; + } + + // Update cursor shape + if (!cursorShapeValue) + { + m_cursor = std::nullopt; + } + else if (!m_cursor.has_value() || m_cursor.value().shape() != cursorShapeValue.value()) + { + m_cursor = QCursor(cursorShapeValue.value()); + } +} + PDFWidget* PDFPageContentScene::widget() const { return m_widget; @@ -660,7 +751,7 @@ void PDFPageContentScene::setActive(bool newIsActive) m_manipulator.reset(); } - emit sceneChanged(); + emit sceneChanged(false); } } @@ -672,7 +763,7 @@ void PDFPageContentScene::removeElementsById(const std::set& selecti if (newSize < oldSize) { - emit sceneChanged(); + emit sceneChanged(false); } } @@ -1280,9 +1371,13 @@ void PDFPageContentElementManipulator::startManipulation(PDFInteger pageIndex, } } - m_lastUpdatedPoint = startPoint; - updateManipulation(pageIndex, startPoint, currentPoint); - emit stateChanged(); + if (!m_manipulatedElements.empty()) + { + m_isManipulationInProgress = true; + m_lastUpdatedPoint = startPoint; + updateManipulation(pageIndex, startPoint, currentPoint); + emit stateChanged(); + } } void PDFPageContentElementManipulator::updateManipulation(PDFInteger pageIndex, @@ -1295,12 +1390,13 @@ void PDFPageContentElementManipulator::updateManipulation(PDFInteger pageIndex, for (const auto& element : m_manipulatedElements) { - if (element->getElementId() == pageIndex) + if (element->getPageIndex() == pageIndex) { element->performManipulation(m_manipulationModes[element->getElementId()], offset); } } + m_lastUpdatedPoint = currentPoint; emit stateChanged(); } @@ -1345,4 +1441,27 @@ void PDFPageContentElementManipulator::cancelManipulation() Q_ASSERT(m_manipulationModes.empty()); } +void PDFPageContentElementManipulator::drawPage(QPainter* painter, + PDFInteger pageIndex, + const PDFPrecompiledPage* compiledPage, + PDFTextLayoutGetter& layoutGetter, + const QMatrix& pagePointToDevicePointMatrix, + QList& errors) const +{ + // Draw selection + + + // Draw dragged items + if (isManipulationInProgress()) + { + PDFPainterStateGuard guard(painter); + painter->setOpacity(0.2); + + for (const auto& manipulatedElement : m_manipulatedElements) + { + manipulatedElement->drawPage(painter, pageIndex, compiledPage, layoutGetter, pagePointToDevicePointMatrix, errors); + } + } +} + } // namespace pdf diff --git a/Pdf4QtLib/sources/pdfpagecontentelements.h b/Pdf4QtLib/sources/pdfpagecontentelements.h index db6f30f..e1af638 100644 --- a/Pdf4QtLib/sources/pdfpagecontentelements.h +++ b/Pdf4QtLib/sources/pdfpagecontentelements.h @@ -68,6 +68,10 @@ public: PDFInteger getElementId() const; void setElementId(PDFInteger newElementId); + /// Returns cursor shape for manipulation mode + /// \param mode Manipulation mode + static Qt::CursorShape getCursorShapeForManipulationMode(uint mode); + protected: enum ManipulationModes : uint @@ -330,6 +334,13 @@ public: void cancelManipulation(); + void drawPage(QPainter* painter, + PDFInteger pageIndex, + const PDFPrecompiledPage* compiledPage, + PDFTextLayoutGetter& layoutGetter, + const QMatrix& pagePointToDevicePointMatrix, + QList& errors) const; + signals: void selectionChanged(); void stateChanged(); @@ -407,7 +418,7 @@ public: signals: /// This signal is emitted when scene has changed (including graphics) - void sceneChanged(); + void sceneChanged(bool graphicsOnly); private: @@ -448,6 +459,11 @@ private: /// \param event Mouse event void ungrabMouse(const MouseEventInfo& info, QMouseEvent* event); + /// Updates mouse cursor + /// \param info Mouse event info + /// \param snapPointDistanceTreshold Snap point threshold + void updateMouseCursor(const MouseEventInfo& info, PDFReal snapPointDistanceThreshold); + PDFInteger m_firstFreeId; bool m_isActive; PDFWidget* m_widget; diff --git a/Pdf4QtViewerPlugins/SignaturePlugin/signatureplugin.cpp b/Pdf4QtViewerPlugins/SignaturePlugin/signatureplugin.cpp index d7c8e9e..4b08bbf 100644 --- a/Pdf4QtViewerPlugins/SignaturePlugin/signatureplugin.cpp +++ b/Pdf4QtViewerPlugins/SignaturePlugin/signatureplugin.cpp @@ -170,9 +170,13 @@ std::vector SignaturePlugin::getActions() const return result; } -void SignaturePlugin::onSceneChanged() +void SignaturePlugin::onSceneChanged(bool graphicsOnly) { - updateActions(); + if (!graphicsOnly) + { + updateActions(); + } + updateGraphics(); } diff --git a/Pdf4QtViewerPlugins/SignaturePlugin/signatureplugin.h b/Pdf4QtViewerPlugins/SignaturePlugin/signatureplugin.h index af1c7ef..997d6ae 100644 --- a/Pdf4QtViewerPlugins/SignaturePlugin/signatureplugin.h +++ b/Pdf4QtViewerPlugins/SignaturePlugin/signatureplugin.h @@ -48,7 +48,7 @@ public: virtual std::vector getActions() const override; private: - void onSceneChanged(); + void onSceneChanged(bool graphicsOnly); enum Action { From a401b8c031444de2188dace257d41282a07d37ef Mon Sep 17 00:00:00 2001 From: Jakub Melka Date: Tue, 8 Mar 2022 19:36:52 +0100 Subject: [PATCH 11/39] Signature plugin: selection --- Pdf4QtLib/sources/pdfpagecontentelements.cpp | 71 +++++++++++++++++++- Pdf4QtLib/sources/pdfpagecontentelements.h | 12 +++- 2 files changed, 80 insertions(+), 3 deletions(-) diff --git a/Pdf4QtLib/sources/pdfpagecontentelements.cpp b/Pdf4QtLib/sources/pdfpagecontentelements.cpp index 2db90ab..2aa7da9 100644 --- a/Pdf4QtLib/sources/pdfpagecontentelements.cpp +++ b/Pdf4QtLib/sources/pdfpagecontentelements.cpp @@ -296,6 +296,11 @@ void PDFPageContentElementRectangle::performManipulation(uint mode, const QPoint performRectangleManipulation(m_rectangle, mode, offset); } +QRectF PDFPageContentElementRectangle::getBoundingBox() const +{ + return getRectangle(); +} + PDFPageContentScene::PDFPageContentScene(QObject* parent) : QObject(parent), m_firstFreeId(1), @@ -303,7 +308,7 @@ PDFPageContentScene::PDFPageContentScene(QObject* parent) : m_widget(nullptr), m_manipulator(this, nullptr) { - + connect(&m_manipulator, &PDFPageContentElementManipulator::selectionChanged, this, &PDFPageContentScene::onSelectionChanged); } PDFPageContentScene::~PDFPageContentScene() @@ -724,6 +729,11 @@ void PDFPageContentScene::updateMouseCursor(const MouseEventInfo& info, PDFReal } } +void PDFPageContentScene::onSelectionChanged() +{ + emit sceneChanged(true); +} + PDFWidget* PDFPageContentScene::widget() const { return m_widget; @@ -820,7 +830,7 @@ uint PDFPageContentElementLine::getManipulationMode(const QPointF& point, QPointF vl = m_line.p2() - m_line.p1(); QPointF vp = point - m_line.p1(); - const qreal lengthSquared = QPointF::dotProduct(vp, vp); + const qreal lengthSquared = QPointF::dotProduct(vl, vl); if (qFuzzyIsNull(lengthSquared)) { @@ -865,6 +875,20 @@ void PDFPageContentElementLine::performManipulation(uint mode, const QPointF& of } } +QRectF PDFPageContentElementLine::getBoundingBox() const +{ + if (!qFuzzyIsNull(m_line.length())) + { + const qreal xMin = qMin(m_line.p1().x(), m_line.p2().x()); + const qreal xMax = qMax(m_line.p1().x(), m_line.p2().x()); + const qreal yMin = qMin(m_line.p1().y(), m_line.p2().y()); + const qreal yMax = qMax(m_line.p1().y(), m_line.p2().y()); + return QRectF(xMin, yMin, xMax - xMin, yMax - yMin); + } + + return QRectF(); +} + PDFPageContentElementLine::LineGeometry PDFPageContentElementLine::getGeometry() const { return m_geometry; @@ -965,6 +989,11 @@ void PDFPageContentSvgElement::performManipulation(uint mode, const QPointF& off performRectangleManipulation(m_rectangle, mode, offset); } +QRectF PDFPageContentSvgElement::getBoundingBox() const +{ + return getRectangle(); +} + const QByteArray& PDFPageContentSvgElement::getContent() const { return m_content; @@ -1052,6 +1081,11 @@ void PDFPageContentElementDot::performManipulation(uint mode, const QPointF& off } } +QRectF PDFPageContentElementDot::getBoundingBox() const +{ + return QRectF(m_point, QSizeF(0.001, 0.001)); +} + QPointF PDFPageContentElementDot::getPoint() const { return m_point; @@ -1132,6 +1166,11 @@ void PDFPageContentElementFreehandCurve::performManipulation(uint mode, const QP } } +QRectF PDFPageContentElementFreehandCurve::getBoundingBox() const +{ + return m_curve.controlPointRect(); +} + QPainterPath PDFPageContentElementFreehandCurve::getCurve() const { return m_curve; @@ -1449,7 +1488,35 @@ void PDFPageContentElementManipulator::drawPage(QPainter* painter, QList& errors) const { // Draw selection + if (!isSelectionEmpty()) + { + QPainterPath selectionPath; + for (const PDFInteger id : m_selection) + { + if (PDFPageContentElement* element = m_scene->getElementById(id)) + { + QPainterPath tempPath; + tempPath.addRect(element->getBoundingBox()); + selectionPath = selectionPath.united(tempPath); + } + } + if (!selectionPath.isEmpty()) + { + PDFPainterStateGuard guard(painter); + QPen pen(Qt::SolidLine); + pen.setWidthF(2.0); + pen.setColor(QColor::fromRgbF(0.8, 0.8, 0.1, 0.7)); + QBrush brush(Qt::SolidPattern); + brush.setColor(QColor::fromRgbF(1.0, 1.0, 0.0, 0.2)); + + painter->setPen(std::move(pen)); + painter->setBrush(std::move(brush)); + + selectionPath = pagePointToDevicePointMatrix.map(selectionPath); + painter->drawPath(selectionPath); + } + } // Draw dragged items if (isManipulationInProgress()) diff --git a/Pdf4QtLib/sources/pdfpagecontentelements.h b/Pdf4QtLib/sources/pdfpagecontentelements.h index e1af638..40e7f12 100644 --- a/Pdf4QtLib/sources/pdfpagecontentelements.h +++ b/Pdf4QtLib/sources/pdfpagecontentelements.h @@ -62,6 +62,9 @@ public: /// \param offset Offset virtual void performManipulation(uint mode, const QPointF& offset) = 0; + /// Returns bounding box of the element + virtual QRectF getBoundingBox() const = 0; + PDFInteger getPageIndex() const; void setPageIndex(PDFInteger newPageIndex); @@ -143,6 +146,7 @@ public: PDFReal snapPointDistanceThreshold) const override; virtual void performManipulation(uint mode, const QPointF& offset) override; + virtual QRectF getBoundingBox() const override; private: bool m_rounded = false; @@ -174,6 +178,7 @@ public: PDFReal snapPointDistanceThreshold) const override; virtual void performManipulation(uint mode, const QPointF& offset) override; + virtual QRectF getBoundingBox() const override; LineGeometry getGeometry() const; void setGeometry(LineGeometry newGeometry); @@ -204,7 +209,7 @@ public: PDFReal snapPointDistanceThreshold) const override; virtual void performManipulation(uint mode, const QPointF& offset) override; - + virtual QRectF getBoundingBox() const override; QPointF getPoint() const; void setPoint(QPointF newPoint); @@ -231,6 +236,7 @@ public: PDFReal snapPointDistanceThreshold) const override; virtual void performManipulation(uint mode, const QPointF& offset) override; + virtual QRectF getBoundingBox() const override; QPainterPath getCurve() const; void setCurve(QPainterPath newCurve); @@ -263,6 +269,7 @@ public: PDFReal snapPointDistanceThreshold) const override; virtual void performManipulation(uint mode, const QPointF& offset) override; + virtual QRectF getBoundingBox() const override; const QByteArray& getContent() const; void setContent(const QByteArray& newContent); @@ -464,6 +471,9 @@ private: /// \param snapPointDistanceTreshold Snap point threshold void updateMouseCursor(const MouseEventInfo& info, PDFReal snapPointDistanceThreshold); + /// Reaction on selection changed + void onSelectionChanged(); + PDFInteger m_firstFreeId; bool m_isActive; PDFWidget* m_widget; From 2b7d5939bd57c9115705939398f557913f73dda8 Mon Sep 17 00:00:00 2001 From: Jakub Melka Date: Wed, 9 Mar 2022 20:42:05 +0100 Subject: [PATCH 12/39] Signature plugin: Keypress handling --- Pdf4QtLib/sources/pdfpagecontentelements.cpp | 66 +++++++++++++++++++- Pdf4QtLib/sources/pdfpagecontentelements.h | 6 +- 2 files changed, 69 insertions(+), 3 deletions(-) diff --git a/Pdf4QtLib/sources/pdfpagecontentelements.cpp b/Pdf4QtLib/sources/pdfpagecontentelements.cpp index 2aa7da9..33baf1e 100644 --- a/Pdf4QtLib/sources/pdfpagecontentelements.cpp +++ b/Pdf4QtLib/sources/pdfpagecontentelements.cpp @@ -361,13 +361,57 @@ void PDFPageContentScene::clear() void PDFPageContentScene::shortcutOverrideEvent(QWidget* widget, QKeyEvent* event) { Q_UNUSED(widget); - event->ignore(); + + constexpr QKeySequence::StandardKey acceptedKeys[] = { QKeySequence::Delete, + QKeySequence::SelectAll, + QKeySequence::Deselect, + QKeySequence::Cancel }; + + if (std::any_of(std::begin(acceptedKeys), std::end(acceptedKeys), [event](QKeySequence::StandardKey standardKey) { return event == standardKey; })) + { + event->accept(); + return; + } } void PDFPageContentScene::keyPressEvent(QWidget* widget, QKeyEvent* event) { Q_UNUSED(widget); event->ignore(); + + if (event == QKeySequence::Delete) + { + if (!m_manipulator.isSelectionEmpty()) + { + m_manipulator.performDeleteSelection(); + event->accept(); + } + } + else if (event == QKeySequence::SelectAll) + { + if (!isEmpty()) + { + m_manipulator.selectAll(); + event->accept(); + } + } + else if (event == QKeySequence::Deselect) + { + if (!m_manipulator.isSelectionEmpty()) + { + m_manipulator.deselectAll(); + event->accept(); + } + } + else if (event == QKeySequence::Cancel) + { + if (m_manipulator.isManipulationInProgress()) + { + m_manipulator.cancelManipulation(); + m_manipulator.deselectAll(); + event->accept(); + } + } } void PDFPageContentScene::keyReleaseEvent(QWidget* widget, QKeyEvent* event) @@ -765,6 +809,18 @@ void PDFPageContentScene::setActive(bool newIsActive) } } +std::set PDFPageContentScene::getElementIds() const +{ + std::set result; + + for (const auto& element : m_elements) + { + result.insert(element->getElementId()); + } + + return result; +} + void PDFPageContentScene::removeElementsById(const std::set& selection) { const size_t oldSize = m_elements.size(); @@ -1356,6 +1412,12 @@ void PDFPageContentElementManipulator::deselect(const std::set& ids) update(ids, Deselect); } +void PDFPageContentElementManipulator::selectAll() +{ + std::set ids = m_scene->getElementIds(); + update(ids, Select); +} + void PDFPageContentElementManipulator::deselectAll() { update(-1, Clear); @@ -1377,7 +1439,7 @@ bool PDFPageContentElementManipulator::isManipulationAllowed(PDFInteger pageInde return false; } -void PDFPageContentElementManipulator::manipulateDeleteSelection() +void PDFPageContentElementManipulator::performDeleteSelection() { cancelManipulation(); m_scene->removeElementsById(m_selection); diff --git a/Pdf4QtLib/sources/pdfpagecontentelements.h b/Pdf4QtLib/sources/pdfpagecontentelements.h index 40e7f12..5833220 100644 --- a/Pdf4QtLib/sources/pdfpagecontentelements.h +++ b/Pdf4QtLib/sources/pdfpagecontentelements.h @@ -318,12 +318,13 @@ public: void selectNew(const std::set& ids); void deselect(PDFInteger id); void deselect(const std::set& ids); + void selectAll(); void deselectAll(); bool isManipulationAllowed(PDFInteger pageIndex) const; bool isManipulationInProgress() const { return m_isManipulationInProgress; } - void manipulateDeleteSelection(); + void performDeleteSelection(); void startManipulation(PDFInteger pageIndex, const QPointF& startPoint, @@ -394,6 +395,9 @@ public: bool isActive() const; void setActive(bool newIsActive); + /// Returns set of all element ids + std::set getElementIds() const; + /// Removes elements specified in selection /// \param selection Items to be removed void removeElementsById(const std::set& selection); From 5163bd1c803144868f61d5bffa42c41dad6bcf1d Mon Sep 17 00:00:00 2001 From: Jakub Melka Date: Sun, 13 Mar 2022 19:37:36 +0100 Subject: [PATCH 13/39] Signature plugin: manipulation operations --- .../sources/pdfpagecontenteditorwidget.cpp | 36 ++ .../sources/pdfpagecontenteditorwidget.h | 4 + .../sources/pdfpagecontenteditorwidget.ui | 64 ++-- Pdf4QtLib/sources/pdfpagecontentelements.cpp | 317 +++++++++++++++++- Pdf4QtLib/sources/pdfpagecontentelements.h | 58 +++- .../SignaturePlugin/signatureplugin.cpp | 1 + 6 files changed, 433 insertions(+), 47 deletions(-) diff --git a/Pdf4QtLib/sources/pdfpagecontenteditorwidget.cpp b/Pdf4QtLib/sources/pdfpagecontenteditorwidget.cpp index f46a1ff..11d129f 100644 --- a/Pdf4QtLib/sources/pdfpagecontenteditorwidget.cpp +++ b/Pdf4QtLib/sources/pdfpagecontenteditorwidget.cpp @@ -18,6 +18,7 @@ #include "pdfpagecontenteditorwidget.h" #include "ui_pdfpagecontenteditorwidget.h" #include "pdfwidgetutils.h" +#include "pdfpagecontentelements.h" #include #include @@ -39,7 +40,42 @@ PDFPageContentEditorWidget::PDFPageContentEditorWidget(QWidget *parent) : button->setIconSize(m_toolButtonIconSize); } + m_operationMapper.setMapping(ui->alignVertTopButton, static_cast(PDFPageContentElementManipulator::Operation::AlignTop)); + m_operationMapper.setMapping(ui->alignVertMiddleButton, static_cast(PDFPageContentElementManipulator::Operation::AlignCenterVertically)); + m_operationMapper.setMapping(ui->alignVertBottomButton, static_cast(PDFPageContentElementManipulator::Operation::AlignBottom)); + m_operationMapper.setMapping(ui->alignHorLeftButton, static_cast(PDFPageContentElementManipulator::Operation::AlignLeft)); + m_operationMapper.setMapping(ui->alignHorMiddleButton, static_cast(PDFPageContentElementManipulator::Operation::AlignCenterHorizontally)); + m_operationMapper.setMapping(ui->alignHorRightButton, static_cast(PDFPageContentElementManipulator::Operation::AlignRight)); + m_operationMapper.setMapping(ui->setSameWidthButton, static_cast(PDFPageContentElementManipulator::Operation::SetSameWidth)); + m_operationMapper.setMapping(ui->setSameHeightButton, static_cast(PDFPageContentElementManipulator::Operation::SetSameHeight)); + m_operationMapper.setMapping(ui->setSameSizeButton, static_cast(PDFPageContentElementManipulator::Operation::SetSameSize)); + m_operationMapper.setMapping(ui->centerHorizontallyButton, static_cast(PDFPageContentElementManipulator::Operation::CenterHorizontally)); + m_operationMapper.setMapping(ui->centerVerticallyButton, static_cast(PDFPageContentElementManipulator::Operation::CenterVertically)); + m_operationMapper.setMapping(ui->centerRectButton, static_cast(PDFPageContentElementManipulator::Operation::CenterHorAndVert)); + m_operationMapper.setMapping(ui->layoutHorizontallyButton, static_cast(PDFPageContentElementManipulator::Operation::LayoutHorizontally)); + m_operationMapper.setMapping(ui->layoutVerticallyButton, static_cast(PDFPageContentElementManipulator::Operation::LayoutVertically)); + m_operationMapper.setMapping(ui->layoutFormButton, static_cast(PDFPageContentElementManipulator::Operation::LayoutForm)); + m_operationMapper.setMapping(ui->layoutGridButton, static_cast(PDFPageContentElementManipulator::Operation::LayoutGrid)); + + connect(ui->alignVertTopButton, &QToolButton::clicked, &m_operationMapper, QOverload<>::of(&QSignalMapper::map)); + connect(ui->alignVertMiddleButton, &QToolButton::clicked, &m_operationMapper, QOverload<>::of(&QSignalMapper::map)); + connect(ui->alignVertBottomButton, &QToolButton::clicked, &m_operationMapper, QOverload<>::of(&QSignalMapper::map)); + connect(ui->alignHorLeftButton, &QToolButton::clicked, &m_operationMapper, QOverload<>::of(&QSignalMapper::map)); + connect(ui->alignHorMiddleButton, &QToolButton::clicked, &m_operationMapper, QOverload<>::of(&QSignalMapper::map)); + connect(ui->alignHorRightButton, &QToolButton::clicked, &m_operationMapper, QOverload<>::of(&QSignalMapper::map)); + connect(ui->setSameWidthButton, &QToolButton::clicked, &m_operationMapper, QOverload<>::of(&QSignalMapper::map)); + connect(ui->setSameHeightButton, &QToolButton::clicked, &m_operationMapper, QOverload<>::of(&QSignalMapper::map)); + connect(ui->setSameSizeButton, &QToolButton::clicked, &m_operationMapper, QOverload<>::of(&QSignalMapper::map)); + connect(ui->centerHorizontallyButton, &QToolButton::clicked, &m_operationMapper, QOverload<>::of(&QSignalMapper::map)); + connect(ui->centerVerticallyButton, &QToolButton::clicked, &m_operationMapper, QOverload<>::of(&QSignalMapper::map)); + connect(ui->centerRectButton, &QToolButton::clicked, &m_operationMapper, QOverload<>::of(&QSignalMapper::map)); + connect(ui->layoutHorizontallyButton, &QToolButton::clicked, &m_operationMapper, QOverload<>::of(&QSignalMapper::map)); + connect(ui->layoutVerticallyButton, &QToolButton::clicked, &m_operationMapper, QOverload<>::of(&QSignalMapper::map)); + connect(ui->layoutFormButton, &QToolButton::clicked, &m_operationMapper, QOverload<>::of(&QSignalMapper::map)); + connect(ui->layoutGridButton, &QToolButton::clicked, &m_operationMapper, QOverload<>::of(&QSignalMapper::map)); + connect(&m_actionMapper, &QSignalMapper::mappedObject, this, &PDFPageContentEditorWidget::onActionTriggerRequest); + connect(&m_operationMapper, &QSignalMapper::mappedInt, this, &PDFPageContentEditorWidget::operationTriggered); } PDFPageContentEditorWidget::~PDFPageContentEditorWidget() diff --git a/Pdf4QtLib/sources/pdfpagecontenteditorwidget.h b/Pdf4QtLib/sources/pdfpagecontenteditorwidget.h index b2dbbd6..19a6d9f 100644 --- a/Pdf4QtLib/sources/pdfpagecontenteditorwidget.h +++ b/Pdf4QtLib/sources/pdfpagecontenteditorwidget.h @@ -42,12 +42,16 @@ public: /// Adds external action to the tool box void addAction(QAction* action); +signals: + void operationTriggered(int operation); + private: void onActionTriggerRequest(QObject* actionObject); void onActionChanged(); Ui::PDFPageContentEditorWidget* ui; QSignalMapper m_actionMapper; + QSignalMapper m_operationMapper; int m_toolBoxColumnCount; QSize m_toolButtonIconSize; }; diff --git a/Pdf4QtLib/sources/pdfpagecontenteditorwidget.ui b/Pdf4QtLib/sources/pdfpagecontenteditorwidget.ui index b74af84..3f9e52f 100644 --- a/Pdf4QtLib/sources/pdfpagecontenteditorwidget.ui +++ b/Pdf4QtLib/sources/pdfpagecontenteditorwidget.ui @@ -34,85 +34,85 @@ - - ... + + Align to Top - - ... + + Align to Vertical Center - - ... + + Align to Bottom - - ... + + Set Same Width - - ... + + Set Same Height - - ... + + Set Same Size - - ... + + Align to Left - - ... + + Align to Horizontal Center - - ... + + Align to Right - - ... + + Center Horizontally - - ... + + Center Vertically - - ... + + Center to Page Media Box @@ -127,29 +127,29 @@ - - ... + + Make Horizontal Layout - - ... + + Make Vertical Layout - - ... + + Make Form Layout - - ... + + Make Grid Layout diff --git a/Pdf4QtLib/sources/pdfpagecontentelements.cpp b/Pdf4QtLib/sources/pdfpagecontentelements.cpp index 33baf1e..1e8e1d7 100644 --- a/Pdf4QtLib/sources/pdfpagecontentelements.cpp +++ b/Pdf4QtLib/sources/pdfpagecontentelements.cpp @@ -199,6 +199,13 @@ void PDFPageContentElement::performRectangleManipulation(QRectF& rectangle, } } +void PDFPageContentElement::performRectangleSetSize(QRectF& rectangle, QSizeF size) +{ + const qreal offset = rectangle.size().height() - size.height(); + rectangle.setSize(size); + rectangle.translate(0, offset); +} + const QPen& PDFPageContentStyledElement::getPen() const { return m_pen; @@ -301,6 +308,11 @@ QRectF PDFPageContentElementRectangle::getBoundingBox() const return getRectangle(); } +void PDFPageContentElementRectangle::setSize(QSizeF size) +{ + performRectangleSetSize(m_rectangle, size); +} + PDFPageContentScene::PDFPageContentScene(QObject* parent) : QObject(parent), m_firstFreeId(1), @@ -353,6 +365,7 @@ void PDFPageContentScene::clear() { if (!m_elements.empty()) { + m_manipulator.reset(); m_elements.clear(); emit sceneChanged(false); } @@ -821,10 +834,10 @@ std::set PDFPageContentScene::getElementIds() const return result; } -void PDFPageContentScene::removeElementsById(const std::set& selection) +void PDFPageContentScene::removeElementsById(const std::vector& selection) { const size_t oldSize = m_elements.size(); - m_elements.erase(std::remove_if(m_elements.begin(), m_elements.end(), [&selection](const auto& element){ return selection.count(element->getElementId()); }), m_elements.end()); + m_elements.erase(std::remove_if(m_elements.begin(), m_elements.end(), [&selection](const auto& element){ return std::find(selection.cbegin(), selection.cend(), element->getElementId()) != selection.cend(); }), m_elements.end()); const size_t newSize = m_elements.size(); if (newSize < oldSize) @@ -833,6 +846,21 @@ void PDFPageContentScene::removeElementsById(const std::set& selecti } } +void PDFPageContentScene::performOperation(int operation) +{ + m_manipulator.performOperation(static_cast(operation)); +} + +const PDFDocument* PDFPageContentScene::getDocument() const +{ + if (m_widget) + { + return m_widget->getDrawWidgetProxy()->getDocument(); + } + + return nullptr; +} + PDFPageContentElementLine* PDFPageContentElementLine::clone() const { PDFPageContentElementLine* copy = new PDFPageContentElementLine(); @@ -945,6 +973,32 @@ QRectF PDFPageContentElementLine::getBoundingBox() const return QRectF(); } +void PDFPageContentElementLine::setSize(QSizeF size) +{ + QPointF p1 = m_line.p1(); + QPointF p2 = m_line.p2(); + + if (p1.x() < p2.x()) + { + p2.setX(p1.x() + size.width()); + } + else + { + p1.setX(p2.x() + size.width()); + } + + if (p1.y() < p2.y()) + { + p1.setY(p2.y() - size.height()); + } + else + { + p2.setY(p1.y() - size.height()); + } + + m_line.setPoints(p1, p2); +} + PDFPageContentElementLine::LineGeometry PDFPageContentElementLine::getGeometry() const { return m_geometry; @@ -1050,6 +1104,11 @@ QRectF PDFPageContentSvgElement::getBoundingBox() const return getRectangle(); } +void PDFPageContentSvgElement::setSize(QSizeF size) +{ + performRectangleSetSize(m_rectangle, size); +} + const QByteArray& PDFPageContentSvgElement::getContent() const { return m_content; @@ -1142,6 +1201,11 @@ QRectF PDFPageContentElementDot::getBoundingBox() const return QRectF(m_point, QSizeF(0.001, 0.001)); } +void PDFPageContentElementDot::setSize(QSizeF size) +{ + Q_UNUSED(size); +} + QPointF PDFPageContentElementDot::getPoint() const { return m_point; @@ -1227,6 +1291,11 @@ QRectF PDFPageContentElementFreehandCurve::getBoundingBox() const return m_curve.controlPointRect(); } +void PDFPageContentElementFreehandCurve::setSize(QSizeF size) +{ + Q_UNUSED(size); +} + QPainterPath PDFPageContentElementFreehandCurve::getCurve() const { return m_curve; @@ -1261,6 +1330,11 @@ PDFPageContentElementManipulator::PDFPageContentElementManipulator(PDFPageConten } +bool PDFPageContentElementManipulator::isSelected(PDFInteger id) const +{ + return std::find(m_selection.cbegin(), m_selection.cend(), id) != m_selection.cend(); +} + void PDFPageContentElementManipulator::reset() { cancelManipulation(); @@ -1285,7 +1359,7 @@ void PDFPageContentElementManipulator::update(PDFInteger id, SelectionModes mode if (!isSelected(id)) { modified = true; - m_selection.insert(id); + m_selection.push_back(id); } } @@ -1294,7 +1368,7 @@ void PDFPageContentElementManipulator::update(PDFInteger id, SelectionModes mode if (isSelected(id)) { modified = true; - m_selection.erase(id); + eraseSelectedElementById(id); } } @@ -1302,11 +1376,11 @@ void PDFPageContentElementManipulator::update(PDFInteger id, SelectionModes mode { if (isSelected(id)) { - m_selection.erase(id); + eraseSelectedElementById(id); } else { - m_selection.insert(id); + m_selection.push_back(id); } // When toggle is performed, selection is always changed @@ -1340,7 +1414,7 @@ void PDFPageContentElementManipulator::update(const std::set& ids, S if (!isSelected(id)) { modified = true; - m_selection.insert(id); + m_selection.push_back(id); } } } @@ -1352,7 +1426,7 @@ void PDFPageContentElementManipulator::update(const std::set& ids, S if (isSelected(id)) { modified = true; - m_selection.erase(id); + eraseSelectedElementById(id); } } } @@ -1363,11 +1437,11 @@ void PDFPageContentElementManipulator::update(const std::set& ids, S { if (isSelected(id)) { - m_selection.erase(id); + eraseSelectedElementById(id); } else { - m_selection.insert(id); + m_selection.push_back(id); } // When toggle is performed, selection is always changed @@ -1439,6 +1513,185 @@ bool PDFPageContentElementManipulator::isManipulationAllowed(PDFInteger pageInde return false; } +void PDFPageContentElementManipulator::performOperation(Operation operation) +{ + if (isSelectionEmpty()) + { + // Jakub Melka: nothing selected + return; + } + + QRectF representativeRect = getSelectionBoundingRect(); + std::vector manipulatedElements; + for (const PDFInteger id : m_selection) + { + const PDFPageContentElement* element = m_scene->getElementById(id); + manipulatedElements.push_back(element->clone()); + } + + switch (operation) + { + case Operation::AlignTop: + { + for (PDFPageContentElement* element : manipulatedElements) + { + QRectF boundingBox = element->getBoundingBox(); + const qreal offset = representativeRect.bottom() - boundingBox.bottom(); + element->performManipulation(PDFPageContentElement::Translate, QPointF(0.0, offset)); + } + break; + } + + case Operation::AlignCenterVertically: + { + for (PDFPageContentElement* element : manipulatedElements) + { + QRectF boundingBox = element->getBoundingBox(); + const qreal offset = representativeRect.center().y() - boundingBox.center().y(); + element->performManipulation(PDFPageContentElement::Translate, QPointF(0.0, offset)); + } + break; + } + + case Operation::AlignBottom: + { + for (PDFPageContentElement* element : manipulatedElements) + { + QRectF boundingBox = element->getBoundingBox(); + const qreal offset = representativeRect.top() - boundingBox.top(); + element->performManipulation(PDFPageContentElement::Translate, QPointF(0.0, offset)); + } + break; + } + + case Operation::AlignLeft: + { + for (PDFPageContentElement* element : manipulatedElements) + { + QRectF boundingBox = element->getBoundingBox(); + const qreal offset = representativeRect.left() - boundingBox.left(); + element->performManipulation(PDFPageContentElement::Translate, QPointF(offset, 0.0)); + } + break; + } + + case Operation::AlignCenterHorizontally: + { + for (PDFPageContentElement* element : manipulatedElements) + { + QRectF boundingBox = element->getBoundingBox(); + const qreal offset = representativeRect.center().x() - boundingBox.center().x(); + element->performManipulation(PDFPageContentElement::Translate, QPointF(offset, 0.0)); + } + break; + } + + case Operation::AlignRight: + { + for (PDFPageContentElement* element : manipulatedElements) + { + QRectF boundingBox = element->getBoundingBox(); + const qreal offset = representativeRect.right() - boundingBox.right(); + element->performManipulation(PDFPageContentElement::Translate, QPointF(offset, 0.0)); + } + break; + } + + case Operation::SetSameHeight: + { + QSizeF size = manipulatedElements.front()->getBoundingBox().size(); + + for (PDFPageContentElement* element : manipulatedElements) + { + size.setWidth(element->getBoundingBox().width()); + element->setSize(size); + } + break; + } + + case Operation::SetSameWidth: + { + QSizeF size = manipulatedElements.front()->getBoundingBox().size(); + + for (PDFPageContentElement* element : manipulatedElements) + { + size.setHeight(element->getBoundingBox().height()); + element->setSize(size); + } + break; + } + + case Operation::SetSameSize: + { + QSizeF size = manipulatedElements.front()->getBoundingBox().size(); + + for (PDFPageContentElement* element : manipulatedElements) + { + element->setSize(size); + } + break; + } + + case Operation::CenterHorizontally: + { + const PDFInteger pageIndex = manipulatedElements.front()->getPageIndex(); + QRectF pageMediaBox = getPageMediaBox(pageIndex); + const qreal offset = pageMediaBox.center().x() - representativeRect.center().x(); + + for (PDFPageContentElement* element : manipulatedElements) + { + element->performManipulation(PDFPageContentElement::Translate, QPointF(offset, 0.0)); + } + break; + } + + case Operation::CenterVertically: + { + const PDFInteger pageIndex = manipulatedElements.front()->getPageIndex(); + QRectF pageMediaBox = getPageMediaBox(pageIndex); + const qreal offset = pageMediaBox.center().y() - representativeRect.center().y(); + + for (PDFPageContentElement* element : manipulatedElements) + { + element->performManipulation(PDFPageContentElement::Translate, QPointF(0.0, offset)); + } + break; + } + + case Operation::CenterHorAndVert: + { + const PDFInteger pageIndex = manipulatedElements.front()->getPageIndex(); + QRectF pageMediaBox = getPageMediaBox(pageIndex); + const qreal offsetX = pageMediaBox.center().x() - representativeRect.center().x(); + const qreal offsetY = pageMediaBox.center().y() - representativeRect.center().y(); + QPointF offset(offsetX, offsetY); + + for (PDFPageContentElement* element : manipulatedElements) + { + element->performManipulation(PDFPageContentElement::Translate, offset); + } + break; + } + + case Operation::LayoutVertically: + break; + + case Operation::LayoutHorizontally: + break; + + case Operation::LayoutForm: + break; + + case Operation::LayoutGrid: + break; + } + + for (PDFPageContentElement* element : manipulatedElements) + { + m_scene->replaceElement(element); + } +} + void PDFPageContentElementManipulator::performDeleteSelection() { cancelManipulation(); @@ -1593,4 +1846,48 @@ void PDFPageContentElementManipulator::drawPage(QPainter* painter, } } +QRectF PDFPageContentElementManipulator::getSelectionBoundingRect() const +{ + QRectF rect; + + for (const PDFInteger elementId : m_selection) + { + if (const PDFPageContentElement* element = m_scene->getElementById(elementId)) + { + rect = rect.united(element->getBoundingBox()); + } + } + + return rect; +} + +QRectF PDFPageContentElementManipulator::getPageMediaBox(PDFInteger pageIndex) const +{ + if (pageIndex < 0) + { + return QRectF(); + } + + if (const PDFDocument* document = m_scene->getDocument()) + { + size_t pageCount = document->getCatalog()->getPageCount(); + if (size_t(pageIndex) < pageCount) + { + const PDFPage* page = document->getCatalog()->getPage(pageIndex); + return page->getMediaBox(); + } + } + + return QRectF(); +} + +void PDFPageContentElementManipulator::eraseSelectedElementById(PDFInteger id) +{ + auto it = std::find(m_selection.begin(), m_selection.end(), id); + if (it != m_selection.end()) + { + m_selection.erase(it); + } +} + } // namespace pdf diff --git a/Pdf4QtLib/sources/pdfpagecontentelements.h b/Pdf4QtLib/sources/pdfpagecontentelements.h index 5833220..caa291e 100644 --- a/Pdf4QtLib/sources/pdfpagecontentelements.h +++ b/Pdf4QtLib/sources/pdfpagecontentelements.h @@ -32,6 +32,7 @@ class QSvgRenderer; namespace pdf { class PDFWidget; +class PDFDocument; class PDFPageContentScene; class PDF4QTLIBSHARED_EXPORT PDFPageContentElement @@ -65,6 +66,10 @@ public: /// Returns bounding box of the element virtual QRectF getBoundingBox() const = 0; + /// Sets size to the elements that supports it. Does + /// nothing for elements, which does not support it. + virtual void setSize(QSizeF size) = 0; + PDFInteger getPageIndex() const; void setPageIndex(PDFInteger newPageIndex); @@ -75,8 +80,6 @@ public: /// \param mode Manipulation mode static Qt::CursorShape getCursorShapeForManipulationMode(uint mode); -protected: - enum ManipulationModes : uint { None = 0, @@ -93,6 +96,7 @@ protected: Pt2 }; +protected: uint getRectangleManipulationMode(const QRectF& rectangle, const QPointF& point, PDFReal snapPointDistanceThreshold) const; @@ -101,6 +105,8 @@ protected: uint mode, const QPointF& offset); + void performRectangleSetSize(QRectF& rectangle, QSizeF size); + PDFInteger m_elementId = -1; PDFInteger m_pageIndex = -1; }; @@ -147,6 +153,7 @@ public: virtual void performManipulation(uint mode, const QPointF& offset) override; virtual QRectF getBoundingBox() const override; + virtual void setSize(QSizeF size); private: bool m_rounded = false; @@ -179,6 +186,7 @@ public: virtual void performManipulation(uint mode, const QPointF& offset) override; virtual QRectF getBoundingBox() const override; + virtual void setSize(QSizeF size); LineGeometry getGeometry() const; void setGeometry(LineGeometry newGeometry); @@ -210,6 +218,7 @@ public: virtual void performManipulation(uint mode, const QPointF& offset) override; virtual QRectF getBoundingBox() const override; + virtual void setSize(QSizeF size); QPointF getPoint() const; void setPoint(QPointF newPoint); @@ -237,6 +246,7 @@ public: virtual void performManipulation(uint mode, const QPointF& offset) override; virtual QRectF getBoundingBox() const override; + virtual void setSize(QSizeF size); QPainterPath getCurve() const; void setCurve(QPainterPath newCurve); @@ -270,6 +280,7 @@ public: virtual void performManipulation(uint mode, const QPointF& offset) override; virtual QRectF getBoundingBox() const override; + virtual void setSize(QSizeF size); const QByteArray& getContent() const; void setContent(const QByteArray& newContent); @@ -290,6 +301,26 @@ class PDF4QTLIBSHARED_EXPORT PDFPageContentElementManipulator : public QObject public: PDFPageContentElementManipulator(PDFPageContentScene* scene, QObject* parent); + enum class Operation + { + AlignTop, + AlignCenterVertically, + AlignBottom, + AlignLeft, + AlignCenterHorizontally, + AlignRight, + SetSameHeight, + SetSameWidth, + SetSameSize, + CenterHorizontally, + CenterVertically, + CenterHorAndVert, + LayoutVertically, + LayoutHorizontally, + LayoutForm, + LayoutGrid + }; + enum SelectionMode { NoUpdate = 0x0000, @@ -302,7 +333,7 @@ public: /// Returns true, if element with given id is selected /// \param id Element id - bool isSelected(PDFInteger id) const { return m_selection.count(id); } + bool isSelected(PDFInteger id) const; /// Returns true, if selection is empty bool isSelectionEmpty() const { return m_selection.empty(); } @@ -324,6 +355,7 @@ public: bool isManipulationAllowed(PDFInteger pageIndex) const; bool isManipulationInProgress() const { return m_isManipulationInProgress; } + void performOperation(Operation operation); void performDeleteSelection(); void startManipulation(PDFInteger pageIndex, @@ -349,13 +381,21 @@ public: const QMatrix& pagePointToDevicePointMatrix, QList& errors) const; + /// Returns bounding box of whole selection + QRectF getSelectionBoundingRect() const; + + /// Returns page rectangle for the page + QRectF getPageMediaBox(PDFInteger pageIndex) const; + signals: void selectionChanged(); void stateChanged(); private: + void eraseSelectedElementById(PDFInteger id); + PDFPageContentScene* m_scene; - std::set m_selection; + std::vector m_selection; bool m_isManipulationInProgress; std::vector> m_manipulatedElements; std::map m_manipulationModes; @@ -372,6 +412,8 @@ public: explicit PDFPageContentScene(QObject* parent); virtual ~PDFPageContentScene(); + static constexpr PDFInteger INVALID_ELEMENT_ID = 0; + /// Add new element to page content scene, scene /// takes ownership over the element. /// \param element Element @@ -400,7 +442,13 @@ public: /// Removes elements specified in selection /// \param selection Items to be removed - void removeElementsById(const std::set& selection); + void removeElementsById(const std::vector& selection); + + /// Performs manipulation of selected elements with manipulator + void performOperation(int operation); + + /// Returns document (or nullptr) + const PDFDocument* getDocument() const; // IDrawWidgetInputInterface interface public: diff --git a/Pdf4QtViewerPlugins/SignaturePlugin/signatureplugin.cpp b/Pdf4QtViewerPlugins/SignaturePlugin/signatureplugin.cpp index 4b08bbf..04ce4cd 100644 --- a/Pdf4QtViewerPlugins/SignaturePlugin/signatureplugin.cpp +++ b/Pdf4QtViewerPlugins/SignaturePlugin/signatureplugin.cpp @@ -274,6 +274,7 @@ void SignaturePlugin::updateDockWidget() m_dataExchangeInterface->getMainWindow()->addDockWidget(Qt::RightDockWidgetArea, m_editorWidget, Qt::Vertical); m_editorWidget->setFloating(false); m_editorWidget->setWindowTitle(tr("Signature Toolbox")); + connect(m_editorWidget, &pdf::PDFPageContentEditorWidget::operationTriggered, &m_scene, &pdf::PDFPageContentScene::performOperation); for (QAction* action : m_actions) { From 2082732cb547c71b0a0aea76b11be7fbc216b2b0 Mon Sep 17 00:00:00 2001 From: Jakub Melka Date: Wed, 16 Mar 2022 20:25:09 +0100 Subject: [PATCH 14/39] Signature plugin: Layout horizontally/vertically --- Pdf4QtLib/sources/pdfpagecontentelements.cpp | 40 ++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/Pdf4QtLib/sources/pdfpagecontentelements.cpp b/Pdf4QtLib/sources/pdfpagecontentelements.cpp index 1e8e1d7..7491cb4 100644 --- a/Pdf4QtLib/sources/pdfpagecontentelements.cpp +++ b/Pdf4QtLib/sources/pdfpagecontentelements.cpp @@ -1674,10 +1674,50 @@ void PDFPageContentElementManipulator::performOperation(Operation operation) } case Operation::LayoutVertically: + { + auto comparator = [](PDFPageContentElement* left, PDFPageContentElement* right) + { + QRectF r1 = left->getBoundingBox(); + QRectF r2 = right->getBoundingBox(); + + return r1.y() > r2.y(); + }; + std::stable_sort(manipulatedElements.begin(), manipulatedElements.end(), comparator); + + qreal yTop = representativeRect.bottom(); + for (PDFPageContentElement* element : manipulatedElements) + { + QRectF boundingBox = element->getBoundingBox(); + QPointF offset(0.0, yTop - boundingBox.bottom()); + element->performManipulation(PDFPageContentElement::Translate, offset); + yTop -= boundingBox.height(); + } + break; + } case Operation::LayoutHorizontally: + { + auto comparator = [](PDFPageContentElement* left, PDFPageContentElement* right) + { + QRectF r1 = left->getBoundingBox(); + QRectF r2 = right->getBoundingBox(); + + return r1.x() < r2.x(); + }; + std::stable_sort(manipulatedElements.begin(), manipulatedElements.end(), comparator); + + qreal x = representativeRect.left(); + for (PDFPageContentElement* element : manipulatedElements) + { + QRectF boundingBox = element->getBoundingBox(); + QPointF offset(x - boundingBox.left(), 0.0); + element->performManipulation(PDFPageContentElement::Translate, offset); + x += boundingBox.width(); + } + break; + } case Operation::LayoutForm: break; From a5cd109a6e93e9541496a2c577f8a2f41ba8d800 Mon Sep 17 00:00:00 2001 From: Jakub Melka Date: Fri, 18 Mar 2022 19:49:14 +0100 Subject: [PATCH 15/39] Signature plugin: Form layout --- Pdf4QtLib/sources/pdfpagecontentelements.cpp | 151 ++++++++++++++++++- Pdf4QtLib/sources/pdfpagecontentelements.h | 4 + Pdf4QtLib/sources/pdfutils.h | 5 + 3 files changed, 159 insertions(+), 1 deletion(-) diff --git a/Pdf4QtLib/sources/pdfpagecontentelements.cpp b/Pdf4QtLib/sources/pdfpagecontentelements.cpp index 7491cb4..b7b2e31 100644 --- a/Pdf4QtLib/sources/pdfpagecontentelements.cpp +++ b/Pdf4QtLib/sources/pdfpagecontentelements.cpp @@ -20,6 +20,7 @@ #include "pdfdrawwidget.h" #include "pdfdrawspacecontroller.h" #include "pdfwidgetutils.h" +#include "pdfutils.h" #include #include @@ -464,7 +465,7 @@ void PDFPageContentScene::mousePressEvent(QWidget* widget, QMouseEvent* event) { m_manipulator.deselect(info.hoveredElementIds); } - else + else if (!m_manipulator.isAllSelected(info.hoveredElementIds)) { m_manipulator.selectNew(info.hoveredElementIds); } @@ -1335,6 +1336,11 @@ bool PDFPageContentElementManipulator::isSelected(PDFInteger id) const return std::find(m_selection.cbegin(), m_selection.cend(), id) != m_selection.cend(); } +bool PDFPageContentElementManipulator::isAllSelected(const std::set& elementIds) const +{ + return std::all_of(elementIds.cbegin(), elementIds.cend(), [this](PDFInteger id) { return isSelected(id); }); +} + void PDFPageContentElementManipulator::reset() { cancelManipulation(); @@ -1720,7 +1726,150 @@ void PDFPageContentElementManipulator::performOperation(Operation operation) } case Operation::LayoutForm: + { + std::vector> formLayout; + + // Divide elements to left/right side + std::vector elementsLeft; + std::vector elementsRight; + + for (PDFPageContentElement* element : manipulatedElements) + { + QRectF boundingBox = element->getBoundingBox(); + + const qreal distanceLeft = boundingBox.left() - representativeRect.left(); + const qreal distanceRight = representativeRect.right() - boundingBox.right(); + + if (distanceLeft < distanceRight) + { + elementsLeft.push_back(element); + } + else + { + elementsRight.push_back(element); + } + } + + // Create pairs of left/right elements + while (!elementsLeft.empty()) + { + if (!elementsLeft.empty() && !elementsRight.empty()) + { + PDFPageContentElement* elementRight = nullptr; + PDFPageContentElement* elementLeft = elementsLeft.back(); + QRectF leftBoundingBox = elementLeft->getBoundingBox(); + elementsLeft.pop_back(); + qreal overlap = 0.0; + + // Find matching element on the right + for (PDFPageContentElement* elementRightCurrent : elementsRight) + { + QRectF rightBoundingBox = elementRightCurrent->getBoundingBox(); + if (isRectangleVerticallyOverlapped(leftBoundingBox, rightBoundingBox)) + { + QRectF unitedRect = leftBoundingBox.united(rightBoundingBox); + qreal currentOverlap = leftBoundingBox.height() + rightBoundingBox.height() - unitedRect.height(); + + if (currentOverlap > overlap) + { + overlap = currentOverlap; + elementRight = elementRightCurrent; + } + } + } + + if (elementRight) + { + auto it = std::find(elementsRight.begin(), elementsRight.end(), elementRight); + elementsRight.erase(it); + } + + formLayout.emplace_back(elementLeft, elementRight); + continue; + } + } + + for (PDFPageContentElement* rightElement : elementsRight) + { + formLayout.emplace_back(nullptr, rightElement); + } + + // Sort elements vertically + auto comparator = [](const auto& left, const auto& right) + { + const PDFPageContentElement* l1 = left.first ? left.first : left.second; + const PDFPageContentElement* l2 = left.second ? left.second : left.first; + + const PDFPageContentElement* r1 = right.first ? right.first : right.second; + const PDFPageContentElement* r2 = right.second ? right.second : right.first; + + QRectF lbb1 = l1->getBoundingBox(); + QRectF lbb2 = l2->getBoundingBox(); + QRectF rbb1 = r1->getBoundingBox(); + QRectF rbb2 = r2->getBoundingBox(); + + const qreal ly = (lbb1.center().y() + lbb2.center().y()) * 0.5; + const qreal ry = (rbb1.center().y() + rbb2.center().y()) * 0.5; + + return ly > ry; + }; + std::stable_sort(formLayout.begin(), formLayout.end(), comparator); + + // Calculate width + qreal leftWidth = 0.0; + qreal rightWidth = 0.0; + + for (const auto& row : formLayout) + { + PDFPageContentElement* elementLeft = row.first; + PDFPageContentElement* elementRight = row.second; + + if (elementLeft) + { + qreal width = elementLeft->getBoundingBox().width(); + leftWidth = qMax(leftWidth, width); + } + + if (elementRight) + { + qreal width = elementRight->getBoundingBox().width(); + rightWidth = qMax(rightWidth, width); + } + } + + // Now, perform layout + qreal yTop = representativeRect.bottom(); + qreal xLeft = representativeRect.left(); + qreal xRight = representativeRect.right() - rightWidth; + + for (const auto& row : formLayout) + { + PDFPageContentElement* elementLeft = row.first; + PDFPageContentElement* elementRight = row.second; + + qreal yHeight = 0.0; + + if (elementLeft) + { + QRectF boundingBoxLeft = elementLeft->getBoundingBox(); + QPointF offsetLeft(xLeft - boundingBoxLeft.x(), yTop - boundingBoxLeft.bottom()); + elementLeft->performManipulation(PDFPageContentElement::Translate, offsetLeft); + yHeight = qMax(yHeight, boundingBoxLeft.height()); + } + + if (elementRight) + { + QRectF boundingBoxRight = elementRight->getBoundingBox(); + QPointF offsetRight(xRight - boundingBoxRight.x(), yTop - boundingBoxRight.bottom()); + elementRight->performManipulation(PDFPageContentElement::Translate, offsetRight); + yHeight = qMax(yHeight, boundingBoxRight.height()); + } + + yTop -= yHeight; + } + break; + } case Operation::LayoutGrid: break; diff --git a/Pdf4QtLib/sources/pdfpagecontentelements.h b/Pdf4QtLib/sources/pdfpagecontentelements.h index caa291e..6367219 100644 --- a/Pdf4QtLib/sources/pdfpagecontentelements.h +++ b/Pdf4QtLib/sources/pdfpagecontentelements.h @@ -335,6 +335,10 @@ public: /// \param id Element id bool isSelected(PDFInteger id) const; + /// Returns true, if all elements are selected + /// \param ids Element ids + bool isAllSelected(const std::set& elementIds) const; + /// Returns true, if selection is empty bool isSelectionEmpty() const { return m_selection.empty(); } diff --git a/Pdf4QtLib/sources/pdfutils.h b/Pdf4QtLib/sources/pdfutils.h index 9aaf0fb..afa864c 100644 --- a/Pdf4QtLib/sources/pdfutils.h +++ b/Pdf4QtLib/sources/pdfutils.h @@ -465,6 +465,11 @@ constexpr bool isRectangleHorizontallyOverlapped(const QRectF& r1, const QRectF& return isIntervalOverlap(r1.left(), r1.right(), r2.left(), r2.right()); } +constexpr bool isRectangleVerticallyOverlapped(const QRectF& r1, const QRectF& r2) +{ + return isIntervalOverlap(r1.top(), r1.bottom(), r2.top(), r2.bottom()); +} + inline QColor invertColor(QColor color) { qreal r = 0.0; From 2a478c2af7f68030819250e8fc762eaa8d3fba1a Mon Sep 17 00:00:00 2001 From: Jakub Melka Date: Sat, 19 Mar 2022 16:08:36 +0100 Subject: [PATCH 16/39] Signature plugin: Layout algorithm bugfixing --- Pdf4QtLib/sources/pdfpagecontentelements.cpp | 41 ++++++++++++++------ 1 file changed, 29 insertions(+), 12 deletions(-) diff --git a/Pdf4QtLib/sources/pdfpagecontentelements.cpp b/Pdf4QtLib/sources/pdfpagecontentelements.cpp index b7b2e31..7c382cf 100644 --- a/Pdf4QtLib/sources/pdfpagecontentelements.cpp +++ b/Pdf4QtLib/sources/pdfpagecontentelements.cpp @@ -1751,15 +1751,17 @@ void PDFPageContentElementManipulator::performOperation(Operation operation) } // Create pairs of left/right elements - while (!elementsLeft.empty()) + while (!elementsLeft.empty() && !elementsRight.empty()) { - if (!elementsLeft.empty() && !elementsRight.empty()) + PDFPageContentElement* elementRight = nullptr; + PDFPageContentElement* elementLeft = nullptr; + + qreal overlap = 0.0; + + // Iterate trough element on the left + for (PDFPageContentElement* elementLeftCurrent : elementsLeft) { - PDFPageContentElement* elementRight = nullptr; - PDFPageContentElement* elementLeft = elementsLeft.back(); - QRectF leftBoundingBox = elementLeft->getBoundingBox(); - elementsLeft.pop_back(); - qreal overlap = 0.0; + QRectF leftBoundingBox = elementLeftCurrent->getBoundingBox(); // Find matching element on the right for (PDFPageContentElement* elementRightCurrent : elementsRight) @@ -1774,19 +1776,34 @@ void PDFPageContentElementManipulator::performOperation(Operation operation) { overlap = currentOverlap; elementRight = elementRightCurrent; + elementLeft = elementLeftCurrent; } } } + } - if (elementRight) - { - auto it = std::find(elementsRight.begin(), elementsRight.end(), elementRight); - elementsRight.erase(it); - } + Q_ASSERT((elementLeft != nullptr) == (elementRight != nullptr)); + + if (elementLeft && elementRight) + { + auto itLeft = std::find(elementsLeft.begin(), elementsLeft.end(), elementLeft); + elementsLeft.erase(itLeft); + + auto itRight = std::find(elementsRight.begin(), elementsRight.end(), elementRight); + elementsRight.erase(itRight); formLayout.emplace_back(elementLeft, elementRight); continue; } + else + { + break; + } + } + + for (PDFPageContentElement* leftElement : elementsLeft) + { + formLayout.emplace_back(leftElement, nullptr); } for (PDFPageContentElement* rightElement : elementsRight) From ebf5ffb6abbca284a15572f02ef79603200151db Mon Sep 17 00:00:00 2001 From: Jakub Melka Date: Sat, 19 Mar 2022 18:42:49 +0100 Subject: [PATCH 17/39] Signature plugin: Grid layout --- Pdf4QtLib/sources/pdfpagecontentelements.cpp | 116 +++++++++++++++++++ 1 file changed, 116 insertions(+) diff --git a/Pdf4QtLib/sources/pdfpagecontentelements.cpp b/Pdf4QtLib/sources/pdfpagecontentelements.cpp index 7c382cf..a538ab3 100644 --- a/Pdf4QtLib/sources/pdfpagecontentelements.cpp +++ b/Pdf4QtLib/sources/pdfpagecontentelements.cpp @@ -1889,7 +1889,123 @@ void PDFPageContentElementManipulator::performOperation(Operation operation) } case Operation::LayoutGrid: + { + std::map rowHeights; + std::map columnWidths; + std::map elementToRow; + std::map elementToColumn; + + // Detect rows + auto comparatorRow = [](PDFPageContentElement* left, PDFPageContentElement* right) + { + QRectF r1 = left->getBoundingBox(); + QRectF r2 = right->getBoundingBox(); + + return r1.top() < r2.top(); + }; + std::stable_sort(manipulatedElements.begin(), manipulatedElements.end(), comparatorRow); + + PDFInteger row = 0; + std::vector rowElementsToProcess = manipulatedElements; + while (!rowElementsToProcess.empty()) + { + PDFPageContentElement* sampleElement = rowElementsToProcess.back(); + elementToRow[sampleElement] = row; + rowElementsToProcess.pop_back(); + QRectF boundingBox = sampleElement->getBoundingBox(); + qreal maxHeight = boundingBox.height(); + + for (auto it = rowElementsToProcess.begin(); it != rowElementsToProcess.end();) + { + QRectF currentBoundingBox = (*it)->getBoundingBox(); + if (isRectangleVerticallyOverlapped(boundingBox, currentBoundingBox)) + { + elementToRow[*it] = row; + maxHeight = qMax(currentBoundingBox.height(), maxHeight); + it = rowElementsToProcess.erase(it); + } + else + { + ++it; + } + } + + rowHeights[row] = qMax(rowHeights[row], maxHeight); + + ++row; + } + + // Detect columns + auto comparatorColumn = [](PDFPageContentElement* left, PDFPageContentElement* right) + { + QRectF r1 = left->getBoundingBox(); + QRectF r2 = right->getBoundingBox(); + + return r1.left() > r2.left(); + }; + std::stable_sort(manipulatedElements.begin(), manipulatedElements.end(), comparatorColumn); + + PDFInteger column = 0; + std::vector columnElementsToProcess = manipulatedElements; + while (!columnElementsToProcess.empty()) + { + PDFPageContentElement* sampleElement = columnElementsToProcess.back(); + elementToColumn[sampleElement] = column; + columnElementsToProcess.pop_back(); + QRectF boundingBox = sampleElement->getBoundingBox(); + qreal maxWidth = boundingBox.width(); + + for (auto it = columnElementsToProcess.begin(); it != columnElementsToProcess.end();) + { + QRectF currentBoundingBox = (*it)->getBoundingBox(); + if (isRectangleHorizontallyOverlapped(boundingBox, currentBoundingBox)) + { + elementToColumn[*it] = column; + maxWidth = qMax(currentBoundingBox.width(), maxWidth); + it = columnElementsToProcess.erase(it); + } + else + { + ++it; + } + } + + columnWidths[column] = qMax(columnWidths[column], maxWidth); + + ++column; + } + + // Calculate cell offsets + std::vector cellOffsetX(column, 0.0); + std::vector cellOffsetY(row, 0.0); + + for (size_t i = 1; i < size_t(column); ++i) + { + cellOffsetX[i] = cellOffsetX[i - 1] + columnWidths[i - 1]; + } + for (size_t i = 1; i < size_t(row); ++i) + { + cellOffsetY[i] = cellOffsetY[i - 1] + rowHeights[i - 1]; + } + + // Move elements + qreal xLeft = representativeRect.left(); + qreal yTop = representativeRect.bottom(); + for (PDFPageContentElement* element : manipulatedElements) + { + const PDFInteger row = elementToRow[element]; + const PDFInteger column = elementToColumn[element]; + + const qreal xOffset = cellOffsetX[column]; + const qreal yOffset = cellOffsetY[row]; + + QRectF boundingBox = element->getBoundingBox(); + QPointF offset(xLeft + xOffset - boundingBox.left(), yTop - yOffset - boundingBox.bottom()); + element->performManipulation(PDFPageContentElement::Translate, offset); + } + break; + } } for (PDFPageContentElement* element : manipulatedElements) From 5a8b1ca670165ce1a4bac44c6332c410c3d70948 Mon Sep 17 00:00:00 2001 From: Jakub Melka Date: Sun, 20 Mar 2022 19:26:54 +0100 Subject: [PATCH 18/39] Signature plugin: Some adjustments, icons --- .../sources/pdfpagecontenteditorwidget.cpp | 5 ++ .../sources/pdfpagecontenteditorwidget.h | 4 ++ Pdf4QtLib/sources/pdfpagecontentelements.cpp | 31 ++++++++-- Pdf4QtViewer/pdf4qtviewer.qrc | 16 ++++++ Pdf4QtViewer/resources/pce-align-bottom.svg | 57 +++++++++++++++++++ Pdf4QtViewer/resources/pce-align-h-center.svg | 57 +++++++++++++++++++ Pdf4QtViewer/resources/pce-align-left.svg | 57 +++++++++++++++++++ Pdf4QtViewer/resources/pce-align-right.svg | 57 +++++++++++++++++++ Pdf4QtViewer/resources/pce-align-top.svg | 57 +++++++++++++++++++ Pdf4QtViewer/resources/pce-align-v-center.svg | 57 +++++++++++++++++++ Pdf4QtViewer/resources/pce-center-h.svg | 57 +++++++++++++++++++ Pdf4QtViewer/resources/pce-center-v.svg | 57 +++++++++++++++++++ Pdf4QtViewer/resources/pce-center-vh.svg | 57 +++++++++++++++++++ Pdf4QtViewer/resources/pce-layout-form.svg | 57 +++++++++++++++++++ Pdf4QtViewer/resources/pce-layout-grid.svg | 57 +++++++++++++++++++ Pdf4QtViewer/resources/pce-layout-h.svg | 57 +++++++++++++++++++ Pdf4QtViewer/resources/pce-layout-v.svg | 57 +++++++++++++++++++ Pdf4QtViewer/resources/pce-same-height.svg | 57 +++++++++++++++++++ Pdf4QtViewer/resources/pce-same-size.svg | 57 +++++++++++++++++++ Pdf4QtViewer/resources/pce-same-width.svg | 57 +++++++++++++++++++ Pdf4QtViewer/resources/placeholder.svg | 57 +++++++++++++++++++ .../SignaturePlugin/signatureplugin.cpp | 18 ++++++ 22 files changed, 1038 insertions(+), 5 deletions(-) create mode 100644 Pdf4QtViewer/resources/pce-align-bottom.svg create mode 100644 Pdf4QtViewer/resources/pce-align-h-center.svg create mode 100644 Pdf4QtViewer/resources/pce-align-left.svg create mode 100644 Pdf4QtViewer/resources/pce-align-right.svg create mode 100644 Pdf4QtViewer/resources/pce-align-top.svg create mode 100644 Pdf4QtViewer/resources/pce-align-v-center.svg create mode 100644 Pdf4QtViewer/resources/pce-center-h.svg create mode 100644 Pdf4QtViewer/resources/pce-center-v.svg create mode 100644 Pdf4QtViewer/resources/pce-center-vh.svg create mode 100644 Pdf4QtViewer/resources/pce-layout-form.svg create mode 100644 Pdf4QtViewer/resources/pce-layout-grid.svg create mode 100644 Pdf4QtViewer/resources/pce-layout-h.svg create mode 100644 Pdf4QtViewer/resources/pce-layout-v.svg create mode 100644 Pdf4QtViewer/resources/pce-same-height.svg create mode 100644 Pdf4QtViewer/resources/pce-same-size.svg create mode 100644 Pdf4QtViewer/resources/pce-same-width.svg create mode 100644 Pdf4QtViewer/resources/placeholder.svg diff --git a/Pdf4QtLib/sources/pdfpagecontenteditorwidget.cpp b/Pdf4QtLib/sources/pdfpagecontenteditorwidget.cpp index 11d129f..ee89cb8 100644 --- a/Pdf4QtLib/sources/pdfpagecontenteditorwidget.cpp +++ b/Pdf4QtLib/sources/pdfpagecontenteditorwidget.cpp @@ -121,6 +121,11 @@ void PDFPageContentEditorWidget::addAction(QAction* action) ui->toolGroupBoxLayout->addWidget(button, row, column, Qt::AlignCenter); } +QToolButton* PDFPageContentEditorWidget::getToolButtonForOperation(int operation) const +{ + return qobject_cast(m_operationMapper.mapping(operation)); +} + void PDFPageContentEditorWidget::onActionTriggerRequest(QObject* actionObject) { QAction* action = qobject_cast(actionObject); diff --git a/Pdf4QtLib/sources/pdfpagecontenteditorwidget.h b/Pdf4QtLib/sources/pdfpagecontenteditorwidget.h index 19a6d9f..995f11e 100644 --- a/Pdf4QtLib/sources/pdfpagecontenteditorwidget.h +++ b/Pdf4QtLib/sources/pdfpagecontenteditorwidget.h @@ -23,6 +23,8 @@ #include #include +class QToolButton; + namespace Ui { class PDFPageContentEditorWidget; @@ -42,6 +44,8 @@ public: /// Adds external action to the tool box void addAction(QAction* action); + QToolButton* getToolButtonForOperation(int operation) const; + signals: void operationTriggered(int operation); diff --git a/Pdf4QtLib/sources/pdfpagecontentelements.cpp b/Pdf4QtLib/sources/pdfpagecontentelements.cpp index a538ab3..6375e33 100644 --- a/Pdf4QtLib/sources/pdfpagecontentelements.cpp +++ b/Pdf4QtLib/sources/pdfpagecontentelements.cpp @@ -1920,6 +1920,15 @@ void PDFPageContentElementManipulator::performOperation(Operation operation) QRectF currentBoundingBox = (*it)->getBoundingBox(); if (isRectangleVerticallyOverlapped(boundingBox, currentBoundingBox)) { + QRectF unitedRect = boundingBox.united(currentBoundingBox); + qreal currentOverlap = boundingBox.height() + currentBoundingBox.height() - unitedRect.height(); + + if (qFuzzyIsNull(currentOverlap)) + { + ++it; + continue; + } + elementToRow[*it] = row; maxHeight = qMax(currentBoundingBox.height(), maxHeight); it = rowElementsToProcess.erase(it); @@ -1960,6 +1969,15 @@ void PDFPageContentElementManipulator::performOperation(Operation operation) QRectF currentBoundingBox = (*it)->getBoundingBox(); if (isRectangleHorizontallyOverlapped(boundingBox, currentBoundingBox)) { + QRectF unitedRect = boundingBox.united(currentBoundingBox); + qreal currentOverlap = boundingBox.width() + currentBoundingBox.width() - unitedRect.width(); + + if (qFuzzyIsNull(currentOverlap)) + { + ++it; + continue; + } + elementToColumn[*it] = column; maxWidth = qMax(currentBoundingBox.width(), maxWidth); it = columnElementsToProcess.erase(it); @@ -2038,13 +2056,16 @@ void PDFPageContentElementManipulator::startManipulation(PDFInteger pageIndex, continue; } - const uint manipulationMode = element->getManipulationMode(startPoint, snapPointDistanceThreshold); - if (manipulationMode) + uint manipulationMode = element->getManipulationMode(startPoint, snapPointDistanceThreshold); + + if (!manipulationMode) { - // Jakub Melka: yes, we can manipulate this element - m_manipulatedElements.emplace_back(element->clone()); - m_manipulationModes[id] = manipulationMode; + manipulationMode = PDFPageContentElement::Translate; } + + // Jakub Melka: manipulate this element + m_manipulatedElements.emplace_back(element->clone()); + m_manipulationModes[id] = manipulationMode; } if (!m_manipulatedElements.empty()) diff --git a/Pdf4QtViewer/pdf4qtviewer.qrc b/Pdf4QtViewer/pdf4qtviewer.qrc index 985f175..6f2a227 100644 --- a/Pdf4QtViewer/pdf4qtviewer.qrc +++ b/Pdf4QtViewer/pdf4qtviewer.qrc @@ -74,5 +74,21 @@ resources/save.svg resources/save-as.svg resources/select-all-text.svg + resources/pce-align-bottom.svg + resources/pce-align-h-center.svg + resources/pce-align-left.svg + resources/pce-align-right.svg + resources/pce-align-top.svg + resources/pce-align-v-center.svg + resources/pce-center-h.svg + resources/pce-center-v.svg + resources/pce-center-vh.svg + resources/pce-layout-form.svg + resources/pce-layout-grid.svg + resources/pce-layout-h.svg + resources/pce-layout-v.svg + resources/pce-same-height.svg + resources/pce-same-size.svg + resources/pce-same-width.svg diff --git a/Pdf4QtViewer/resources/pce-align-bottom.svg b/Pdf4QtViewer/resources/pce-align-bottom.svg new file mode 100644 index 0000000..d2a3670 --- /dev/null +++ b/Pdf4QtViewer/resources/pce-align-bottom.svg @@ -0,0 +1,57 @@ + + + +image/svg+xml + +? + \ No newline at end of file diff --git a/Pdf4QtViewer/resources/pce-align-h-center.svg b/Pdf4QtViewer/resources/pce-align-h-center.svg new file mode 100644 index 0000000..d2a3670 --- /dev/null +++ b/Pdf4QtViewer/resources/pce-align-h-center.svg @@ -0,0 +1,57 @@ + + + +image/svg+xml + +? + \ No newline at end of file diff --git a/Pdf4QtViewer/resources/pce-align-left.svg b/Pdf4QtViewer/resources/pce-align-left.svg new file mode 100644 index 0000000..d2a3670 --- /dev/null +++ b/Pdf4QtViewer/resources/pce-align-left.svg @@ -0,0 +1,57 @@ + + + +image/svg+xml + +? + \ No newline at end of file diff --git a/Pdf4QtViewer/resources/pce-align-right.svg b/Pdf4QtViewer/resources/pce-align-right.svg new file mode 100644 index 0000000..d2a3670 --- /dev/null +++ b/Pdf4QtViewer/resources/pce-align-right.svg @@ -0,0 +1,57 @@ + + + +image/svg+xml + +? + \ No newline at end of file diff --git a/Pdf4QtViewer/resources/pce-align-top.svg b/Pdf4QtViewer/resources/pce-align-top.svg new file mode 100644 index 0000000..d2a3670 --- /dev/null +++ b/Pdf4QtViewer/resources/pce-align-top.svg @@ -0,0 +1,57 @@ + + + +image/svg+xml + +? + \ No newline at end of file diff --git a/Pdf4QtViewer/resources/pce-align-v-center.svg b/Pdf4QtViewer/resources/pce-align-v-center.svg new file mode 100644 index 0000000..d2a3670 --- /dev/null +++ b/Pdf4QtViewer/resources/pce-align-v-center.svg @@ -0,0 +1,57 @@ + + + +image/svg+xml + +? + \ No newline at end of file diff --git a/Pdf4QtViewer/resources/pce-center-h.svg b/Pdf4QtViewer/resources/pce-center-h.svg new file mode 100644 index 0000000..d2a3670 --- /dev/null +++ b/Pdf4QtViewer/resources/pce-center-h.svg @@ -0,0 +1,57 @@ + + + +image/svg+xml + +? + \ No newline at end of file diff --git a/Pdf4QtViewer/resources/pce-center-v.svg b/Pdf4QtViewer/resources/pce-center-v.svg new file mode 100644 index 0000000..d2a3670 --- /dev/null +++ b/Pdf4QtViewer/resources/pce-center-v.svg @@ -0,0 +1,57 @@ + + + +image/svg+xml + +? + \ No newline at end of file diff --git a/Pdf4QtViewer/resources/pce-center-vh.svg b/Pdf4QtViewer/resources/pce-center-vh.svg new file mode 100644 index 0000000..d2a3670 --- /dev/null +++ b/Pdf4QtViewer/resources/pce-center-vh.svg @@ -0,0 +1,57 @@ + + + +image/svg+xml + +? + \ No newline at end of file diff --git a/Pdf4QtViewer/resources/pce-layout-form.svg b/Pdf4QtViewer/resources/pce-layout-form.svg new file mode 100644 index 0000000..d2a3670 --- /dev/null +++ b/Pdf4QtViewer/resources/pce-layout-form.svg @@ -0,0 +1,57 @@ + + + +image/svg+xml + +? + \ No newline at end of file diff --git a/Pdf4QtViewer/resources/pce-layout-grid.svg b/Pdf4QtViewer/resources/pce-layout-grid.svg new file mode 100644 index 0000000..d2a3670 --- /dev/null +++ b/Pdf4QtViewer/resources/pce-layout-grid.svg @@ -0,0 +1,57 @@ + + + +image/svg+xml + +? + \ No newline at end of file diff --git a/Pdf4QtViewer/resources/pce-layout-h.svg b/Pdf4QtViewer/resources/pce-layout-h.svg new file mode 100644 index 0000000..d2a3670 --- /dev/null +++ b/Pdf4QtViewer/resources/pce-layout-h.svg @@ -0,0 +1,57 @@ + + + +image/svg+xml + +? + \ No newline at end of file diff --git a/Pdf4QtViewer/resources/pce-layout-v.svg b/Pdf4QtViewer/resources/pce-layout-v.svg new file mode 100644 index 0000000..d2a3670 --- /dev/null +++ b/Pdf4QtViewer/resources/pce-layout-v.svg @@ -0,0 +1,57 @@ + + + +image/svg+xml + +? + \ No newline at end of file diff --git a/Pdf4QtViewer/resources/pce-same-height.svg b/Pdf4QtViewer/resources/pce-same-height.svg new file mode 100644 index 0000000..d2a3670 --- /dev/null +++ b/Pdf4QtViewer/resources/pce-same-height.svg @@ -0,0 +1,57 @@ + + + +image/svg+xml + +? + \ No newline at end of file diff --git a/Pdf4QtViewer/resources/pce-same-size.svg b/Pdf4QtViewer/resources/pce-same-size.svg new file mode 100644 index 0000000..d2a3670 --- /dev/null +++ b/Pdf4QtViewer/resources/pce-same-size.svg @@ -0,0 +1,57 @@ + + + +image/svg+xml + +? + \ No newline at end of file diff --git a/Pdf4QtViewer/resources/pce-same-width.svg b/Pdf4QtViewer/resources/pce-same-width.svg new file mode 100644 index 0000000..d2a3670 --- /dev/null +++ b/Pdf4QtViewer/resources/pce-same-width.svg @@ -0,0 +1,57 @@ + + + +image/svg+xml + +? + \ No newline at end of file diff --git a/Pdf4QtViewer/resources/placeholder.svg b/Pdf4QtViewer/resources/placeholder.svg new file mode 100644 index 0000000..d2a3670 --- /dev/null +++ b/Pdf4QtViewer/resources/placeholder.svg @@ -0,0 +1,57 @@ + + + +image/svg+xml + +? + \ No newline at end of file diff --git a/Pdf4QtViewerPlugins/SignaturePlugin/signatureplugin.cpp b/Pdf4QtViewerPlugins/SignaturePlugin/signatureplugin.cpp index 04ce4cd..03c33d1 100644 --- a/Pdf4QtViewerPlugins/SignaturePlugin/signatureplugin.cpp +++ b/Pdf4QtViewerPlugins/SignaturePlugin/signatureplugin.cpp @@ -20,6 +20,7 @@ #include "pdfpagecontenteditorwidget.h" #include +#include #include namespace pdfplugin @@ -276,6 +277,23 @@ void SignaturePlugin::updateDockWidget() m_editorWidget->setWindowTitle(tr("Signature Toolbox")); connect(m_editorWidget, &pdf::PDFPageContentEditorWidget::operationTriggered, &m_scene, &pdf::PDFPageContentScene::performOperation); + m_editorWidget->getToolButtonForOperation(static_cast(pdf::PDFPageContentElementManipulator::Operation::AlignTop))->setIcon(QIcon(":/resources/pce-align-top.svg")); + m_editorWidget->getToolButtonForOperation(static_cast(pdf::PDFPageContentElementManipulator::Operation::AlignCenterVertically))->setIcon(QIcon(":/resources/pce-align-v-center.svg")); + m_editorWidget->getToolButtonForOperation(static_cast(pdf::PDFPageContentElementManipulator::Operation::AlignBottom))->setIcon(QIcon(":/resources/pce-align-bottom.svg")); + m_editorWidget->getToolButtonForOperation(static_cast(pdf::PDFPageContentElementManipulator::Operation::AlignLeft))->setIcon(QIcon(":/resources/pce-align-left.svg")); + m_editorWidget->getToolButtonForOperation(static_cast(pdf::PDFPageContentElementManipulator::Operation::AlignCenterHorizontally))->setIcon(QIcon(":/resources/pce-align-h-center.svg")); + m_editorWidget->getToolButtonForOperation(static_cast(pdf::PDFPageContentElementManipulator::Operation::AlignRight))->setIcon(QIcon(":/resources/pce-align-right.svg")); + m_editorWidget->getToolButtonForOperation(static_cast(pdf::PDFPageContentElementManipulator::Operation::SetSameHeight))->setIcon(QIcon(":/resources/pce-same-height.svg")); + m_editorWidget->getToolButtonForOperation(static_cast(pdf::PDFPageContentElementManipulator::Operation::SetSameWidth))->setIcon(QIcon(":/resources/pce-same-width.svg")); + m_editorWidget->getToolButtonForOperation(static_cast(pdf::PDFPageContentElementManipulator::Operation::SetSameSize))->setIcon(QIcon(":/resources/pce-same-size.svg")); + m_editorWidget->getToolButtonForOperation(static_cast(pdf::PDFPageContentElementManipulator::Operation::CenterHorizontally))->setIcon(QIcon(":/resources/pce-center-h.svg")); + m_editorWidget->getToolButtonForOperation(static_cast(pdf::PDFPageContentElementManipulator::Operation::CenterVertically))->setIcon(QIcon(":/resources/pce-center-v.svg")); + m_editorWidget->getToolButtonForOperation(static_cast(pdf::PDFPageContentElementManipulator::Operation::CenterHorAndVert))->setIcon(QIcon(":/resources/pce-center-vh.svg")); + m_editorWidget->getToolButtonForOperation(static_cast(pdf::PDFPageContentElementManipulator::Operation::LayoutVertically))->setIcon(QIcon(":/resources/pce-layout-v.svg")); + m_editorWidget->getToolButtonForOperation(static_cast(pdf::PDFPageContentElementManipulator::Operation::LayoutHorizontally))->setIcon(QIcon(":/resources/pce-layout-h.svg")); + m_editorWidget->getToolButtonForOperation(static_cast(pdf::PDFPageContentElementManipulator::Operation::LayoutForm))->setIcon(QIcon(":/resources/pce-layout-form.svg")); + m_editorWidget->getToolButtonForOperation(static_cast(pdf::PDFPageContentElementManipulator::Operation::LayoutGrid))->setIcon(QIcon(":/resources/pce-layout-grid.svg")); + for (QAction* action : m_actions) { m_editorWidget->addAction(action); From 22e8fd4522013156317f468565c2b80844f8de62 Mon Sep 17 00:00:00 2001 From: Jakub Melka Date: Sat, 26 Mar 2022 19:26:32 +0100 Subject: [PATCH 19/39] Signature plugin: text edit box --- Pdf4QtLib/Pdf4QtLib.pro | 2 + Pdf4QtLib/sources/pdfform.cpp | 1056 +---------------- Pdf4QtLib/sources/pdfform.h | 2 +- .../sources/pdfpagecontenteditortools.cpp | 259 ++++ Pdf4QtLib/sources/pdfpagecontenteditortools.h | 45 + Pdf4QtLib/sources/pdfpagecontentelements.cpp | 110 ++ Pdf4QtLib/sources/pdfpagecontentelements.h | 45 + Pdf4QtLib/sources/pdftexteditpseudowidget.cpp | 947 +++++++++++++++ Pdf4QtLib/sources/pdftexteditpseudowidget.h | 217 ++++ Pdf4QtLib/sources/pdfwidgettool.cpp | 44 +- Pdf4QtLib/sources/pdfwidgettool.h | 10 + .../SignaturePlugin/signatureplugin.cpp | 1 + .../SignaturePlugin/signatureplugin.h | 1 + 13 files changed, 1679 insertions(+), 1060 deletions(-) create mode 100644 Pdf4QtLib/sources/pdftexteditpseudowidget.cpp create mode 100644 Pdf4QtLib/sources/pdftexteditpseudowidget.h diff --git a/Pdf4QtLib/Pdf4QtLib.pro b/Pdf4QtLib/Pdf4QtLib.pro index 7963ea1..4cf97f3 100644 --- a/Pdf4QtLib/Pdf4QtLib.pro +++ b/Pdf4QtLib/Pdf4QtLib.pro @@ -89,6 +89,7 @@ SOURCES += \ sources/pdfsignaturehandler.cpp \ sources/pdfsnapper.cpp \ sources/pdfstructuretree.cpp \ + sources/pdftexteditpseudowidget.cpp \ sources/pdftextlayout.cpp \ sources/pdftransparencyrenderer.cpp \ sources/pdfutils.cpp \ @@ -170,6 +171,7 @@ HEADERS += \ sources/pdfsignaturehandler_impl.h \ sources/pdfsnapper.h \ sources/pdfstructuretree.h \ + sources/pdftexteditpseudowidget.h \ sources/pdftextlayout.h \ sources/pdftransparencyrenderer.h \ sources/pdfwidgettool.h \ diff --git a/Pdf4QtLib/sources/pdfform.cpp b/Pdf4QtLib/sources/pdfform.cpp index 23c64b0..50868dc 100644 --- a/Pdf4QtLib/sources/pdfform.cpp +++ b/Pdf4QtLib/sources/pdfform.cpp @@ -17,6 +17,7 @@ #include "pdfform.h" #include "pdfdocument.h" +#include "pdftexteditpseudowidget.h" #include "pdfdrawspacecontroller.h" #include "pdfdrawwidget.h" #include "pdfdocumentbuilder.h" @@ -33,175 +34,6 @@ namespace pdf { -/// "Pseudo" widget, which is emulating text editor, which can be single line, or multiline. -/// Passwords can also be edited and editor can be read only. -class PDFTextEditPseudowidget -{ -public: - explicit PDFTextEditPseudowidget(PDFFormField::FieldFlags flags); - - void shortcutOverrideEvent(QWidget* widget, QKeyEvent* event); - void keyPressEvent(QWidget* widget, QKeyEvent* event); - - inline bool isReadonly() const { return m_flags.testFlag(PDFFormField::ReadOnly); } - inline bool isMultiline() const { return m_flags.testFlag(PDFFormField::Multiline); } - inline bool isPassword() const { return m_flags.testFlag(PDFFormField::Password); } - inline bool isFileSelect() const { return m_flags.testFlag(PDFFormField::FileSelect); } - inline bool isComb() const { return m_flags.testFlag(PDFFormField::Comb); } - - inline bool isEmpty() const { return m_editText.isEmpty(); } - inline bool isTextSelected() const { return !isEmpty() && getSelectionLength() > 0; } - inline bool isWholeTextSelected() const { return !isEmpty() && getSelectionLength() == m_editText.length(); } - - inline int getTextLength() const { return m_editText.length(); } - inline int getSelectionLength() const { return m_selectionEnd - m_selectionStart; } - - inline int getPositionCursor() const { return m_positionCursor; } - inline int getPositionStart() const { return 0; } - inline int getPositionEnd() const { return getTextLength(); } - - inline void clearSelection() { m_selectionStart = m_selectionEnd = 0; } - - inline const QString& getText() const { return m_editText; } - inline QString getSelectedText() const { return m_editText.mid(m_selectionStart, getSelectionLength()); } - - /// Sets (updates) text selection - /// \param startPosition From where we are selecting text - /// \param selectionLength Selection length (positive - to the right, negative - to the left) - void setSelection(int startPosition, int selectionLength); - - /// Moves cursor position. It behaves as usual in text boxes, - /// when user moves the cursor. If \p select is true, then - /// selection is updated. - /// \param position New position of the cursor - /// \param select Select text when moving the cursor? - void setCursorPosition(int position, bool select); - - /// Sets text content of the widget. This functions sets the text, - /// even if widget is readonly. - /// \param text Text to be set - void setText(const QString& text); - - /// Sets widget appearance, such as font, font size, color, text alignment, - /// and rectangle, in which widget resides on page (in page coordinates) - /// \param appearance Appearance - /// \param textAlignment Text alignment - /// \param rect Widget rectangle in page coordinates - /// \param maxTextLength Maximal text length - void setAppearance(const PDFAnnotationDefaultAppearance& appearance, - Qt::Alignment textAlignment, - QRectF rect, - int maxTextLength); - - void performCut(); - void performCopy(); - void performPaste(); - void performClear(); - void performSelectAll(); - void performBackspace(); - void performDelete(); - void performRemoveSelectedText(); - void performInsertText(const QString& text); - - /// Draw text edit using given parameters - /// \param parameters Parameters - void draw(AnnotationDrawParameters& parameters, bool edit) const; - - /// Returns valid cursor position retrieved from position in the widget. - /// \param point Point in page coordinate space - /// \param edit Are we using edit transformations? - /// \returns Cursor position - int getCursorPositionFromWidgetPosition(const QPointF& point, bool edit) const; - - inline int getCursorForward(QTextLayout::CursorMode mode) const { return getNextPrevCursorPosition(getSingleStepForward(), mode); } - inline int getCursorBackward(QTextLayout::CursorMode mode) const { return getNextPrevCursorPosition(getSingleStepBackward(), mode); } - inline int getCursorCharacterForward() const { return getCursorForward(QTextLayout::SkipCharacters); } - inline int getCursorCharacterBackward() const { return getCursorBackward(QTextLayout::SkipCharacters); } - inline int getCursorWordForward() const { return getCursorForward(QTextLayout::SkipWords); } - inline int getCursorWordBackward() const { return getCursorBackward(QTextLayout::SkipWords); } - inline int getCursorDocumentStart() const { return (getSingleStepForward() > 0) ? getPositionStart() : getPositionEnd(); } - inline int getCursorDocumentEnd() const { return (getSingleStepForward() > 0) ? getPositionEnd() : getPositionStart(); } - inline int getCursorLineStart() const { return (getSingleStepForward() > 0) ? getCurrentLineTextStart() : getCurrentLineTextEnd(); } - inline int getCursorLineEnd() const { return (getSingleStepForward() > 0) ? getCurrentLineTextEnd() : getCurrentLineTextStart(); } - inline int getCursorNextLine() const { return getCurrentLineTextEnd(); } - inline int getCursorPreviousLine() const { return getNextPrevCursorPosition(getCurrentLineTextStart(), -1, QTextLayout::SkipCharacters); } - - const QRectF& getWidgetRect() const { return m_widgetRect; } - QFont getFont() const { return m_textLayout.font(); } - QColor getFontColor() const { return m_textColor; } - -private: - /// This function does following things: - /// 1) Clamps edit text to fit maximum length - /// 2) Creates display string from edit string - /// 3) Updates text layout - void updateTextLayout(); - - /// Returns single step forward, which is determined - /// by cursor move style and layout direction. - int getSingleStepForward() const; - - /// Returns single step backward, which is determined - /// by cursor move style and layout direction. - int getSingleStepBackward() const { return -getSingleStepForward(); } - - /// Returns next/previous position, by number of steps, - /// using given cursor mode (skipping characters or whole words). - /// \param steps Number of steps to proceed (can be negative number) - /// \param mode Skip mode - letters or words? - int getNextPrevCursorPosition(int steps, QTextLayout::CursorMode mode) const { return getNextPrevCursorPosition(m_positionCursor, steps, mode); } - - /// Returns next/previous position from reference cursor position, by number of steps, - /// using given cursor mode (skipping characters or whole words). - /// \param referencePosition Reference cursor position - /// \param steps Number of steps to proceed (can be negative number) - /// \param mode Skip mode - letters or words? - int getNextPrevCursorPosition(int referencePosition, int steps, QTextLayout::CursorMode mode) const; - - /// Returns current line text start position - int getCurrentLineTextStart() const; - - /// Returns current line text end position - int getCurrentLineTextEnd() const; - - /// Creates text box transform matrix, which transforms from - /// widget space to page space. - /// \param edit Create matrix for text editing? - QMatrix createTextBoxTransformMatrix(bool edit) const; - - /// Returns vector of cursor positions (which may be not equal - /// to edit string length, because edit string can contain surrogates, - /// or graphemes, which are single glyphs, but represented by more - /// 16-bit unicode codes). - std::vector getCursorPositions() const; - - int getCursorLineUp() const; - int getCursorLineDown() const; - - PDFFormField::FieldFlags m_flags; - - /// Text edited by the user - QString m_editText; - - /// Text, which is displayed. It can differ from text - /// edited by user, in case password is being entered. - QString m_displayText; - - /// Text layout - QTextLayout m_textLayout; - - /// Character for displaying passwords - QChar m_passwordReplacementCharacter; - - int m_selectionStart; - int m_selectionEnd; - int m_positionCursor; - int m_maxTextLength; - - QRectF m_widgetRect; - QColor m_textColor; -}; - /// Editor for button-like editors class PDFFormFieldAbstractButtonEditor : public PDFFormFieldWidgetEditor { @@ -2643,893 +2475,7 @@ void PDFFormFieldTextBoxEditor::draw(AnnotationDrawParameters& parameters, bool } } -PDFTextEditPseudowidget::PDFTextEditPseudowidget(PDFFormField::FieldFlags flags) : - m_flags(flags), - m_selectionStart(0), - m_selectionEnd(0), - m_positionCursor(0), - m_maxTextLength(0) -{ - m_textLayout.setCacheEnabled(true); - m_passwordReplacementCharacter = QApplication::style()->styleHint(QStyle::SH_LineEdit_PasswordCharacter); -} -void PDFTextEditPseudowidget::shortcutOverrideEvent(QWidget* widget, QKeyEvent* event) -{ - Q_UNUSED(widget); - constexpr QKeySequence::StandardKey acceptedKeys[] = { QKeySequence::Delete, QKeySequence::Cut, QKeySequence::Copy, QKeySequence::Paste, - QKeySequence::SelectAll, QKeySequence::MoveToNextChar, QKeySequence::MoveToPreviousChar, - QKeySequence::MoveToNextWord, QKeySequence::MoveToPreviousWord, QKeySequence::MoveToNextLine, - QKeySequence::MoveToPreviousLine, QKeySequence::MoveToStartOfLine, QKeySequence::MoveToEndOfLine, - QKeySequence::MoveToStartOfBlock, QKeySequence::MoveToEndOfBlock, QKeySequence::MoveToStartOfDocument, - QKeySequence::MoveToEndOfDocument, QKeySequence::SelectNextChar, QKeySequence::SelectPreviousChar, - QKeySequence::SelectNextWord, QKeySequence::SelectPreviousWord, QKeySequence::SelectNextLine, - QKeySequence::SelectPreviousLine, QKeySequence::SelectStartOfLine, QKeySequence::SelectEndOfLine, - QKeySequence::SelectStartOfBlock, QKeySequence::SelectEndOfBlock, QKeySequence::SelectStartOfDocument, - QKeySequence::SelectEndOfDocument, QKeySequence::DeleteStartOfWord, QKeySequence::DeleteEndOfWord, - QKeySequence::DeleteEndOfLine, QKeySequence::Deselect, QKeySequence::DeleteCompleteLine, QKeySequence::Backspace }; - - if (std::any_of(std::begin(acceptedKeys), std::end(acceptedKeys), [event](QKeySequence::StandardKey standardKey) { return event == standardKey; })) - { - event->accept(); - return; - } - - switch (event->key()) - { - case Qt::Key_Direction_L: - case Qt::Key_Direction_R: - case Qt::Key_Up: - case Qt::Key_Down: - case Qt::Key_Left: - case Qt::Key_Right: - event->accept(); - break; - - default: - break; - } - - if (!event->text().isEmpty()) - { - event->accept(); - for (const QChar& character : event->text()) - { - if (!character.isPrint()) - { - event->ignore(); - break; - } - } - } -} - -void PDFTextEditPseudowidget::keyPressEvent(QWidget* widget, QKeyEvent* event) -{ - Q_UNUSED(widget); - - /* - We will support following key sequences: - Delete - Cut, - Copy, - Paste, - SelectAll, - MoveToNextChar, - MoveToPreviousChar, - MoveToNextWord, - MoveToPreviousWord, - MoveToNextLine, - MoveToPreviousLine, - MoveToStartOfLine, - MoveToEndOfLine, - MoveToStartOfBlock, - MoveToEndOfBlock, - MoveToStartOfDocument, - MoveToEndOfDocument, - SelectNextChar, - SelectPreviousChar, - SelectNextWord, - SelectPreviousWord, - SelectNextLine, - SelectPreviousLine, - SelectStartOfLine, - SelectEndOfLine, - SelectStartOfBlock, - SelectEndOfBlock, - SelectStartOfDocument, - SelectEndOfDocument, - DeleteStartOfWord, - DeleteEndOfWord, - DeleteEndOfLine, - Deselect, - DeleteCompleteLine, - Backspace, - * */ - - event->accept(); - - if (event == QKeySequence::Delete) - { - performDelete(); - } - else if (event == QKeySequence::Cut) - { - performCut(); - } - else if (event == QKeySequence::Copy) - { - performCopy(); - } - else if (event == QKeySequence::Paste) - { - performPaste(); - } - else if (event == QKeySequence::SelectAll) - { - setSelection(0, getTextLength()); - } - else if (event == QKeySequence::MoveToNextChar) - { - setCursorPosition(getCursorCharacterForward(), false); - } - else if (event == QKeySequence::MoveToPreviousChar) - { - setCursorPosition(getCursorCharacterBackward(), false); - } - else if (event == QKeySequence::MoveToNextWord) - { - setCursorPosition(getCursorWordForward(), false); - } - else if (event == QKeySequence::MoveToPreviousWord) - { - setCursorPosition(getCursorWordBackward(), false); - } - else if (event == QKeySequence::MoveToNextLine) - { - setCursorPosition(getCursorLineDown(), false); - } - else if (event == QKeySequence::MoveToPreviousLine) - { - setCursorPosition(getCursorLineUp(), false); - } - else if (event == QKeySequence::MoveToStartOfLine || event == QKeySequence::MoveToStartOfBlock) - { - setCursorPosition(getCursorLineStart(), false); - } - else if (event == QKeySequence::MoveToEndOfLine || event == QKeySequence::MoveToEndOfBlock) - { - setCursorPosition(getCursorLineEnd(), false); - } - else if (event == QKeySequence::MoveToStartOfDocument) - { - setCursorPosition(getCursorDocumentStart(), false); - } - else if (event == QKeySequence::MoveToEndOfDocument) - { - setCursorPosition(getCursorDocumentEnd(), false); - } - else if (event == QKeySequence::SelectNextChar) - { - setCursorPosition(getCursorCharacterForward(), true); - } - else if (event == QKeySequence::SelectPreviousChar) - { - setCursorPosition(getCursorCharacterBackward(), true); - } - else if (event == QKeySequence::SelectNextWord) - { - setCursorPosition(getCursorWordForward(), true); - } - else if (event == QKeySequence::SelectPreviousWord) - { - setCursorPosition(getCursorWordBackward(), true); - } - else if (event == QKeySequence::SelectNextLine) - { - setCursorPosition(getCursorLineDown(), true); - } - else if (event == QKeySequence::SelectPreviousLine) - { - setCursorPosition(getCursorLineUp(), true); - } - else if (event == QKeySequence::SelectStartOfLine || event == QKeySequence::SelectStartOfBlock) - { - setCursorPosition(getCursorLineStart(), true); - } - else if (event == QKeySequence::SelectEndOfLine || event == QKeySequence::SelectEndOfBlock) - { - setCursorPosition(getCursorLineEnd(), true); - } - else if (event == QKeySequence::SelectStartOfDocument) - { - setCursorPosition(getCursorDocumentStart(), true); - } - else if (event == QKeySequence::SelectEndOfDocument) - { - setCursorPosition(getCursorDocumentEnd(), true); - } - else if (event == QKeySequence::DeleteStartOfWord) - { - if (!isReadonly()) - { - setCursorPosition(getCursorWordBackward(), true); - performRemoveSelectedText(); - } - } - else if (event == QKeySequence::DeleteEndOfWord) - { - if (!isReadonly()) - { - setCursorPosition(getCursorWordForward(), true); - performRemoveSelectedText(); - } - } - else if (event == QKeySequence::DeleteEndOfLine) - { - if (!isReadonly()) - { - setCursorPosition(getCursorLineEnd(), true); - performRemoveSelectedText(); - } - } - else if (event == QKeySequence::Deselect) - { - clearSelection(); - } - else if (event == QKeySequence::DeleteCompleteLine) - { - if (!isReadonly()) - { - m_selectionStart = getCurrentLineTextStart(); - m_selectionEnd = getCurrentLineTextEnd(); - performRemoveSelectedText(); - } - } - else if (event == QKeySequence::Backspace || event->key() == Qt::Key_Backspace) - { - performBackspace(); - } - else if (event->key() == Qt::Key_Direction_L) - { - QTextOption option = m_textLayout.textOption(); - option.setTextDirection(Qt::LeftToRight); - m_textLayout.setTextOption(qMove(option)); - updateTextLayout(); - } - else if (event->key() == Qt::Key_Direction_R) - { - QTextOption option = m_textLayout.textOption(); - option.setTextDirection(Qt::LeftToRight); - m_textLayout.setTextOption(qMove(option)); - updateTextLayout(); - } - else if (event->key() == Qt::Key_Up) - { - setCursorPosition(getCursorLineUp(), event->modifiers().testFlag(Qt::ShiftModifier)); - } - else if (event->key() == Qt::Key_Down) - { - setCursorPosition(getCursorLineDown(), event->modifiers().testFlag(Qt::ShiftModifier)); - } - else if (event->key() == Qt::Key_Left) - { - const int position = (event->modifiers().testFlag(Qt::ControlModifier)) ? getCursorWordBackward() : getCursorCharacterBackward(); - setCursorPosition(position, event->modifiers().testFlag(Qt::ShiftModifier)); - } - else if (event->key() == Qt::Key_Right) - { - const int position = (event->modifiers().testFlag(Qt::ControlModifier)) ? getCursorWordForward() : getCursorCharacterForward(); - setCursorPosition(position, event->modifiers().testFlag(Qt::ShiftModifier)); - } - else if (isMultiline() && (event->key() == Qt::Key_Enter || event->key() == Qt::Key_Return)) - { - performInsertText(QString::fromUtf16(u"\u2028")); - } - else - { - QString text = event->text(); - if (!text.isEmpty()) - { - performInsertText(text); - } - else - { - event->ignore(); - } - } -} - -void PDFTextEditPseudowidget::setSelection(int startPosition, int selectionLength) -{ - if (selectionLength > 0) - { - // We are selecting to the right - m_selectionStart = startPosition; - m_selectionEnd = qMin(startPosition + selectionLength, getTextLength()); - m_positionCursor = m_selectionEnd; - } - else if (selectionLength < 0) - { - // We are selecting to the left - m_selectionStart = qMax(startPosition + selectionLength, 0); - m_selectionEnd = startPosition; - m_positionCursor = m_selectionStart; - } - else - { - // Clear the selection - m_selectionStart = 0; - m_selectionEnd = 0; - m_positionCursor = startPosition; - } -} - -void PDFTextEditPseudowidget::setCursorPosition(int position, bool select) -{ - if (select) - { - const bool isTextSelected = this->isTextSelected(); - const bool isCursorAtStartOfSelection = isTextSelected && m_selectionStart == m_positionCursor; - const bool isCursorAtEndOfSelection = isTextSelected && m_selectionEnd == m_positionCursor; - - // Do we have selected text, and cursor is at the end of selected text? - // In this case, we must preserve start of the selection (we are manipulating - // with the end of selection. - if (isCursorAtEndOfSelection) - { - m_selectionStart = qMin(m_selectionStart, position); - m_selectionEnd = qMax(m_selectionStart, position); - } - else if (isCursorAtStartOfSelection) - { - // We must preserve end of the text selection, because we are manipulating - // with start of text selection. - m_selectionStart = qMin(m_selectionEnd, position); - m_selectionEnd = qMax(m_selectionEnd, position); - } - else - { - // Otherwise we are manipulating with cursor - m_selectionStart = qMin(m_positionCursor, position); - m_selectionEnd = qMax(m_positionCursor, position); - } - } - - // Why we are clearing text selection, even if we doesn't have it? - // We can have, for example m_selectionStart == m_selectionEnd == 3, - // and we want to have it both zero. - if (!select || !isTextSelected()) - { - clearSelection(); - } - - m_positionCursor = position; -} - -void PDFTextEditPseudowidget::setText(const QString& text) -{ - clearSelection(); - m_editText = text; - setCursorPosition(getPositionEnd(), false); - updateTextLayout(); -} - -void PDFTextEditPseudowidget::setAppearance(const PDFAnnotationDefaultAppearance& appearance, Qt::Alignment textAlignment, QRectF rect, int maxTextLength) -{ - // Set appearance - qreal fontSize = appearance.getFontSize(); - if (qFuzzyIsNull(fontSize)) - { - fontSize = rect.height(); - } - - QFont font(appearance.getFontName()); - font.setHintingPreference(QFont::PreferNoHinting); - font.setPixelSize(qCeil(fontSize)); - font.setStyleStrategy(QFont::ForceOutline); - m_textLayout.setFont(font); - - QTextOption option = m_textLayout.textOption(); - option.setWrapMode(isMultiline() ? QTextOption::WrapAtWordBoundaryOrAnywhere : QTextOption::NoWrap); - option.setAlignment(textAlignment); - option.setUseDesignMetrics(true); - m_textLayout.setTextOption(option); - - m_textColor = appearance.getFontColor(); - if (!m_textColor.isValid()) - { - m_textColor = Qt::black; - } - - m_maxTextLength = maxTextLength; - m_widgetRect = rect; -} - -void PDFTextEditPseudowidget::performCut() -{ - if (isReadonly()) - { - return; - } - - performCopy(); - performRemoveSelectedText(); -} - -void PDFTextEditPseudowidget::performCopy() -{ - if (isTextSelected() && !isPassword()) - { - QApplication::clipboard()->setText(getSelectedText(), QClipboard::Clipboard); - } -} - -void PDFTextEditPseudowidget::performPaste() -{ - // We always insert text, even if it is empty. Because we want - // to erase selected text. - performInsertText(QApplication::clipboard()->text(QClipboard::Clipboard)); -} - -void PDFTextEditPseudowidget::performClear() -{ - if (isReadonly()) - { - return; - } - - performSelectAll(); - performRemoveSelectedText(); -} - -void PDFTextEditPseudowidget::performSelectAll() -{ - m_selectionStart = getPositionStart(); - m_selectionEnd = getPositionEnd(); -} - -void PDFTextEditPseudowidget::performBackspace() -{ - if (isReadonly()) - { - return; - } - - // If we have selection, then delete selected text. If we do not have - // selection, then we delete previous character. - if (!isTextSelected()) - { - setCursorPosition(m_textLayout.previousCursorPosition(m_positionCursor, QTextLayout::SkipCharacters), true); - } - - performRemoveSelectedText(); -} - -void PDFTextEditPseudowidget::performDelete() -{ - if (isReadonly()) - { - return; - } - - // If we have selection, then delete selected text. If we do not have - // selection, then we delete previous character. - if (!isTextSelected()) - { - setCursorPosition(m_textLayout.nextCursorPosition(m_positionCursor, QTextLayout::SkipCharacters), true); - } - - performRemoveSelectedText(); -} - -void PDFTextEditPseudowidget::performRemoveSelectedText() -{ - if (isTextSelected()) - { - m_editText.remove(m_selectionStart, getSelectionLength()); - setCursorPosition(m_selectionStart, false); - clearSelection(); - updateTextLayout(); - } -} - -void PDFTextEditPseudowidget::performInsertText(const QString& text) -{ - if (isReadonly()) - { - return; - } - - // Insert text at the cursor - performRemoveSelectedText(); - m_editText.insert(m_positionCursor, text); - setCursorPosition(m_positionCursor + text.length(), false); - updateTextLayout(); -} - -QMatrix PDFTextEditPseudowidget::createTextBoxTransformMatrix(bool edit) const -{ - QMatrix matrix; - - matrix.translate(m_widgetRect.left(), m_widgetRect.bottom()); - matrix.scale(1.0, -1.0); - - if (edit && !isComb() && m_textLayout.isValidCursorPosition(m_positionCursor)) - { - // Jakub Melka: we must scroll the control, so cursor is always - // visible in the widget area. If we are not editing, this not the - // case, because we always show the text from the beginning. - - const QTextLine line = m_textLayout.lineForTextPosition(m_positionCursor); - if (line.isValid()) - { - const qreal xCursorPosition = line.cursorToX(m_positionCursor); - if (xCursorPosition >= m_widgetRect.width()) - { - const qreal delta = xCursorPosition - m_widgetRect.width(); - matrix.translate(-delta, 0.0); - } - - // Check, if we aren't outside of y position - const qreal lineSpacing = line.leadingIncluded() ? line.height() : line.leading() + line.height(); - const qreal lineBottom = lineSpacing * (line.lineNumber() + 1); - - if (lineBottom >= m_widgetRect.height()) - { - const qreal delta = lineBottom - m_widgetRect.height(); - matrix.translate(0.0, -delta); - } - } - } - - if (!isMultiline() && !isComb()) - { - // If text is single line, then adjust text position to the vertical center - QTextLine textLine = m_textLayout.lineAt(0); - if (textLine.isValid()) - { - const qreal lineSpacing = textLine.leadingIncluded() ? textLine.height() : textLine.leading() + textLine.height(); - const qreal textBoxHeight = m_widgetRect.height(); - - if (lineSpacing < textBoxHeight) - { - const qreal delta = (textBoxHeight - lineSpacing) * 0.5; - matrix.translate(0.0, delta); - } - } - } - - return matrix; -} - -std::vector PDFTextEditPseudowidget::getCursorPositions() const -{ - std::vector result; - result.reserve(m_editText.length()); - result.push_back(0); - - int currentPos = 0; - while (currentPos < m_editText.length()) - { - currentPos = m_textLayout.nextCursorPosition(currentPos, QTextLayout::SkipCharacters); - result.push_back(currentPos); - } - - return result; -} - -void PDFTextEditPseudowidget::draw(AnnotationDrawParameters& parameters, bool edit) const -{ - pdf::PDFPainterStateGuard guard(parameters.painter); - parameters.boundingRectangle = parameters.annotation->getRectangle(); - - QPalette palette = QApplication::palette(); - - auto getAdjustedColor = [¶meters](QColor color) - { - if (parameters.invertColors) - { - return invertColor(color); - } - - return color; - }; - - QPainter* painter = parameters.painter; - - if (edit) - { - pdf::PDFPainterStateGuard guard(painter); - painter->setPen(getAdjustedColor(Qt::black)); - painter->setBrush(Qt::NoBrush); - painter->drawRect(parameters.boundingRectangle); - } - - painter->setClipRect(parameters.boundingRectangle, Qt::IntersectClip); - painter->setWorldMatrix(createTextBoxTransformMatrix(edit), true); - painter->setPen(getAdjustedColor(Qt::black)); - - if (isComb()) - { - const qreal combCount = qMax(m_maxTextLength, 1); - qreal combWidth = m_widgetRect.width() / combCount; - QRectF combRect(0.0, 0.0, combWidth, m_widgetRect.height()); - painter->setFont(m_textLayout.font()); - - QColor textColor = getAdjustedColor(m_textColor); - QColor highlightTextColor = getAdjustedColor(palette.color(QPalette::HighlightedText)); - QColor highlightColor = getAdjustedColor(palette.color(QPalette::Highlight)); - - std::vector positions = getCursorPositions(); - for (size_t i = 1; i < positions.size(); ++i) - { - if (positions[i - 1] >= m_selectionStart && positions[i] - 1 < m_selectionEnd) - { - // We are in text selection - painter->fillRect(combRect, highlightColor); - painter->setPen(highlightTextColor); - } - else - { - // We are not in text selection - painter->setPen(textColor); - } - - int length = positions[i] - positions[i - 1]; - QString text = m_displayText.mid(positions[i - 1], length); - painter->drawText(combRect, Qt::AlignCenter, text); - - // Draw the cursor? - if (edit && m_positionCursor >= positions[i - 1] && m_positionCursor < positions[i]) - { - QRectF cursorRect(combRect.left(), combRect.bottom() - 1, combRect.width(), 1); - painter->fillRect(cursorRect, textColor); - } - - combRect.translate(combWidth, 0.0); - } - - // Draw the cursor onto next unfilled cell? - if (edit && m_positionCursor == getPositionEnd()) - { - QRectF cursorRect(combRect.left(), combRect.bottom() - 1, combRect.width(), 1); - painter->fillRect(cursorRect, textColor); - } - } - else - { - QVector selections; - - QTextLayout::FormatRange defaultFormat; - defaultFormat.start = getPositionStart(); - defaultFormat.length = getTextLength(); - defaultFormat.format.clearBackground(); - defaultFormat.format.setForeground(QBrush(getAdjustedColor(m_textColor), Qt::SolidPattern)); - - // If we are editing, draw selections - if (edit && isTextSelected()) - { - QTextLayout::FormatRange before = defaultFormat; - QTextLayout::FormatRange after = defaultFormat; - - before.start = getPositionStart(); - before.length = m_selectionStart; - after.start = m_selectionEnd; - after.length = getTextLength() - m_selectionEnd; - - QTextLayout::FormatRange selectedFormat = defaultFormat; - selectedFormat.start = m_selectionStart; - selectedFormat.length = getSelectionLength(); - selectedFormat.format.setForeground(QBrush(getAdjustedColor(palette.color(QPalette::HighlightedText)), Qt::SolidPattern)); - selectedFormat.format.setBackground(QBrush(getAdjustedColor(palette.color(QPalette::Highlight)), Qt::SolidPattern)); - - selections = { before, selectedFormat, after}; - } - else - { - selections.push_back(defaultFormat); - } - - // Draw text - m_textLayout.draw(painter, QPointF(0.0, 0.0), selections, QRectF()); - - // If we are editing, also draw text - if (edit && !isReadonly()) - { - m_textLayout.drawCursor(painter, QPointF(0.0, 0.0), m_positionCursor); - } - } -} - -int PDFTextEditPseudowidget::getCursorPositionFromWidgetPosition(const QPointF& point, bool edit) const -{ - QMatrix textBoxSpaceToPageSpace = createTextBoxTransformMatrix(edit); - QMatrix pageSpaceToTextBoxSpace = textBoxSpaceToPageSpace.inverted(); - - QPointF textBoxPoint = pageSpaceToTextBoxSpace.map(point); - - if (isComb()) - { - // If it is comb, then characters are spaced equidistantly - const qreal x = qBound(0.0, textBoxPoint.x(), m_widgetRect.width()); - const size_t position = qFloor(x * qreal(m_maxTextLength) / qreal(m_widgetRect.width())); - std::vector positions = getCursorPositions(); - if (position < positions.size()) - { - return positions[position]; - } - - return positions.back(); - } - else if (m_textLayout.lineCount() > 0) - { - QTextLine line; - qreal yPos = 0.0; - - // Find line under cursor - for (int i = 0; i < m_textLayout.lineCount(); ++i) - { - QTextLine currentLine = m_textLayout.lineAt(i); - const qreal lineSpacing = currentLine.leadingIncluded() ? currentLine.height() : currentLine.leading() + currentLine.height(); - const qreal yNextPos = yPos + lineSpacing; - - if (textBoxPoint.y() >= yPos && textBoxPoint.y() < yNextPos) - { - line = currentLine; - break; - } - - yPos = yNextPos; - } - - // If no line is found, select last line - if (!line.isValid()) - { - if (textBoxPoint.y() < 0.0) - { - line = m_textLayout.lineAt(0); - } - else - { - line = m_textLayout.lineAt(m_textLayout.lineCount() - 1); - } - } - - return line.xToCursor(textBoxPoint.x(), QTextLine::CursorBetweenCharacters); - } - - return 0; -} - -void PDFTextEditPseudowidget::updateTextLayout() -{ - // Prepare display text - if (isPassword()) - { - m_displayText.resize(m_editText.length(), m_passwordReplacementCharacter); - } - else - { - m_displayText = m_editText; - } - - // Perform text layout - m_textLayout.clearLayout(); - m_textLayout.setText(m_displayText); - m_textLayout.beginLayout(); - - QPointF textLinePosition(0.0, 0.0); - - while (true) - { - QTextLine textLine = m_textLayout.createLine(); - if (!textLine.isValid()) - { - // We are finished with layout - break; - } - - textLinePosition.ry() += textLine.leading(); - textLine.setLineWidth(m_widgetRect.width()); - textLine.setPosition(textLinePosition); - textLinePosition.ry() += textLine.height(); - } - m_textLayout.endLayout(); - - // Check length - if (m_maxTextLength > 0) - { - int length = 0; - int currentPos = 0; - - while (currentPos < m_editText.length()) - { - currentPos = m_textLayout.nextCursorPosition(currentPos, QTextLayout::SkipCharacters); - ++length; - - if (length == m_maxTextLength) - { - break; - } - } - - if (currentPos < m_editText.length()) - { - m_editText = m_editText.left(currentPos); - m_positionCursor = qBound(getPositionStart(), m_positionCursor, getPositionEnd()); - updateTextLayout(); - } - } -} - -int PDFTextEditPseudowidget::getSingleStepForward() const -{ - // If direction is right-to-left, then move backward (because - // text is painted from right to left. - return (m_textLayout.textOption().textDirection() == Qt::RightToLeft) ? -1 : 1; -} - -int PDFTextEditPseudowidget::getNextPrevCursorPosition(int referencePosition, int steps, QTextLayout::CursorMode mode) const -{ - int cursor = referencePosition; - - if (steps > 0) - { - for (int i = 0; i < steps; ++i) - { - cursor = m_textLayout.nextCursorPosition(cursor, mode); - } - } - else if (steps < 0) - { - for (int i = 0; i < -steps; ++i) - { - cursor = m_textLayout.previousCursorPosition(cursor, mode); - } - } - - return cursor; -} - -int PDFTextEditPseudowidget::getCurrentLineTextStart() const -{ - return m_textLayout.lineForTextPosition(m_positionCursor).textStart(); -} - -int PDFTextEditPseudowidget::getCurrentLineTextEnd() const -{ - QTextLine textLine = m_textLayout.lineForTextPosition(m_positionCursor); - return textLine.textStart() + textLine.textLength(); -} - -int PDFTextEditPseudowidget::getCursorLineUp() const -{ - QTextLine line = m_textLayout.lineForTextPosition(m_positionCursor); - const int lineIndex = line.lineNumber() - 1; - - if (lineIndex >= 0) - { - QTextLine upLine = m_textLayout.lineAt(lineIndex); - return upLine.xToCursor(line.cursorToX(m_positionCursor), QTextLine::CursorBetweenCharacters); - } - - return m_positionCursor; -} - -int PDFTextEditPseudowidget::getCursorLineDown() const -{ - QTextLine line = m_textLayout.lineForTextPosition(m_positionCursor); - const int lineIndex = line.lineNumber() + 1; - - if (lineIndex < m_textLayout.lineCount()) - { - QTextLine downLine = m_textLayout.lineAt(lineIndex); - return downLine.xToCursor(line.cursorToX(m_positionCursor), QTextLine::CursorBetweenCharacters); - } - - return m_positionCursor; -} bool PDFFormFieldText::setValue(const SetValueParameters& parameters) { diff --git a/Pdf4QtLib/sources/pdfform.h b/Pdf4QtLib/sources/pdfform.h index 0297edb..8c40a96 100644 --- a/Pdf4QtLib/sources/pdfform.h +++ b/Pdf4QtLib/sources/pdfform.h @@ -1,4 +1,4 @@ -// Copyright (C) 2020-2021 Jakub Melka +// Copyright (C) 2020-2022 Jakub Melka // // This file is part of PDF4QT. // diff --git a/Pdf4QtLib/sources/pdfpagecontenteditortools.cpp b/Pdf4QtLib/sources/pdfpagecontenteditortools.cpp index f6fb545..8b7a06a 100644 --- a/Pdf4QtLib/sources/pdfpagecontenteditortools.cpp +++ b/Pdf4QtLib/sources/pdfpagecontenteditortools.cpp @@ -18,10 +18,12 @@ #include "pdfpagecontenteditortools.h" #include "pdfpagecontentelements.h" #include "pdfpainterutils.h" +#include "pdftexteditpseudowidget.h" #include #include #include +#include namespace pdf { @@ -494,4 +496,261 @@ void PDFCreatePCElementFreehandCurveTool::resetTool() m_element->clear(); } +PDFCreatePCElementTextTool::PDFCreatePCElementTextTool(PDFDrawWidgetProxy* proxy, + PDFPageContentScene* scene, + QAction* action, + QObject* parent) : + BaseClass(proxy, scene, action, parent), + m_pickTool(nullptr), + m_element(nullptr), + m_textEditWidget(nullptr) +{ + m_pickTool = new PDFPickTool(proxy, PDFPickTool::Mode::Rectangles, this); + m_pickTool->setDrawSelectionRectangle(true); + connect(m_pickTool, &PDFPickTool::rectanglePicked, this, &PDFCreatePCElementTextTool::onRectanglePicked); + + QFont font = QGuiApplication::font(); + font.setPixelSize(16.0); + + m_element = new PDFPageContentElementTextBox(); + m_element->setBrush(Qt::NoBrush); + m_element->setPen(QPen(Qt::SolidLine)); + m_element->setFont(font); + + m_textEditWidget = new PDFTextEditPseudowidget(PDFFormField::FieldFlags()); +} + +PDFCreatePCElementTextTool::~PDFCreatePCElementTextTool() +{ + delete m_textEditWidget; + delete m_element; +} + +void PDFCreatePCElementTextTool::drawPage(QPainter* painter, + PDFInteger pageIndex, + const PDFPrecompiledPage* compiledPage, + PDFTextLayoutGetter& layoutGetter, + const QMatrix& pagePointToDevicePointMatrix, + QList& errors) const +{ + BaseClass::drawPage(painter, pageIndex, compiledPage, layoutGetter, pagePointToDevicePointMatrix, errors); + + if (pageIndex != m_element->getPageIndex()) + { + return; + } + + if (isEditing()) + { + PDFPainterStateGuard guard(painter); + AnnotationDrawParameters parameters; + parameters.painter = painter; + parameters.boundingRectangle = m_element->getRectangle(); + parameters.key.first = PDFAppeareanceStreams::Appearance::Normal; + parameters.invertColors = getProxy()->getFeatures().testFlag(PDFRenderer::InvertColors); + + painter->setWorldMatrix(pagePointToDevicePointMatrix, true); + m_textEditWidget->draw(parameters, true); + } +} + +void PDFCreatePCElementTextTool::setActiveImpl(bool active) +{ + BaseClass::setActiveImpl(active); + + if (active) + { + Q_ASSERT(!getTopToolstackTool()); + addTool(m_pickTool); + } + else + { + m_textEditWidget->setText(QString()); + m_element->setText(QString()); + + if (getTopToolstackTool()) + { + removeTool(); + } + } + + m_pickTool->setActive(active); +} + +void PDFCreatePCElementTextTool::onRectanglePicked(PDFInteger pageIndex, QRectF pageRectangle) +{ + if (pageRectangle.isEmpty()) + { + return; + } + + m_element->setPageIndex(pageIndex); + m_element->setRectangle(pageRectangle); + + m_textEditWidget->setAppearance(m_element->getFont(), + m_element->getAlignment(), + m_element->getRectangle(), + std::numeric_limits::max(), + m_element->getPen().color()); + + removeTool(); +} + +void PDFCreatePCElementTextTool::finishEditing() +{ + setActive(false); +} + +std::optional PDFCreatePCElementTextTool::getPagePointUnderMouse(QMouseEvent* event) const +{ + QPointF pagePoint; + PDFInteger pageIndex = getProxy()->getPageUnderPoint(event->pos(), &pagePoint); + if (pageIndex == m_element->getPageIndex() && + m_element->getRectangle().contains(pagePoint)) + { + return pagePoint; + } + + return std::nullopt; +} + +bool PDFCreatePCElementTextTool::isEditing() const +{ + return isActive() && !getTopToolstackTool(); +} + +void PDFCreatePCElementTextTool::shortcutOverrideEvent(QWidget* widget, QKeyEvent* event) +{ + Q_UNUSED(widget); + + if (isEditing()) + { + m_textEditWidget->shortcutOverrideEvent(widget, event); + } +} + +void PDFCreatePCElementTextTool::keyPressEvent(QWidget* widget, QKeyEvent* event) +{ + event->ignore(); + + if (!isEditing()) + { + BaseClass::keyPressEvent(widget, event); + return; + } + + if (event->key() == Qt::Key_Escape) + { + return; + } + + if (!m_textEditWidget->isMultiline() && (event->key() == Qt::Key_Enter || event->key() == Qt::Key_Return)) + { + // Commit the editor and create element + finishEditing(); + event->accept(); + return; + } + + m_textEditWidget->keyPressEvent(widget, event); + + if (event->isAccepted()) + { + widget->update(); + } +} + +void PDFCreatePCElementTextTool::mousePressEvent(QWidget* widget, QMouseEvent* event) +{ + if (isEditing()) + { + if (event->button() == Qt::LeftButton) + { + std::optional pagePoint = getPagePointUnderMouse(event); + if (pagePoint) + { + const int cursorPosition = m_textEditWidget->getCursorPositionFromWidgetPosition(pagePoint.value(), true); + m_textEditWidget->setCursorPosition(cursorPosition, event->modifiers() & Qt::ShiftModifier); + } + else + { + finishEditing(); + } + + event->accept(); + widget->update(); + } + } + else + { + BaseClass::mousePressEvent(widget, event); + } +} + +void PDFCreatePCElementTextTool::mouseDoubleClickEvent(QWidget* widget, QMouseEvent* event) +{ + if (isEditing()) + { + if (event->button() == Qt::LeftButton) + { + std::optional pagePoint = getPagePointUnderMouse(event); + if (pagePoint) + { + const int cursorPosition = m_textEditWidget->getCursorPositionFromWidgetPosition(pagePoint.value(), true); + m_textEditWidget->setCursorPosition(cursorPosition, false); + m_textEditWidget->setCursorPosition(m_textEditWidget->getCursorWordBackward(), false); + m_textEditWidget->setCursorPosition(m_textEditWidget->getCursorWordForward(), true); + } + else + { + finishEditing(); + } + + event->accept(); + widget->update(); + } + } + else + { + BaseClass::mousePressEvent(widget, event); + } +} + +void PDFCreatePCElementTextTool::mouseMoveEvent(QWidget* widget, QMouseEvent* event) +{ + if (isEditing()) + { + std::optional pagePoint = getPagePointUnderMouse(event); + if (pagePoint) + { + // We must test, if left mouse button is pressed while + // we are moving the mouse - if yes, then select the text. + if (event->buttons() & Qt::LeftButton) + { + const int cursorPosition = m_textEditWidget->getCursorPositionFromWidgetPosition(pagePoint.value(), true); + m_textEditWidget->setCursorPosition(cursorPosition, true); + + event->accept(); + widget->update(); + } + } + } + else + { + BaseClass::mouseMoveEvent(widget, event); + } +} + +void PDFCreatePCElementTextTool::wheelEvent(QWidget* widget, QWheelEvent* event) +{ + if (isEditing()) + { + + } + else + { + BaseClass::wheelEvent(widget, event); + } +} + } // namespace pdf diff --git a/Pdf4QtLib/sources/pdfpagecontenteditortools.h b/Pdf4QtLib/sources/pdfpagecontenteditortools.h index 5dcb457..1a4211c 100644 --- a/Pdf4QtLib/sources/pdfpagecontenteditortools.h +++ b/Pdf4QtLib/sources/pdfpagecontenteditortools.h @@ -27,8 +27,10 @@ class PDFPageContentScene; class PDFPageContentSvgElement; class PDFPageContentElementDot; class PDFPageContentElementLine; +class PDFPageContentElementTextBox; class PDFPageContentElementRectangle; class PDFPageContentElementFreehandCurve; +class PDFTextEditPseudowidget; class PDFCreatePCElementTool : public PDFWidgetTool { @@ -201,6 +203,49 @@ private: PDFPageContentElementFreehandCurve* m_element; }; +/// Tool that displays SVG image +class PDF4QTLIBSHARED_EXPORT PDFCreatePCElementTextTool : public PDFCreatePCElementTool +{ + Q_OBJECT + +private: + using BaseClass = PDFCreatePCElementTool; + +public: + explicit PDFCreatePCElementTextTool(PDFDrawWidgetProxy* proxy, + PDFPageContentScene* scene, + QAction* action, + QObject* parent); + virtual ~PDFCreatePCElementTextTool() override; + + virtual void drawPage(QPainter* painter, + PDFInteger pageIndex, + const PDFPrecompiledPage* compiledPage, + PDFTextLayoutGetter& layoutGetter, + const QMatrix& pagePointToDevicePointMatrix, + QList& errors) const override; + + virtual void setActiveImpl(bool active) override; + virtual void shortcutOverrideEvent(QWidget* widget, QKeyEvent* event) override; + virtual void keyPressEvent(QWidget* widget, QKeyEvent* event) override; + virtual void mousePressEvent(QWidget* widget, QMouseEvent* event) override; + virtual void mouseDoubleClickEvent(QWidget* widget, QMouseEvent* event) override; + virtual void mouseMoveEvent(QWidget* widget, QMouseEvent* event) override; + virtual void wheelEvent(QWidget* widget, QWheelEvent* event) override; + +private: + void onRectanglePicked(pdf::PDFInteger pageIndex, QRectF pageRectangle); + + void finishEditing(); + std::optional getPagePointUnderMouse(QMouseEvent* event) const; + + bool isEditing() const; + + PDFPickTool* m_pickTool; + PDFPageContentElementTextBox* m_element; + PDFTextEditPseudowidget* m_textEditWidget; +}; + } // namespace pdf #endif // PDFPAGECONTENTEDITORTOOLS_H diff --git a/Pdf4QtLib/sources/pdfpagecontentelements.cpp b/Pdf4QtLib/sources/pdfpagecontentelements.cpp index 6375e33..bde5512 100644 --- a/Pdf4QtLib/sources/pdfpagecontentelements.cpp +++ b/Pdf4QtLib/sources/pdfpagecontentelements.cpp @@ -2233,4 +2233,114 @@ void PDFPageContentElementManipulator::eraseSelectedElementById(PDFInteger id) } } +PDFPageContentElementTextBox* PDFPageContentElementTextBox::clone() const +{ + PDFPageContentElementTextBox* copy = new PDFPageContentElementTextBox(); + copy->setElementId(getElementId()); + copy->setPageIndex(getPageIndex()); + copy->setPen(getPen()); + copy->setBrush(getBrush()); + copy->setRectangle(getRectangle()); + copy->setText(getText()); + copy->setFont(getFont()); + copy->setAngle(getAngle()); + copy->setAlignment(getAlignment()); + return copy; +} + +void PDFPageContentElementTextBox::drawPage(QPainter* painter, + PDFInteger pageIndex, + const PDFPrecompiledPage* compiledPage, + PDFTextLayoutGetter& layoutGetter, + const QMatrix& pagePointToDevicePointMatrix, + QList& errors) const +{ + Q_UNUSED(compiledPage); + Q_UNUSED(layoutGetter); + Q_UNUSED(errors); + + if (pageIndex != getPageIndex()) + { + return; + } + + QRectF rect = getRectangle(); + + PDFPainterStateGuard guard(painter); + painter->setWorldMatrix(pagePointToDevicePointMatrix, true); + painter->setPen(getPen()); + painter->setBrush(getBrush()); + painter->setFont(getFont()); + painter->setRenderHint(QPainter::Antialiasing); + painter->setClipRect(rect, Qt::IntersectClip); + painter->translate(rect.center()); + painter->rotate(getAngle()); + + QTextOption option; + option.setAlignment(getAlignment()); + QRectF textRect(-rect.width() * 0.5, -rect.height() * 0.5, rect.width(), rect.height()); + painter->drawText(textRect, getText(), option); +} + +uint PDFPageContentElementTextBox::getManipulationMode(const QPointF& point, + PDFReal snapPointDistanceThreshold) const +{ + return getRectangleManipulationMode(getRectangle(), point, snapPointDistanceThreshold); +} + +void PDFPageContentElementTextBox::performManipulation(uint mode, const QPointF& offset) +{ + performRectangleManipulation(m_rectangle, mode, offset); +} + +QRectF PDFPageContentElementTextBox::getBoundingBox() const +{ + return m_rectangle; +} + +void PDFPageContentElementTextBox::setSize(QSizeF size) +{ + performRectangleSetSize(m_rectangle, size); +} + +const QString& PDFPageContentElementTextBox::getText() const +{ + return m_text; +} + +void PDFPageContentElementTextBox::setText(const QString& newText) +{ + m_text = newText; +} + +const QFont& PDFPageContentElementTextBox::getFont() const +{ + return m_font; +} + +void PDFPageContentElementTextBox::setFont(const QFont& newFont) +{ + m_font = newFont; +} + +PDFReal PDFPageContentElementTextBox::getAngle() const +{ + return m_angle; +} + +void PDFPageContentElementTextBox::setAngle(PDFReal newAngle) +{ + m_angle = newAngle; +} + +const Qt::Alignment& PDFPageContentElementTextBox::getAlignment() const +{ + return m_alignment; +} + +void PDFPageContentElementTextBox::setAlignment(const Qt::Alignment& newAlignment) +{ + m_alignment = newAlignment; +} + } // namespace pdf diff --git a/Pdf4QtLib/sources/pdfpagecontentelements.h b/Pdf4QtLib/sources/pdfpagecontentelements.h index 6367219..27e6c00 100644 --- a/Pdf4QtLib/sources/pdfpagecontentelements.h +++ b/Pdf4QtLib/sources/pdfpagecontentelements.h @@ -21,6 +21,7 @@ #include "pdfdocumentdrawinterface.h" #include +#include #include #include #include @@ -294,6 +295,50 @@ private: std::unique_ptr m_renderer; }; +class PDF4QTLIBSHARED_EXPORT PDFPageContentElementTextBox : public PDFPageContentStyledElement +{ +public: + virtual ~PDFPageContentElementTextBox() = default; + + virtual PDFPageContentElementTextBox* clone() const override; + + const QRectF& getRectangle() const { return m_rectangle; } + void setRectangle(const QRectF& newRectangle) { m_rectangle = newRectangle; } + + virtual void drawPage(QPainter* painter, + PDFInteger pageIndex, + const PDFPrecompiledPage* compiledPage, + PDFTextLayoutGetter& layoutGetter, + const QMatrix& pagePointToDevicePointMatrix, + QList& errors) const override; + + virtual uint getManipulationMode(const QPointF& point, + PDFReal snapPointDistanceThreshold) const override; + + virtual void performManipulation(uint mode, const QPointF& offset) override; + virtual QRectF getBoundingBox() const override; + virtual void setSize(QSizeF size); + + const QString& getText() const; + void setText(const QString& newText); + + const QFont& getFont() const; + void setFont(const QFont& newFont); + + PDFReal getAngle() const; + void setAngle(PDFReal newAngle); + + const Qt::Alignment& getAlignment() const; + void setAlignment(const Qt::Alignment& newAlignment); + +private: + QString m_text; + QRectF m_rectangle; + QFont m_font; + PDFReal m_angle = 0.0; + Qt::Alignment m_alignment = Qt::AlignCenter; +}; + class PDF4QTLIBSHARED_EXPORT PDFPageContentElementManipulator : public QObject { Q_OBJECT diff --git a/Pdf4QtLib/sources/pdftexteditpseudowidget.cpp b/Pdf4QtLib/sources/pdftexteditpseudowidget.cpp new file mode 100644 index 0000000..8fdceda --- /dev/null +++ b/Pdf4QtLib/sources/pdftexteditpseudowidget.cpp @@ -0,0 +1,947 @@ +// Copyright (C) 2022 Jakub Melka +// +// This file is part of PDF4QT. +// +// PDF4QT 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 +// with the written consent of the copyright owner, any later version. +// +// PDF4QT 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 PDF4QT. If not, see . + +#include "pdftexteditpseudowidget.h" +#include "pdfpainterutils.h" + +#include +#include +#include +#include +#include + +namespace pdf +{ + +PDFTextEditPseudowidget::PDFTextEditPseudowidget(PDFFormField::FieldFlags flags) : + m_flags(flags), + m_selectionStart(0), + m_selectionEnd(0), + m_positionCursor(0), + m_maxTextLength(0) +{ + m_textLayout.setCacheEnabled(true); + m_passwordReplacementCharacter = QApplication::style()->styleHint(QStyle::SH_LineEdit_PasswordCharacter); +} + +void PDFTextEditPseudowidget::shortcutOverrideEvent(QWidget* widget, QKeyEvent* event) +{ + Q_UNUSED(widget); + constexpr QKeySequence::StandardKey acceptedKeys[] = { QKeySequence::Delete, QKeySequence::Cut, QKeySequence::Copy, QKeySequence::Paste, + QKeySequence::SelectAll, QKeySequence::MoveToNextChar, QKeySequence::MoveToPreviousChar, + QKeySequence::MoveToNextWord, QKeySequence::MoveToPreviousWord, QKeySequence::MoveToNextLine, + QKeySequence::MoveToPreviousLine, QKeySequence::MoveToStartOfLine, QKeySequence::MoveToEndOfLine, + QKeySequence::MoveToStartOfBlock, QKeySequence::MoveToEndOfBlock, QKeySequence::MoveToStartOfDocument, + QKeySequence::MoveToEndOfDocument, QKeySequence::SelectNextChar, QKeySequence::SelectPreviousChar, + QKeySequence::SelectNextWord, QKeySequence::SelectPreviousWord, QKeySequence::SelectNextLine, + QKeySequence::SelectPreviousLine, QKeySequence::SelectStartOfLine, QKeySequence::SelectEndOfLine, + QKeySequence::SelectStartOfBlock, QKeySequence::SelectEndOfBlock, QKeySequence::SelectStartOfDocument, + QKeySequence::SelectEndOfDocument, QKeySequence::DeleteStartOfWord, QKeySequence::DeleteEndOfWord, + QKeySequence::DeleteEndOfLine, QKeySequence::Deselect, QKeySequence::DeleteCompleteLine, QKeySequence::Backspace }; + + if (std::any_of(std::begin(acceptedKeys), std::end(acceptedKeys), [event](QKeySequence::StandardKey standardKey) { return event == standardKey; })) + { + event->accept(); + return; + } + + switch (event->key()) + { + case Qt::Key_Direction_L: + case Qt::Key_Direction_R: + case Qt::Key_Up: + case Qt::Key_Down: + case Qt::Key_Left: + case Qt::Key_Right: + event->accept(); + break; + + default: + break; + } + + if (!event->text().isEmpty()) + { + event->accept(); + for (const QChar& character : event->text()) + { + if (!character.isPrint()) + { + event->ignore(); + break; + } + } + } +} + +void PDFTextEditPseudowidget::keyPressEvent(QWidget* widget, QKeyEvent* event) +{ + Q_UNUSED(widget); + + /* + We will support following key sequences: + Delete + Cut, + Copy, + Paste, + SelectAll, + MoveToNextChar, + MoveToPreviousChar, + MoveToNextWord, + MoveToPreviousWord, + MoveToNextLine, + MoveToPreviousLine, + MoveToStartOfLine, + MoveToEndOfLine, + MoveToStartOfBlock, + MoveToEndOfBlock, + MoveToStartOfDocument, + MoveToEndOfDocument, + SelectNextChar, + SelectPreviousChar, + SelectNextWord, + SelectPreviousWord, + SelectNextLine, + SelectPreviousLine, + SelectStartOfLine, + SelectEndOfLine, + SelectStartOfBlock, + SelectEndOfBlock, + SelectStartOfDocument, + SelectEndOfDocument, + DeleteStartOfWord, + DeleteEndOfWord, + DeleteEndOfLine, + Deselect, + DeleteCompleteLine, + Backspace, + * */ + + event->accept(); + + if (event == QKeySequence::Delete) + { + performDelete(); + } + else if (event == QKeySequence::Cut) + { + performCut(); + } + else if (event == QKeySequence::Copy) + { + performCopy(); + } + else if (event == QKeySequence::Paste) + { + performPaste(); + } + else if (event == QKeySequence::SelectAll) + { + setSelection(0, getTextLength()); + } + else if (event == QKeySequence::MoveToNextChar) + { + setCursorPosition(getCursorCharacterForward(), false); + } + else if (event == QKeySequence::MoveToPreviousChar) + { + setCursorPosition(getCursorCharacterBackward(), false); + } + else if (event == QKeySequence::MoveToNextWord) + { + setCursorPosition(getCursorWordForward(), false); + } + else if (event == QKeySequence::MoveToPreviousWord) + { + setCursorPosition(getCursorWordBackward(), false); + } + else if (event == QKeySequence::MoveToNextLine) + { + setCursorPosition(getCursorLineDown(), false); + } + else if (event == QKeySequence::MoveToPreviousLine) + { + setCursorPosition(getCursorLineUp(), false); + } + else if (event == QKeySequence::MoveToStartOfLine || event == QKeySequence::MoveToStartOfBlock) + { + setCursorPosition(getCursorLineStart(), false); + } + else if (event == QKeySequence::MoveToEndOfLine || event == QKeySequence::MoveToEndOfBlock) + { + setCursorPosition(getCursorLineEnd(), false); + } + else if (event == QKeySequence::MoveToStartOfDocument) + { + setCursorPosition(getCursorDocumentStart(), false); + } + else if (event == QKeySequence::MoveToEndOfDocument) + { + setCursorPosition(getCursorDocumentEnd(), false); + } + else if (event == QKeySequence::SelectNextChar) + { + setCursorPosition(getCursorCharacterForward(), true); + } + else if (event == QKeySequence::SelectPreviousChar) + { + setCursorPosition(getCursorCharacterBackward(), true); + } + else if (event == QKeySequence::SelectNextWord) + { + setCursorPosition(getCursorWordForward(), true); + } + else if (event == QKeySequence::SelectPreviousWord) + { + setCursorPosition(getCursorWordBackward(), true); + } + else if (event == QKeySequence::SelectNextLine) + { + setCursorPosition(getCursorLineDown(), true); + } + else if (event == QKeySequence::SelectPreviousLine) + { + setCursorPosition(getCursorLineUp(), true); + } + else if (event == QKeySequence::SelectStartOfLine || event == QKeySequence::SelectStartOfBlock) + { + setCursorPosition(getCursorLineStart(), true); + } + else if (event == QKeySequence::SelectEndOfLine || event == QKeySequence::SelectEndOfBlock) + { + setCursorPosition(getCursorLineEnd(), true); + } + else if (event == QKeySequence::SelectStartOfDocument) + { + setCursorPosition(getCursorDocumentStart(), true); + } + else if (event == QKeySequence::SelectEndOfDocument) + { + setCursorPosition(getCursorDocumentEnd(), true); + } + else if (event == QKeySequence::DeleteStartOfWord) + { + if (!isReadonly()) + { + setCursorPosition(getCursorWordBackward(), true); + performRemoveSelectedText(); + } + } + else if (event == QKeySequence::DeleteEndOfWord) + { + if (!isReadonly()) + { + setCursorPosition(getCursorWordForward(), true); + performRemoveSelectedText(); + } + } + else if (event == QKeySequence::DeleteEndOfLine) + { + if (!isReadonly()) + { + setCursorPosition(getCursorLineEnd(), true); + performRemoveSelectedText(); + } + } + else if (event == QKeySequence::Deselect) + { + clearSelection(); + } + else if (event == QKeySequence::DeleteCompleteLine) + { + if (!isReadonly()) + { + m_selectionStart = getCurrentLineTextStart(); + m_selectionEnd = getCurrentLineTextEnd(); + performRemoveSelectedText(); + } + } + else if (event == QKeySequence::Backspace || event->key() == Qt::Key_Backspace) + { + performBackspace(); + } + else if (event->key() == Qt::Key_Direction_L) + { + QTextOption option = m_textLayout.textOption(); + option.setTextDirection(Qt::LeftToRight); + m_textLayout.setTextOption(qMove(option)); + updateTextLayout(); + } + else if (event->key() == Qt::Key_Direction_R) + { + QTextOption option = m_textLayout.textOption(); + option.setTextDirection(Qt::LeftToRight); + m_textLayout.setTextOption(qMove(option)); + updateTextLayout(); + } + else if (event->key() == Qt::Key_Up) + { + setCursorPosition(getCursorLineUp(), event->modifiers().testFlag(Qt::ShiftModifier)); + } + else if (event->key() == Qt::Key_Down) + { + setCursorPosition(getCursorLineDown(), event->modifiers().testFlag(Qt::ShiftModifier)); + } + else if (event->key() == Qt::Key_Left) + { + const int position = (event->modifiers().testFlag(Qt::ControlModifier)) ? getCursorWordBackward() : getCursorCharacterBackward(); + setCursorPosition(position, event->modifiers().testFlag(Qt::ShiftModifier)); + } + else if (event->key() == Qt::Key_Right) + { + const int position = (event->modifiers().testFlag(Qt::ControlModifier)) ? getCursorWordForward() : getCursorCharacterForward(); + setCursorPosition(position, event->modifiers().testFlag(Qt::ShiftModifier)); + } + else if (isMultiline() && (event->key() == Qt::Key_Enter || event->key() == Qt::Key_Return)) + { + performInsertText(QString::fromUtf16(u"\u2028")); + } + else + { + QString text = event->text(); + if (!text.isEmpty()) + { + performInsertText(text); + } + else + { + event->ignore(); + } + } +} + +void PDFTextEditPseudowidget::setSelection(int startPosition, int selectionLength) +{ + if (selectionLength > 0) + { + // We are selecting to the right + m_selectionStart = startPosition; + m_selectionEnd = qMin(startPosition + selectionLength, getTextLength()); + m_positionCursor = m_selectionEnd; + } + else if (selectionLength < 0) + { + // We are selecting to the left + m_selectionStart = qMax(startPosition + selectionLength, 0); + m_selectionEnd = startPosition; + m_positionCursor = m_selectionStart; + } + else + { + // Clear the selection + m_selectionStart = 0; + m_selectionEnd = 0; + m_positionCursor = startPosition; + } +} + +void PDFTextEditPseudowidget::setCursorPosition(int position, bool select) +{ + if (select) + { + const bool isTextSelected = this->isTextSelected(); + const bool isCursorAtStartOfSelection = isTextSelected && m_selectionStart == m_positionCursor; + const bool isCursorAtEndOfSelection = isTextSelected && m_selectionEnd == m_positionCursor; + + // Do we have selected text, and cursor is at the end of selected text? + // In this case, we must preserve start of the selection (we are manipulating + // with the end of selection. + if (isCursorAtEndOfSelection) + { + m_selectionStart = qMin(m_selectionStart, position); + m_selectionEnd = qMax(m_selectionStart, position); + } + else if (isCursorAtStartOfSelection) + { + // We must preserve end of the text selection, because we are manipulating + // with start of text selection. + m_selectionStart = qMin(m_selectionEnd, position); + m_selectionEnd = qMax(m_selectionEnd, position); + } + else + { + // Otherwise we are manipulating with cursor + m_selectionStart = qMin(m_positionCursor, position); + m_selectionEnd = qMax(m_positionCursor, position); + } + } + + // Why we are clearing text selection, even if we doesn't have it? + // We can have, for example m_selectionStart == m_selectionEnd == 3, + // and we want to have it both zero. + if (!select || !isTextSelected()) + { + clearSelection(); + } + + m_positionCursor = position; +} + +void PDFTextEditPseudowidget::setText(const QString& text) +{ + clearSelection(); + m_editText = text; + setCursorPosition(getPositionEnd(), false); + updateTextLayout(); +} + +void PDFTextEditPseudowidget::setAppearance(const PDFAnnotationDefaultAppearance& appearance, Qt::Alignment textAlignment, QRectF rect, int maxTextLength) +{ + // Set appearance + qreal fontSize = appearance.getFontSize(); + if (qFuzzyIsNull(fontSize)) + { + fontSize = rect.height(); + } + + QFont font(appearance.getFontName()); + font.setHintingPreference(QFont::PreferNoHinting); + font.setPixelSize(qCeil(fontSize)); + font.setStyleStrategy(QFont::ForceOutline); + m_textLayout.setFont(font); + + QTextOption option = m_textLayout.textOption(); + option.setWrapMode(isMultiline() ? QTextOption::WrapAtWordBoundaryOrAnywhere : QTextOption::NoWrap); + option.setAlignment(textAlignment); + option.setUseDesignMetrics(true); + m_textLayout.setTextOption(option); + + m_textColor = appearance.getFontColor(); + if (!m_textColor.isValid()) + { + m_textColor = Qt::black; + } + + m_maxTextLength = maxTextLength; + m_widgetRect = rect; +} + +void PDFTextEditPseudowidget::setAppearance(const QFont& font, + Qt::Alignment textAlignment, + QRectF rect, + int maxTextLength, + QColor textColor) +{ + // Set appearance + m_textLayout.setFont(font); + + QTextOption option = m_textLayout.textOption(); + option.setWrapMode(isMultiline() ? QTextOption::WrapAtWordBoundaryOrAnywhere : QTextOption::NoWrap); + option.setAlignment(textAlignment); + option.setUseDesignMetrics(true); + m_textLayout.setTextOption(option); + + m_textColor = textColor; + if (!m_textColor.isValid()) + { + m_textColor = Qt::black; + } + + m_maxTextLength = maxTextLength; + m_widgetRect = rect; +} + +void PDFTextEditPseudowidget::performCut() +{ + if (isReadonly()) + { + return; + } + + performCopy(); + performRemoveSelectedText(); +} + +void PDFTextEditPseudowidget::performCopy() +{ + if (isTextSelected() && !isPassword()) + { + QApplication::clipboard()->setText(getSelectedText(), QClipboard::Clipboard); + } +} + +void PDFTextEditPseudowidget::performPaste() +{ + // We always insert text, even if it is empty. Because we want + // to erase selected text. + performInsertText(QApplication::clipboard()->text(QClipboard::Clipboard)); +} + +void PDFTextEditPseudowidget::performClear() +{ + if (isReadonly()) + { + return; + } + + performSelectAll(); + performRemoveSelectedText(); +} + +void PDFTextEditPseudowidget::performSelectAll() +{ + m_selectionStart = getPositionStart(); + m_selectionEnd = getPositionEnd(); +} + +void PDFTextEditPseudowidget::performBackspace() +{ + if (isReadonly()) + { + return; + } + + // If we have selection, then delete selected text. If we do not have + // selection, then we delete previous character. + if (!isTextSelected()) + { + setCursorPosition(m_textLayout.previousCursorPosition(m_positionCursor, QTextLayout::SkipCharacters), true); + } + + performRemoveSelectedText(); +} + +void PDFTextEditPseudowidget::performDelete() +{ + if (isReadonly()) + { + return; + } + + // If we have selection, then delete selected text. If we do not have + // selection, then we delete previous character. + if (!isTextSelected()) + { + setCursorPosition(m_textLayout.nextCursorPosition(m_positionCursor, QTextLayout::SkipCharacters), true); + } + + performRemoveSelectedText(); +} + +void PDFTextEditPseudowidget::performRemoveSelectedText() +{ + if (isTextSelected()) + { + m_editText.remove(m_selectionStart, getSelectionLength()); + setCursorPosition(m_selectionStart, false); + clearSelection(); + updateTextLayout(); + } +} + +void PDFTextEditPseudowidget::performInsertText(const QString& text) +{ + if (isReadonly()) + { + return; + } + + // Insert text at the cursor + performRemoveSelectedText(); + m_editText.insert(m_positionCursor, text); + setCursorPosition(m_positionCursor + text.length(), false); + updateTextLayout(); +} + +QMatrix PDFTextEditPseudowidget::createTextBoxTransformMatrix(bool edit) const +{ + QMatrix matrix; + + matrix.translate(m_widgetRect.left(), m_widgetRect.bottom()); + matrix.scale(1.0, -1.0); + + if (edit && !isComb() && m_textLayout.isValidCursorPosition(m_positionCursor)) + { + // Jakub Melka: we must scroll the control, so cursor is always + // visible in the widget area. If we are not editing, this not the + // case, because we always show the text from the beginning. + + const QTextLine line = m_textLayout.lineForTextPosition(m_positionCursor); + if (line.isValid()) + { + const qreal xCursorPosition = line.cursorToX(m_positionCursor); + if (xCursorPosition >= m_widgetRect.width()) + { + const qreal delta = xCursorPosition - m_widgetRect.width(); + matrix.translate(-delta, 0.0); + } + + // Check, if we aren't outside of y position + const qreal lineSpacing = line.leadingIncluded() ? line.height() : line.leading() + line.height(); + const qreal lineBottom = lineSpacing * (line.lineNumber() + 1); + + if (lineBottom >= m_widgetRect.height()) + { + const qreal delta = lineBottom - m_widgetRect.height(); + matrix.translate(0.0, -delta); + } + } + } + + if (!isMultiline() && !isComb()) + { + // If text is single line, then adjust text position to the vertical center + QTextLine textLine = m_textLayout.lineAt(0); + if (textLine.isValid()) + { + const qreal lineSpacing = textLine.leadingIncluded() ? textLine.height() : textLine.leading() + textLine.height(); + const qreal textBoxHeight = m_widgetRect.height(); + + if (lineSpacing < textBoxHeight) + { + const qreal delta = (textBoxHeight - lineSpacing) * 0.5; + matrix.translate(0.0, delta); + } + } + } + + return matrix; +} + +std::vector PDFTextEditPseudowidget::getCursorPositions() const +{ + std::vector result; + result.reserve(m_editText.length()); + result.push_back(0); + + int currentPos = 0; + while (currentPos < m_editText.length()) + { + currentPos = m_textLayout.nextCursorPosition(currentPos, QTextLayout::SkipCharacters); + result.push_back(currentPos); + } + + return result; +} + +void PDFTextEditPseudowidget::draw(AnnotationDrawParameters& parameters, bool edit) const +{ + pdf::PDFPainterStateGuard guard(parameters.painter); + + if (parameters.annotation) + { + parameters.boundingRectangle = parameters.annotation->getRectangle(); + } + + QPalette palette = QApplication::palette(); + + auto getAdjustedColor = [¶meters](QColor color) + { + if (parameters.invertColors) + { + return invertColor(color); + } + + return color; + }; + + QPainter* painter = parameters.painter; + + if (edit) + { + pdf::PDFPainterStateGuard guard(painter); + painter->setPen(getAdjustedColor(Qt::black)); + painter->setBrush(Qt::NoBrush); + painter->drawRect(parameters.boundingRectangle); + } + + painter->setClipRect(parameters.boundingRectangle, Qt::IntersectClip); + painter->setWorldMatrix(createTextBoxTransformMatrix(edit), true); + painter->setPen(getAdjustedColor(Qt::black)); + + if (isComb()) + { + const qreal combCount = qMax(m_maxTextLength, 1); + qreal combWidth = m_widgetRect.width() / combCount; + QRectF combRect(0.0, 0.0, combWidth, m_widgetRect.height()); + painter->setFont(m_textLayout.font()); + + QColor textColor = getAdjustedColor(m_textColor); + QColor highlightTextColor = getAdjustedColor(palette.color(QPalette::HighlightedText)); + QColor highlightColor = getAdjustedColor(palette.color(QPalette::Highlight)); + + std::vector positions = getCursorPositions(); + for (size_t i = 1; i < positions.size(); ++i) + { + if (positions[i - 1] >= m_selectionStart && positions[i] - 1 < m_selectionEnd) + { + // We are in text selection + painter->fillRect(combRect, highlightColor); + painter->setPen(highlightTextColor); + } + else + { + // We are not in text selection + painter->setPen(textColor); + } + + int length = positions[i] - positions[i - 1]; + QString text = m_displayText.mid(positions[i - 1], length); + painter->drawText(combRect, Qt::AlignCenter, text); + + // Draw the cursor? + if (edit && m_positionCursor >= positions[i - 1] && m_positionCursor < positions[i]) + { + QRectF cursorRect(combRect.left(), combRect.bottom() - 1, combRect.width(), 1); + painter->fillRect(cursorRect, textColor); + } + + combRect.translate(combWidth, 0.0); + } + + // Draw the cursor onto next unfilled cell? + if (edit && m_positionCursor == getPositionEnd()) + { + QRectF cursorRect(combRect.left(), combRect.bottom() - 1, combRect.width(), 1); + painter->fillRect(cursorRect, textColor); + } + } + else + { + QVector selections; + + QTextLayout::FormatRange defaultFormat; + defaultFormat.start = getPositionStart(); + defaultFormat.length = getTextLength(); + defaultFormat.format.clearBackground(); + defaultFormat.format.setForeground(QBrush(getAdjustedColor(m_textColor), Qt::SolidPattern)); + + // If we are editing, draw selections + if (edit && isTextSelected()) + { + QTextLayout::FormatRange before = defaultFormat; + QTextLayout::FormatRange after = defaultFormat; + + before.start = getPositionStart(); + before.length = m_selectionStart; + after.start = m_selectionEnd; + after.length = getTextLength() - m_selectionEnd; + + QTextLayout::FormatRange selectedFormat = defaultFormat; + selectedFormat.start = m_selectionStart; + selectedFormat.length = getSelectionLength(); + selectedFormat.format.setForeground(QBrush(getAdjustedColor(palette.color(QPalette::HighlightedText)), Qt::SolidPattern)); + selectedFormat.format.setBackground(QBrush(getAdjustedColor(palette.color(QPalette::Highlight)), Qt::SolidPattern)); + + selections = { before, selectedFormat, after}; + } + else + { + selections.push_back(defaultFormat); + } + + // Draw text + m_textLayout.draw(painter, QPointF(0.0, 0.0), selections, QRectF()); + + // If we are editing, also draw text + if (edit && !isReadonly()) + { + m_textLayout.drawCursor(painter, QPointF(0.0, 0.0), m_positionCursor); + } + } +} + +int PDFTextEditPseudowidget::getCursorPositionFromWidgetPosition(const QPointF& point, bool edit) const +{ + QMatrix textBoxSpaceToPageSpace = createTextBoxTransformMatrix(edit); + QMatrix pageSpaceToTextBoxSpace = textBoxSpaceToPageSpace.inverted(); + + QPointF textBoxPoint = pageSpaceToTextBoxSpace.map(point); + + if (isComb()) + { + // If it is comb, then characters are spaced equidistantly + const qreal x = qBound(0.0, textBoxPoint.x(), m_widgetRect.width()); + const size_t position = qFloor(x * qreal(m_maxTextLength) / qreal(m_widgetRect.width())); + std::vector positions = getCursorPositions(); + if (position < positions.size()) + { + return positions[position]; + } + + return positions.back(); + } + else if (m_textLayout.lineCount() > 0) + { + QTextLine line; + qreal yPos = 0.0; + + // Find line under cursor + for (int i = 0; i < m_textLayout.lineCount(); ++i) + { + QTextLine currentLine = m_textLayout.lineAt(i); + const qreal lineSpacing = currentLine.leadingIncluded() ? currentLine.height() : currentLine.leading() + currentLine.height(); + const qreal yNextPos = yPos + lineSpacing; + + if (textBoxPoint.y() >= yPos && textBoxPoint.y() < yNextPos) + { + line = currentLine; + break; + } + + yPos = yNextPos; + } + + // If no line is found, select last line + if (!line.isValid()) + { + if (textBoxPoint.y() < 0.0) + { + line = m_textLayout.lineAt(0); + } + else + { + line = m_textLayout.lineAt(m_textLayout.lineCount() - 1); + } + } + + return line.xToCursor(textBoxPoint.x(), QTextLine::CursorBetweenCharacters); + } + + return 0; +} + +void PDFTextEditPseudowidget::updateTextLayout() +{ + // Prepare display text + if (isPassword()) + { + m_displayText.resize(m_editText.length(), m_passwordReplacementCharacter); + } + else + { + m_displayText = m_editText; + } + + // Perform text layout + m_textLayout.clearLayout(); + m_textLayout.setText(m_displayText); + m_textLayout.beginLayout(); + + QPointF textLinePosition(0.0, 0.0); + + while (true) + { + QTextLine textLine = m_textLayout.createLine(); + if (!textLine.isValid()) + { + // We are finished with layout + break; + } + + textLinePosition.ry() += textLine.leading(); + textLine.setLineWidth(m_widgetRect.width()); + textLine.setPosition(textLinePosition); + textLinePosition.ry() += textLine.height(); + } + m_textLayout.endLayout(); + + // Check length + if (m_maxTextLength > 0) + { + int length = 0; + int currentPos = 0; + + while (currentPos < m_editText.length()) + { + currentPos = m_textLayout.nextCursorPosition(currentPos, QTextLayout::SkipCharacters); + ++length; + + if (length == m_maxTextLength) + { + break; + } + } + + if (currentPos < m_editText.length()) + { + m_editText = m_editText.left(currentPos); + m_positionCursor = qBound(getPositionStart(), m_positionCursor, getPositionEnd()); + updateTextLayout(); + } + } +} + +int PDFTextEditPseudowidget::getSingleStepForward() const +{ + // If direction is right-to-left, then move backward (because + // text is painted from right to left. + return (m_textLayout.textOption().textDirection() == Qt::RightToLeft) ? -1 : 1; +} + +int PDFTextEditPseudowidget::getNextPrevCursorPosition(int referencePosition, int steps, QTextLayout::CursorMode mode) const +{ + int cursor = referencePosition; + + if (steps > 0) + { + for (int i = 0; i < steps; ++i) + { + cursor = m_textLayout.nextCursorPosition(cursor, mode); + } + } + else if (steps < 0) + { + for (int i = 0; i < -steps; ++i) + { + cursor = m_textLayout.previousCursorPosition(cursor, mode); + } + } + + return cursor; +} + +int PDFTextEditPseudowidget::getCurrentLineTextStart() const +{ + return m_textLayout.lineForTextPosition(m_positionCursor).textStart(); +} + +int PDFTextEditPseudowidget::getCurrentLineTextEnd() const +{ + QTextLine textLine = m_textLayout.lineForTextPosition(m_positionCursor); + return textLine.textStart() + textLine.textLength(); +} + +int PDFTextEditPseudowidget::getCursorLineUp() const +{ + QTextLine line = m_textLayout.lineForTextPosition(m_positionCursor); + const int lineIndex = line.lineNumber() - 1; + + if (lineIndex >= 0) + { + QTextLine upLine = m_textLayout.lineAt(lineIndex); + return upLine.xToCursor(line.cursorToX(m_positionCursor), QTextLine::CursorBetweenCharacters); + } + + return m_positionCursor; +} + +int PDFTextEditPseudowidget::getCursorLineDown() const +{ + QTextLine line = m_textLayout.lineForTextPosition(m_positionCursor); + const int lineIndex = line.lineNumber() + 1; + + if (lineIndex < m_textLayout.lineCount()) + { + QTextLine downLine = m_textLayout.lineAt(lineIndex); + return downLine.xToCursor(line.cursorToX(m_positionCursor), QTextLine::CursorBetweenCharacters); + } + + return m_positionCursor; +} + +} // namespace pdf diff --git a/Pdf4QtLib/sources/pdftexteditpseudowidget.h b/Pdf4QtLib/sources/pdftexteditpseudowidget.h new file mode 100644 index 0000000..19d00d7 --- /dev/null +++ b/Pdf4QtLib/sources/pdftexteditpseudowidget.h @@ -0,0 +1,217 @@ +// Copyright (C) 2022 Jakub Melka +// +// This file is part of PDF4QT. +// +// PDF4QT 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 +// with the written consent of the copyright owner, any later version. +// +// PDF4QT 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 PDF4QT. If not, see . + +#ifndef PDFTEXTEDITPSEUDOWIDGET_H +#define PDFTEXTEDITPSEUDOWIDGET_H + +#include "pdfglobal.h" +#include "pdfform.h" + +#include +#include + +class QWidget; +class QKeyEvent; + +namespace pdf +{ + +/// "Pseudo" widget, which is emulating text editor, which can be single line, or multiline. +/// Passwords can also be edited and editor can be read only. +class PDFTextEditPseudowidget +{ +public: + explicit PDFTextEditPseudowidget(PDFFormField::FieldFlags flags); + + void shortcutOverrideEvent(QWidget* widget, QKeyEvent* event); + void keyPressEvent(QWidget* widget, QKeyEvent* event); + + inline bool isReadonly() const { return m_flags.testFlag(PDFFormField::ReadOnly); } + inline bool isMultiline() const { return m_flags.testFlag(PDFFormField::Multiline); } + inline bool isPassword() const { return m_flags.testFlag(PDFFormField::Password); } + inline bool isFileSelect() const { return m_flags.testFlag(PDFFormField::FileSelect); } + inline bool isComb() const { return m_flags.testFlag(PDFFormField::Comb); } + + inline bool isEmpty() const { return m_editText.isEmpty(); } + inline bool isTextSelected() const { return !isEmpty() && getSelectionLength() > 0; } + inline bool isWholeTextSelected() const { return !isEmpty() && getSelectionLength() == m_editText.length(); } + + inline int getTextLength() const { return m_editText.length(); } + inline int getSelectionLength() const { return m_selectionEnd - m_selectionStart; } + + inline int getPositionCursor() const { return m_positionCursor; } + inline int getPositionStart() const { return 0; } + inline int getPositionEnd() const { return getTextLength(); } + + inline void clearSelection() { m_selectionStart = m_selectionEnd = 0; } + + inline const QString& getText() const { return m_editText; } + inline QString getSelectedText() const { return m_editText.mid(m_selectionStart, getSelectionLength()); } + + /// Sets (updates) text selection + /// \param startPosition From where we are selecting text + /// \param selectionLength Selection length (positive - to the right, negative - to the left) + void setSelection(int startPosition, int selectionLength); + + /// Moves cursor position. It behaves as usual in text boxes, + /// when user moves the cursor. If \p select is true, then + /// selection is updated. + /// \param position New position of the cursor + /// \param select Select text when moving the cursor? + void setCursorPosition(int position, bool select); + + /// Sets text content of the widget. This functions sets the text, + /// even if widget is readonly. + /// \param text Text to be set + void setText(const QString& text); + + /// Sets widget appearance, such as font, font size, color, text alignment, + /// and rectangle, in which widget resides on page (in page coordinates) + /// \param appearance Appearance + /// \param textAlignment Text alignment + /// \param rect Widget rectangle in page coordinates + /// \param maxTextLength Maximal text length + void setAppearance(const PDFAnnotationDefaultAppearance& appearance, + Qt::Alignment textAlignment, + QRectF rect, + int maxTextLength); + + /// Sets widget appearance, such as font, font size, color, text alignment, + /// and rectangle, in which widget resides on page (in page coordinates) + /// \param font Font + /// \param textAlignment Text alignment + /// \param rect Widget rectangle in page coordinates + /// \param maxTextLength Maximal text length + /// \param textColor Color + void setAppearance(const QFont& font, + Qt::Alignment textAlignment, + QRectF rect, + int maxTextLength, + QColor textColor); + + void performCut(); + void performCopy(); + void performPaste(); + void performClear(); + void performSelectAll(); + void performBackspace(); + void performDelete(); + void performRemoveSelectedText(); + void performInsertText(const QString& text); + + /// Draw text edit using given parameters + /// \param parameters Parameters + void draw(AnnotationDrawParameters& parameters, bool edit) const; + + /// Returns valid cursor position retrieved from position in the widget. + /// \param point Point in page coordinate space + /// \param edit Are we using edit transformations? + /// \returns Cursor position + int getCursorPositionFromWidgetPosition(const QPointF& point, bool edit) const; + + inline int getCursorForward(QTextLayout::CursorMode mode) const { return getNextPrevCursorPosition(getSingleStepForward(), mode); } + inline int getCursorBackward(QTextLayout::CursorMode mode) const { return getNextPrevCursorPosition(getSingleStepBackward(), mode); } + inline int getCursorCharacterForward() const { return getCursorForward(QTextLayout::SkipCharacters); } + inline int getCursorCharacterBackward() const { return getCursorBackward(QTextLayout::SkipCharacters); } + inline int getCursorWordForward() const { return getCursorForward(QTextLayout::SkipWords); } + inline int getCursorWordBackward() const { return getCursorBackward(QTextLayout::SkipWords); } + inline int getCursorDocumentStart() const { return (getSingleStepForward() > 0) ? getPositionStart() : getPositionEnd(); } + inline int getCursorDocumentEnd() const { return (getSingleStepForward() > 0) ? getPositionEnd() : getPositionStart(); } + inline int getCursorLineStart() const { return (getSingleStepForward() > 0) ? getCurrentLineTextStart() : getCurrentLineTextEnd(); } + inline int getCursorLineEnd() const { return (getSingleStepForward() > 0) ? getCurrentLineTextEnd() : getCurrentLineTextStart(); } + inline int getCursorNextLine() const { return getCurrentLineTextEnd(); } + inline int getCursorPreviousLine() const { return getNextPrevCursorPosition(getCurrentLineTextStart(), -1, QTextLayout::SkipCharacters); } + + const QRectF& getWidgetRect() const { return m_widgetRect; } + QFont getFont() const { return m_textLayout.font(); } + QColor getFontColor() const { return m_textColor; } + +private: + /// This function does following things: + /// 1) Clamps edit text to fit maximum length + /// 2) Creates display string from edit string + /// 3) Updates text layout + void updateTextLayout(); + + /// Returns single step forward, which is determined + /// by cursor move style and layout direction. + int getSingleStepForward() const; + + /// Returns single step backward, which is determined + /// by cursor move style and layout direction. + int getSingleStepBackward() const { return -getSingleStepForward(); } + + /// Returns next/previous position, by number of steps, + /// using given cursor mode (skipping characters or whole words). + /// \param steps Number of steps to proceed (can be negative number) + /// \param mode Skip mode - letters or words? + int getNextPrevCursorPosition(int steps, QTextLayout::CursorMode mode) const { return getNextPrevCursorPosition(m_positionCursor, steps, mode); } + + /// Returns next/previous position from reference cursor position, by number of steps, + /// using given cursor mode (skipping characters or whole words). + /// \param referencePosition Reference cursor position + /// \param steps Number of steps to proceed (can be negative number) + /// \param mode Skip mode - letters or words? + int getNextPrevCursorPosition(int referencePosition, int steps, QTextLayout::CursorMode mode) const; + + /// Returns current line text start position + int getCurrentLineTextStart() const; + + /// Returns current line text end position + int getCurrentLineTextEnd() const; + + /// Creates text box transform matrix, which transforms from + /// widget space to page space. + /// \param edit Create matrix for text editing? + QMatrix createTextBoxTransformMatrix(bool edit) const; + + /// Returns vector of cursor positions (which may be not equal + /// to edit string length, because edit string can contain surrogates, + /// or graphemes, which are single glyphs, but represented by more + /// 16-bit unicode codes). + std::vector getCursorPositions() const; + + int getCursorLineUp() const; + int getCursorLineDown() const; + + PDFFormField::FieldFlags m_flags; + + /// Text edited by the user + QString m_editText; + + /// Text, which is displayed. It can differ from text + /// edited by user, in case password is being entered. + QString m_displayText; + + /// Text layout + QTextLayout m_textLayout; + + /// Character for displaying passwords + QChar m_passwordReplacementCharacter; + + int m_selectionStart; + int m_selectionEnd; + int m_positionCursor; + int m_maxTextLength; + + QRectF m_widgetRect; + QColor m_textColor; +}; + +} // namespace pdf + +#endif // PDFTEXTEDITPSEUDOWIDGET_H diff --git a/Pdf4QtLib/sources/pdfwidgettool.cpp b/Pdf4QtLib/sources/pdfwidgettool.cpp index 2c009b8..2201acb 100644 --- a/Pdf4QtLib/sources/pdfwidgettool.cpp +++ b/Pdf4QtLib/sources/pdfwidgettool.cpp @@ -106,6 +106,14 @@ void PDFWidgetTool::setActive(bool active) } } +void PDFWidgetTool::shortcutOverrideEvent(QWidget* widget, QKeyEvent* event) +{ + if (PDFWidgetTool* tool = getTopToolstackTool()) + { + tool->shortcutOverrideEvent(widget, event); + } +} + void PDFWidgetTool::keyPressEvent(QWidget* widget, QKeyEvent* event) { if (PDFWidgetTool* tool = getTopToolstackTool()) @@ -130,6 +138,14 @@ void PDFWidgetTool::mousePressEvent(QWidget* widget, QMouseEvent* event) } } +void PDFWidgetTool::mouseDoubleClickEvent(QWidget* widget, QMouseEvent* event) +{ + if (PDFWidgetTool* tool = getTopToolstackTool()) + { + tool->mouseDoubleClickEvent(widget, event); + } +} + void PDFWidgetTool::mouseReleaseEvent(QWidget* widget, QMouseEvent* event) { if (PDFWidgetTool* tool = getTopToolstackTool()) @@ -194,11 +210,13 @@ PDFWidgetTool* PDFWidgetTool::getTopToolstackTool() const void PDFWidgetTool::addTool(PDFWidgetTool* tool) { + tool->setActive(isActive()); m_toolStack.push_back(tool); } void PDFWidgetTool::removeTool() { + m_toolStack.back()->setActive(false); m_toolStack.pop_back(); } @@ -809,8 +827,12 @@ PDFMagnifierTool* PDFToolManager::getMagnifierTool() const void PDFToolManager::shortcutOverrideEvent(QWidget* widget, QKeyEvent* event) { - Q_UNUSED(widget); - Q_UNUSED(event); + event->ignore(); + + if (PDFWidgetTool* activeTool = getActiveTool()) + { + activeTool->shortcutOverrideEvent(widget, event); + } } void PDFToolManager::keyPressEvent(QWidget* widget, QKeyEvent* event) @@ -854,8 +876,12 @@ void PDFToolManager::mousePressEvent(QWidget* widget, QMouseEvent* event) void PDFToolManager::mouseDoubleClickEvent(QWidget* widget, QMouseEvent* event) { - Q_UNUSED(widget); - Q_UNUSED(event); + event->ignore(); + + if (PDFWidgetTool* activeTool = getActiveTool()) + { + activeTool->mouseDoubleClickEvent(widget, event); + } } void PDFToolManager::mouseReleaseEvent(QWidget* widget, QMouseEvent* event) @@ -1070,6 +1096,11 @@ void PDFPickTool::drawPage(QPainter* painter, Q_UNUSED(layoutGetter); Q_UNUSED(errors); + if (!isActive()) + { + return; + } + // If we are picking rectangles, then draw current selection rectangle if (m_mode == Mode::Rectangles && m_drawSelectionRectangle && m_pageIndex == pageIndex && !m_pickedPoints.empty()) { @@ -1097,6 +1128,11 @@ void PDFPickTool::drawPage(QPainter* painter, void PDFPickTool::drawPostRendering(QPainter* painter, QRect rect) const { + if (!isActive()) + { + return; + } + if (m_mode != Mode::Images) { m_snapper.drawSnapPoints(painter); diff --git a/Pdf4QtLib/sources/pdfwidgettool.h b/Pdf4QtLib/sources/pdfwidgettool.h index 01e334b..c85e78d 100644 --- a/Pdf4QtLib/sources/pdfwidgettool.h +++ b/Pdf4QtLib/sources/pdfwidgettool.h @@ -62,6 +62,11 @@ public: /// Returns action for activating/deactivating this tool QAction* getAction() const { return m_action; } + /// Handles shortcut override event from widget, over which tool operates + /// \param widget Widget, over which tool operates + /// \param event Event + virtual void shortcutOverrideEvent(QWidget* widget, QKeyEvent* event); + /// Handles key press event from widget, over which tool operates /// \param widget Widget, over which tool operates /// \param event Event @@ -77,6 +82,11 @@ public: /// \param event Event virtual void mousePressEvent(QWidget* widget, QMouseEvent* event); + /// Handles mouse double click event from widget, over which tool operates + /// \param widget Widget, over which tool operates + /// \param event Event + virtual void mouseDoubleClickEvent(QWidget* widget, QMouseEvent* event); + /// Handles mouse release event from widget, over which tool operates /// \param widget Widget, over which tool operates /// \param event Event diff --git a/Pdf4QtViewerPlugins/SignaturePlugin/signatureplugin.cpp b/Pdf4QtViewerPlugins/SignaturePlugin/signatureplugin.cpp index 03c33d1..69c46b6 100644 --- a/Pdf4QtViewerPlugins/SignaturePlugin/signatureplugin.cpp +++ b/Pdf4QtViewerPlugins/SignaturePlugin/signatureplugin.cpp @@ -122,6 +122,7 @@ void SignaturePlugin::setWidget(pdf::PDFWidget* widget) rejectMarkFile.close(); } + m_tools[TextTool] = new pdf::PDFCreatePCElementTextTool(widget->getDrawWidgetProxy(), &m_scene, createTextAction, this); m_tools[FreehandCurveTool] = new pdf::PDFCreatePCElementFreehandCurveTool(widget->getDrawWidgetProxy(), &m_scene, createFreehandCurveAction, this); m_tools[AcceptMarkTool] = new pdf::PDFCreatePCElementSvgTool(widget->getDrawWidgetProxy(), &m_scene, createAcceptMarkAction, acceptMarkContent, this); m_tools[RejectMarkTool] = new pdf::PDFCreatePCElementSvgTool(widget->getDrawWidgetProxy(), &m_scene, createRejectMarkAction, rejectMarkContent, this); diff --git a/Pdf4QtViewerPlugins/SignaturePlugin/signatureplugin.h b/Pdf4QtViewerPlugins/SignaturePlugin/signatureplugin.h index 997d6ae..29a7fce 100644 --- a/Pdf4QtViewerPlugins/SignaturePlugin/signatureplugin.h +++ b/Pdf4QtViewerPlugins/SignaturePlugin/signatureplugin.h @@ -79,6 +79,7 @@ private: enum Tools { + TextTool, FreehandCurveTool, AcceptMarkTool, RejectMarkTool, From f5881b897d557da42c2679f830700ea9505e21ca Mon Sep 17 00:00:00 2001 From: Jakub Melka Date: Sun, 27 Mar 2022 19:53:08 +0200 Subject: [PATCH 20/39] Signature plugin: Create text tool bugfixing --- .../sources/pdfpagecontenteditortools.cpp | 32 +++++++++++++------ Pdf4QtLib/sources/pdfpagecontenteditortools.h | 1 + Pdf4QtLib/sources/pdfpagecontentelements.cpp | 1 + 3 files changed, 25 insertions(+), 9 deletions(-) diff --git a/Pdf4QtLib/sources/pdfpagecontenteditortools.cpp b/Pdf4QtLib/sources/pdfpagecontenteditortools.cpp index 8b7a06a..011bb7f 100644 --- a/Pdf4QtLib/sources/pdfpagecontenteditortools.cpp +++ b/Pdf4QtLib/sources/pdfpagecontenteditortools.cpp @@ -517,7 +517,7 @@ PDFCreatePCElementTextTool::PDFCreatePCElementTextTool(PDFDrawWidgetProxy* proxy m_element->setPen(QPen(Qt::SolidLine)); m_element->setFont(font); - m_textEditWidget = new PDFTextEditPseudowidget(PDFFormField::FieldFlags()); + m_textEditWidget = new PDFTextEditPseudowidget(PDFFormField::Multiline); } PDFCreatePCElementTextTool::~PDFCreatePCElementTextTool() @@ -554,6 +554,18 @@ void PDFCreatePCElementTextTool::drawPage(QPainter* painter, } } +void PDFCreatePCElementTextTool::resetTool() +{ + m_textEditWidget->setText(QString()); + m_element->setText(QString()); + m_element->setPageIndex(-1); + + if (getTopToolstackTool()) + { + removeTool(); + } +} + void PDFCreatePCElementTextTool::setActiveImpl(bool active) { BaseClass::setActiveImpl(active); @@ -565,13 +577,7 @@ void PDFCreatePCElementTextTool::setActiveImpl(bool active) } else { - m_textEditWidget->setText(QString()); - m_element->setText(QString()); - - if (getTopToolstackTool()) - { - removeTool(); - } + resetTool(); } m_pickTool->setActive(active); @@ -598,6 +604,14 @@ void PDFCreatePCElementTextTool::onRectanglePicked(PDFInteger pageIndex, QRectF void PDFCreatePCElementTextTool::finishEditing() { + m_element->setText(m_textEditWidget->getText()); + + if (!m_element->getText().isEmpty()) + { + m_scene->addElement(m_element->clone()); + } + + resetTool(); setActive(false); } @@ -745,7 +759,7 @@ void PDFCreatePCElementTextTool::wheelEvent(QWidget* widget, QWheelEvent* event) { if (isEditing()) { - + event->ignore(); } else { diff --git a/Pdf4QtLib/sources/pdfpagecontenteditortools.h b/Pdf4QtLib/sources/pdfpagecontenteditortools.h index 1a4211c..959d54a 100644 --- a/Pdf4QtLib/sources/pdfpagecontenteditortools.h +++ b/Pdf4QtLib/sources/pdfpagecontenteditortools.h @@ -237,6 +237,7 @@ private: void onRectanglePicked(pdf::PDFInteger pageIndex, QRectF pageRectangle); void finishEditing(); + void resetTool(); std::optional getPagePointUnderMouse(QMouseEvent* event) const; bool isEditing() const; diff --git a/Pdf4QtLib/sources/pdfpagecontentelements.cpp b/Pdf4QtLib/sources/pdfpagecontentelements.cpp index bde5512..8e8246c 100644 --- a/Pdf4QtLib/sources/pdfpagecontentelements.cpp +++ b/Pdf4QtLib/sources/pdfpagecontentelements.cpp @@ -2274,6 +2274,7 @@ void PDFPageContentElementTextBox::drawPage(QPainter* painter, painter->setRenderHint(QPainter::Antialiasing); painter->setClipRect(rect, Qt::IntersectClip); painter->translate(rect.center()); + painter->scale(1.0, -1.0); painter->rotate(getAngle()); QTextOption option; From 5d8e2818a2644be71b857bae47d42f1f22fe5df9 Mon Sep 17 00:00:00 2001 From: Jakub Melka Date: Sat, 2 Apr 2022 16:29:31 +0200 Subject: [PATCH 21/39] Signature plugin: selection --- .../sources/pdfpagecontenteditorwidget.cpp | 109 +++++++++++++++++- .../sources/pdfpagecontenteditorwidget.h | 16 +++ .../sources/pdfpagecontenteditorwidget.ui | 6 +- Pdf4QtLib/sources/pdfpagecontentelements.cpp | 56 +++++++++ Pdf4QtLib/sources/pdfpagecontentelements.h | 19 +++ .../SignaturePlugin/signatureplugin.cpp | 28 ++++- .../SignaturePlugin/signatureplugin.h | 3 + 7 files changed, 233 insertions(+), 4 deletions(-) diff --git a/Pdf4QtLib/sources/pdfpagecontenteditorwidget.cpp b/Pdf4QtLib/sources/pdfpagecontenteditorwidget.cpp index ee89cb8..4830367 100644 --- a/Pdf4QtLib/sources/pdfpagecontenteditorwidget.cpp +++ b/Pdf4QtLib/sources/pdfpagecontenteditorwidget.cpp @@ -19,6 +19,7 @@ #include "ui_pdfpagecontenteditorwidget.h" #include "pdfwidgetutils.h" #include "pdfpagecontentelements.h" +#include "pdfutils.h" #include #include @@ -26,10 +27,12 @@ namespace pdf { -PDFPageContentEditorWidget::PDFPageContentEditorWidget(QWidget *parent) : +PDFPageContentEditorWidget::PDFPageContentEditorWidget(QWidget* parent) : QDockWidget(parent), ui(new Ui::PDFPageContentEditorWidget), - m_toolBoxColumnCount(6) + m_toolBoxColumnCount(6), + m_scene(nullptr), + m_selectionChangeEnabled(true) { ui->setupUi(this); @@ -76,6 +79,7 @@ PDFPageContentEditorWidget::PDFPageContentEditorWidget(QWidget *parent) : connect(&m_actionMapper, &QSignalMapper::mappedObject, this, &PDFPageContentEditorWidget::onActionTriggerRequest); connect(&m_operationMapper, &QSignalMapper::mappedInt, this, &PDFPageContentEditorWidget::operationTriggered); + connect(ui->itemsListWidget->selectionModel(), &QItemSelectionModel::selectionChanged, this, &PDFPageContentEditorWidget::onItemSelectionChanged); } PDFPageContentEditorWidget::~PDFPageContentEditorWidget() @@ -126,6 +130,56 @@ QToolButton* PDFPageContentEditorWidget::getToolButtonForOperation(int operation return qobject_cast(m_operationMapper.mapping(operation)); } +void PDFPageContentEditorWidget::updateItemsInListWidget() +{ + ui->itemsListWidget->setUpdatesEnabled(false); + + if (m_scene) + { + std::set presentElementIds; + std::set elementIds = m_scene->getElementIds(); + + // Remove items which are not here + for (int i = 0; i < ui->itemsListWidget->count();) + { + QListWidgetItem* item = ui->itemsListWidget->item(i); + const PDFInteger elementId = item->data(Qt::UserRole).toLongLong(); + if (!elementIds.count(elementId)) + { + delete ui->itemsListWidget->takeItem(i); + } + else + { + presentElementIds.insert(elementId); + ++i; + } + } + + // Add items which are here + for (PDFInteger elementId : elementIds) + { + if (presentElementIds.count(elementId)) + { + continue; + } + + const PDFPageContentElement* element = m_scene->getElementById(elementId); + Q_ASSERT(element); + + QListWidgetItem* item = new QListWidgetItem(element->getDescription()); + item->setData(Qt::UserRole, elementId); + + ui->itemsListWidget->addItem(item); + } + } + else + { + ui->itemsListWidget->clear(); + } + + ui->itemsListWidget->setUpdatesEnabled(true); +} + void PDFPageContentEditorWidget::onActionTriggerRequest(QObject* actionObject) { QAction* action = qobject_cast(actionObject); @@ -146,4 +200,55 @@ void PDFPageContentEditorWidget::onActionChanged() button->setEnabled(action->isEnabled()); } +void PDFPageContentEditorWidget::onItemSelectionChanged() +{ + if (m_selectionChangeEnabled) + { + emit itemSelectionChangedByUser(); + } +} + +PDFPageContentScene* PDFPageContentEditorWidget::scene() const +{ + return m_scene; +} + +void PDFPageContentEditorWidget::setScene(PDFPageContentScene* newScene) +{ + if (m_scene != newScene) + { + m_scene = newScene; + updateItemsInListWidget(); + } +} + +std::set PDFPageContentEditorWidget::getSelection() const +{ + std::set result; + + for (int i = 0; i < ui->itemsListWidget->count(); ++i) + { + QListWidgetItem* item = ui->itemsListWidget->item(i); + if (item->isSelected()) + { + const PDFInteger elementId = item->data(Qt::UserRole).toLongLong(); + result.insert(elementId); + } + } + + return result; +} + +void PDFPageContentEditorWidget::setSelection(const std::set& selection) +{ + pdf::PDFTemporaryValueChange guard(&m_selectionChangeEnabled, false); + + for (int i = 0; i < ui->itemsListWidget->count(); ++i) + { + QListWidgetItem* item = ui->itemsListWidget->item(i); + const PDFInteger elementId = item->data(Qt::UserRole).toLongLong(); + item->setSelected(selection.count(elementId)); + } +} + } // namespace pdf diff --git a/Pdf4QtLib/sources/pdfpagecontenteditorwidget.h b/Pdf4QtLib/sources/pdfpagecontenteditorwidget.h index 995f11e..2225c91 100644 --- a/Pdf4QtLib/sources/pdfpagecontenteditorwidget.h +++ b/Pdf4QtLib/sources/pdfpagecontenteditorwidget.h @@ -23,6 +23,8 @@ #include #include +#include + class QToolButton; namespace Ui @@ -32,6 +34,7 @@ class PDFPageContentEditorWidget; namespace pdf { +class PDFPageContentScene; class PDF4QTLIBSHARED_EXPORT PDFPageContentEditorWidget : public QDockWidget { @@ -46,18 +49,31 @@ public: QToolButton* getToolButtonForOperation(int operation) const; + /// Update items in list widget + void updateItemsInListWidget(); + + PDFPageContentScene* scene() const; + void setScene(PDFPageContentScene* newScene); + + std::set getSelection() const; + void setSelection(const std::set& selection); + signals: void operationTriggered(int operation); + void itemSelectionChangedByUser(); private: void onActionTriggerRequest(QObject* actionObject); void onActionChanged(); + void onItemSelectionChanged(); Ui::PDFPageContentEditorWidget* ui; QSignalMapper m_actionMapper; QSignalMapper m_operationMapper; int m_toolBoxColumnCount; QSize m_toolButtonIconSize; + PDFPageContentScene* m_scene; + bool m_selectionChangeEnabled; }; } // namespace pdf diff --git a/Pdf4QtLib/sources/pdfpagecontenteditorwidget.ui b/Pdf4QtLib/sources/pdfpagecontenteditorwidget.ui index 3f9e52f..0aa8987 100644 --- a/Pdf4QtLib/sources/pdfpagecontenteditorwidget.ui +++ b/Pdf4QtLib/sources/pdfpagecontenteditorwidget.ui @@ -170,7 +170,11 @@ - + + + QAbstractItemView::ExtendedSelection + + diff --git a/Pdf4QtLib/sources/pdfpagecontentelements.cpp b/Pdf4QtLib/sources/pdfpagecontentelements.cpp index 8e8246c..800875d 100644 --- a/Pdf4QtLib/sources/pdfpagecontentelements.cpp +++ b/Pdf4QtLib/sources/pdfpagecontentelements.cpp @@ -207,6 +207,11 @@ void PDFPageContentElement::performRectangleSetSize(QRectF& rectangle, QSizeF si rectangle.translate(0, offset); } +QString PDFPageContentElement::formatDescription(const QString& description) const +{ + return PDFTranslationContext::tr("#%1: %2").arg(getElementId()).arg(description); +} + const QPen& PDFPageContentStyledElement::getPen() const { return m_pen; @@ -314,6 +319,11 @@ void PDFPageContentElementRectangle::setSize(QSizeF size) performRectangleSetSize(m_rectangle, size); } +QString PDFPageContentElementRectangle::getDescription() const +{ + return formatDescription(isRounded() ? PDFTranslationContext::tr("Rounded rectangle") : PDFTranslationContext::tr("Rectangle")); +} + PDFPageContentScene::PDFPageContentScene(QObject* parent) : QObject(parent), m_firstFreeId(1), @@ -790,6 +800,7 @@ void PDFPageContentScene::updateMouseCursor(const MouseEventInfo& info, PDFReal void PDFPageContentScene::onSelectionChanged() { emit sceneChanged(true); + emit selectionChanged(); } PDFWidget* PDFPageContentScene::widget() const @@ -835,6 +846,26 @@ std::set PDFPageContentScene::getElementIds() const return result; } +std::set PDFPageContentScene::getSelectedElementIds() const +{ + std::set result; + + for (const auto& element : m_elements) + { + if (m_manipulator.isSelected(element->getElementId())) + { + result.insert(element->getElementId()); + } + } + + return result; +} + +void PDFPageContentScene::setSelectedElementIds(const std::set& selectedElementIds) +{ + m_manipulator.selectNew(selectedElementIds); +} + void PDFPageContentScene::removeElementsById(const std::vector& selection) { const size_t oldSize = m_elements.size(); @@ -1000,6 +1031,11 @@ void PDFPageContentElementLine::setSize(QSizeF size) m_line.setPoints(p1, p2); } +QString PDFPageContentElementLine::getDescription() const +{ + return formatDescription(PDFTranslationContext::tr("Line")); +} + PDFPageContentElementLine::LineGeometry PDFPageContentElementLine::getGeometry() const { return m_geometry; @@ -1110,6 +1146,11 @@ void PDFPageContentSvgElement::setSize(QSizeF size) performRectangleSetSize(m_rectangle, size); } +QString PDFPageContentSvgElement::getDescription() const +{ + return formatDescription(PDFTranslationContext::tr("SVG image")); +} + const QByteArray& PDFPageContentSvgElement::getContent() const { return m_content; @@ -1207,6 +1248,11 @@ void PDFPageContentElementDot::setSize(QSizeF size) Q_UNUSED(size); } +QString PDFPageContentElementDot::getDescription() const +{ + return formatDescription(PDFTranslationContext::tr("Dot")); +} + QPointF PDFPageContentElementDot::getPoint() const { return m_point; @@ -1297,6 +1343,11 @@ void PDFPageContentElementFreehandCurve::setSize(QSizeF size) Q_UNUSED(size); } +QString PDFPageContentElementFreehandCurve::getDescription() const +{ + return formatDescription(PDFTranslationContext::tr("Freehand curve")); +} + QPainterPath PDFPageContentElementFreehandCurve::getCurve() const { return m_curve; @@ -2304,6 +2355,11 @@ void PDFPageContentElementTextBox::setSize(QSizeF size) performRectangleSetSize(m_rectangle, size); } +QString PDFPageContentElementTextBox::getDescription() const +{ + return formatDescription(PDFTranslationContext::tr("Text box '%1'").arg(getText())); +} + const QString& PDFPageContentElementTextBox::getText() const { return m_text; diff --git a/Pdf4QtLib/sources/pdfpagecontentelements.h b/Pdf4QtLib/sources/pdfpagecontentelements.h index 27e6c00..20d3078 100644 --- a/Pdf4QtLib/sources/pdfpagecontentelements.h +++ b/Pdf4QtLib/sources/pdfpagecontentelements.h @@ -71,6 +71,9 @@ public: /// nothing for elements, which does not support it. virtual void setSize(QSizeF size) = 0; + /// Returns description of the element + virtual QString getDescription() const = 0; + PDFInteger getPageIndex() const; void setPageIndex(PDFInteger newPageIndex); @@ -108,6 +111,8 @@ protected: void performRectangleSetSize(QRectF& rectangle, QSizeF size); + QString formatDescription(const QString& description) const; + PDFInteger m_elementId = -1; PDFInteger m_pageIndex = -1; }; @@ -155,6 +160,7 @@ public: virtual void performManipulation(uint mode, const QPointF& offset) override; virtual QRectF getBoundingBox() const override; virtual void setSize(QSizeF size); + virtual QString getDescription() const override; private: bool m_rounded = false; @@ -188,6 +194,7 @@ public: virtual void performManipulation(uint mode, const QPointF& offset) override; virtual QRectF getBoundingBox() const override; virtual void setSize(QSizeF size); + virtual QString getDescription() const override; LineGeometry getGeometry() const; void setGeometry(LineGeometry newGeometry); @@ -220,6 +227,7 @@ public: virtual void performManipulation(uint mode, const QPointF& offset) override; virtual QRectF getBoundingBox() const override; virtual void setSize(QSizeF size); + virtual QString getDescription() const override; QPointF getPoint() const; void setPoint(QPointF newPoint); @@ -248,6 +256,7 @@ public: virtual void performManipulation(uint mode, const QPointF& offset) override; virtual QRectF getBoundingBox() const override; virtual void setSize(QSizeF size); + virtual QString getDescription() const override; QPainterPath getCurve() const; void setCurve(QPainterPath newCurve); @@ -282,6 +291,7 @@ public: virtual void performManipulation(uint mode, const QPointF& offset) override; virtual QRectF getBoundingBox() const override; virtual void setSize(QSizeF size); + virtual QString getDescription() const override; const QByteArray& getContent() const; void setContent(const QByteArray& newContent); @@ -318,6 +328,7 @@ public: virtual void performManipulation(uint mode, const QPointF& offset) override; virtual QRectF getBoundingBox() const override; virtual void setSize(QSizeF size); + virtual QString getDescription() const override; const QString& getText() const; void setText(const QString& newText); @@ -489,6 +500,12 @@ public: /// Returns set of all element ids std::set getElementIds() const; + /// Returns set of selected element ids + std::set getSelectedElementIds() const; + + /// Set selected items + void setSelectedElementIds(const std::set& selectedElementIds); + /// Removes elements specified in selection /// \param selection Items to be removed void removeElementsById(const std::vector& selection); @@ -528,6 +545,8 @@ signals: /// This signal is emitted when scene has changed (including graphics) void sceneChanged(bool graphicsOnly); + void selectionChanged(); + private: struct MouseEventInfo diff --git a/Pdf4QtViewerPlugins/SignaturePlugin/signatureplugin.cpp b/Pdf4QtViewerPlugins/SignaturePlugin/signatureplugin.cpp index 69c46b6..9f445f1 100644 --- a/Pdf4QtViewerPlugins/SignaturePlugin/signatureplugin.cpp +++ b/Pdf4QtViewerPlugins/SignaturePlugin/signatureplugin.cpp @@ -17,6 +17,7 @@ #include "signatureplugin.h" #include "pdfdrawwidget.h" +#include "pdfutils.h" #include "pdfpagecontenteditorwidget.h" #include @@ -31,7 +32,8 @@ SignaturePlugin::SignaturePlugin() : m_actions({ }), m_tools({ }), m_editorWidget(nullptr), - m_scene(nullptr) + m_scene(nullptr), + m_sceneSelectionChangeEnabled(true) { } @@ -143,6 +145,7 @@ void SignaturePlugin::setWidget(pdf::PDFWidget* widget) m_widget->getDrawWidgetProxy()->registerDrawInterface(&m_scene); m_scene.setWidget(m_widget); connect(&m_scene, &pdf::PDFPageContentScene::sceneChanged, this, &SignaturePlugin::onSceneChanged); + connect(&m_scene, &pdf::PDFPageContentScene::selectionChanged, this, &SignaturePlugin::onSceneSelectionChanged); connect(clearAction, &QAction::triggered, &m_scene, &pdf::PDFPageContentScene::clear); connect(activateAction, &QAction::triggered, this, &SignaturePlugin::setActive); @@ -179,9 +182,30 @@ void SignaturePlugin::onSceneChanged(bool graphicsOnly) updateActions(); } + if (m_editorWidget) + { + m_editorWidget->updateItemsInListWidget(); + } + updateGraphics(); } +void SignaturePlugin::onSceneSelectionChanged() +{ + if (m_editorWidget && m_sceneSelectionChangeEnabled) + { + m_editorWidget->setSelection(m_scene.getSelectedElementIds()); + } +} + +void SignaturePlugin::onWidgetSelectionChanged() +{ + Q_ASSERT(m_editorWidget); + + pdf::PDFTemporaryValueChange guard(&m_sceneSelectionChangeEnabled, false); + m_scene.setSelectedElementIds(m_editorWidget->getSelection()); +} + void SignaturePlugin::setActive(bool active) { if (m_scene.isActive() != active) @@ -276,7 +300,9 @@ void SignaturePlugin::updateDockWidget() m_dataExchangeInterface->getMainWindow()->addDockWidget(Qt::RightDockWidgetArea, m_editorWidget, Qt::Vertical); m_editorWidget->setFloating(false); m_editorWidget->setWindowTitle(tr("Signature Toolbox")); + m_editorWidget->setScene(&m_scene); connect(m_editorWidget, &pdf::PDFPageContentEditorWidget::operationTriggered, &m_scene, &pdf::PDFPageContentScene::performOperation); + connect(m_editorWidget, &pdf::PDFPageContentEditorWidget::itemSelectionChangedByUser, this, &SignaturePlugin::onWidgetSelectionChanged); m_editorWidget->getToolButtonForOperation(static_cast(pdf::PDFPageContentElementManipulator::Operation::AlignTop))->setIcon(QIcon(":/resources/pce-align-top.svg")); m_editorWidget->getToolButtonForOperation(static_cast(pdf::PDFPageContentElementManipulator::Operation::AlignCenterVertically))->setIcon(QIcon(":/resources/pce-align-v-center.svg")); diff --git a/Pdf4QtViewerPlugins/SignaturePlugin/signatureplugin.h b/Pdf4QtViewerPlugins/SignaturePlugin/signatureplugin.h index 29a7fce..7acdcc0 100644 --- a/Pdf4QtViewerPlugins/SignaturePlugin/signatureplugin.h +++ b/Pdf4QtViewerPlugins/SignaturePlugin/signatureplugin.h @@ -49,6 +49,8 @@ public: private: void onSceneChanged(bool graphicsOnly); + void onSceneSelectionChanged(); + void onWidgetSelectionChanged(); enum Action { @@ -103,6 +105,7 @@ private: pdf::PDFPageContentEditorWidget* m_editorWidget; pdf::PDFPageContentScene m_scene; + bool m_sceneSelectionChangeEnabled; }; } // namespace pdfplugin From e5dd0106097d3ec256f173f1bbace5cc42d37294 Mon Sep 17 00:00:00 2001 From: Jakub Melka Date: Wed, 6 Apr 2022 20:36:18 +0200 Subject: [PATCH 22/39] Signature plugin: Appearance settings --- Pdf4QtLib/Pdf4QtLib.pro | 3 + .../pdfpagecontenteditorstylesettings.cpp | 298 ++++++++++++++++++ .../pdfpagecontenteditorstylesettings.h | 99 ++++++ .../pdfpagecontenteditorstylesettings.ui | 203 ++++++++++++ .../sources/pdfpagecontenteditorwidget.cpp | 1 + .../sources/pdfpagecontenteditorwidget.ui | 15 +- 6 files changed, 618 insertions(+), 1 deletion(-) create mode 100644 Pdf4QtLib/sources/pdfpagecontenteditorstylesettings.cpp create mode 100644 Pdf4QtLib/sources/pdfpagecontenteditorstylesettings.h create mode 100644 Pdf4QtLib/sources/pdfpagecontenteditorstylesettings.ui diff --git a/Pdf4QtLib/Pdf4QtLib.pro b/Pdf4QtLib/Pdf4QtLib.pro index 4cf97f3..0b1b258 100644 --- a/Pdf4QtLib/Pdf4QtLib.pro +++ b/Pdf4QtLib/Pdf4QtLib.pro @@ -71,6 +71,7 @@ SOURCES += \ sources/pdfoptimizer.cpp \ sources/pdfoptionalcontent.cpp \ sources/pdfoutline.cpp \ + sources/pdfpagecontenteditorstylesettings.cpp \ sources/pdfpagecontenteditortools.cpp \ sources/pdfpagecontenteditorwidget.cpp \ sources/pdfpagecontentelements.cpp \ @@ -150,6 +151,7 @@ HEADERS += \ sources/pdfoptimizer.h \ sources/pdfoptionalcontent.h \ sources/pdfoutline.h \ + sources/pdfpagecontenteditorstylesettings.h \ sources/pdfpagecontenteditortools.h \ sources/pdfpagecontenteditorwidget.h \ sources/pdfpagecontentelements.h \ @@ -201,6 +203,7 @@ HEADERS += \ sources/pdfimage.h FORMS += \ + sources/pdfpagecontenteditorstylesettings.ui \ sources/pdfpagecontenteditorwidget.ui \ sources/pdfrenderingerrorswidget.ui \ sources/pdfselectpagesdialog.ui diff --git a/Pdf4QtLib/sources/pdfpagecontenteditorstylesettings.cpp b/Pdf4QtLib/sources/pdfpagecontenteditorstylesettings.cpp new file mode 100644 index 0000000..516cf73 --- /dev/null +++ b/Pdf4QtLib/sources/pdfpagecontenteditorstylesettings.cpp @@ -0,0 +1,298 @@ +// Copyright (C) 2022 Jakub Melka +// +// This file is part of PDF4QT. +// +// PDF4QT 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 +// with the written consent of the copyright owner, any later version. +// +// PDF4QT 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 PDF4QT. If not, see . + +#include "pdfpagecontenteditorstylesettings.h" +#include "ui_pdfpagecontenteditorstylesettings.h" + +#include "pdfwidgetutils.h" +#include "pdfpagecontentelements.h" + +#include +#include + +namespace pdf +{ + +PDFPageContentEditorStyleSettings::PDFPageContentEditorStyleSettings(QWidget* parent) : + QWidget(parent), + ui(new Ui::PDFPageContentEditorStyleSettings) +{ + ui->setupUi(this); + + for (QString colorName : QColor::colorNames()) + { + QColor color(colorName); + QIcon icon = getIconForColor(color); + + ui->penColorCombo->addItem(icon, colorName, color); + ui->brushColorCombo->addItem(icon, colorName, color); + } + + ui->penStyleCombo->addItem(tr("None"), int(Qt::NoPen)); + ui->penStyleCombo->addItem(tr("Solid"), int(Qt::SolidLine)); + ui->penStyleCombo->addItem(tr("Dashed"), int(Qt::DashLine)); + ui->penStyleCombo->addItem(tr("Dotted"), int(Qt::DotLine)); + ui->penStyleCombo->addItem(tr("Dash-dot"), int(Qt::DashDotLine)); + ui->penStyleCombo->addItem(tr("Dash-dot-dot"), int(Qt::DashDotDotLine)); + + connect(ui->fontComboBox, &QFontComboBox::currentFontChanged, this, &PDFPageContentEditorStyleSettings::onFontChanged); + connect(ui->selectPenColorButton, &QToolButton::clicked, this, &PDFPageContentEditorStyleSettings::onSelectPenColorButtonClicked); + connect(ui->selectBrushColorButton, &QToolButton::clicked, this, &PDFPageContentEditorStyleSettings::onSelectBrushColorButtonClicked); + connect(ui->selectFontButton, &QToolButton::clicked, this, &PDFPageContentEditorStyleSettings::onSelectFontButtonClicked); + + m_alignmentMapper.setMapping(ui->al11Button, int(Qt::AlignLeft | Qt::AlignTop)); + m_alignmentMapper.setMapping(ui->al12Button, int(Qt::AlignHCenter | Qt::AlignTop)); + m_alignmentMapper.setMapping(ui->al13Button, int(Qt::AlignRight | Qt::AlignTop)); + m_alignmentMapper.setMapping(ui->al21Button, int(Qt::AlignLeft | Qt::AlignVCenter)); + m_alignmentMapper.setMapping(ui->al22Button, int(Qt::AlignCenter)); + m_alignmentMapper.setMapping(ui->al23Button, int(Qt::AlignRight | Qt::AlignVCenter)); + m_alignmentMapper.setMapping(ui->al31Button, int(Qt::AlignLeft | Qt::AlignBottom)); + m_alignmentMapper.setMapping(ui->al32Button, int(Qt::AlignHCenter | Qt::AlignBottom)); + m_alignmentMapper.setMapping(ui->al33Button, int(Qt::AlignRight | Qt::AlignBottom)); + + loadFromElement(nullptr, true); +} + +PDFPageContentEditorStyleSettings::~PDFPageContentEditorStyleSettings() +{ + delete ui; +} + +void PDFPageContentEditorStyleSettings::loadFromElement(const PDFPageContentElement* element, bool forceUpdate) +{ + const PDFPageContentStyledElement* styledElement = dynamic_cast(element); + const PDFPageContentElementTextBox* textElement = dynamic_cast(element); + + StyleFeatures features = None; + + if (styledElement) + { + features.setFlag(Pen); + features.setFlag(PenColor); + features.setFlag(Brush); + } + + if (textElement) + { + features.setFlag(PenColor); + features.setFlag(Text); + } + + const bool hasPen = features.testFlag(Pen); + const bool hasPenColor = features.testFlag(PenColor); + const bool hasBrush = features.testFlag(Brush); + const bool hasText = features.testFlag(Text); + + ui->penWidthEdit->setEnabled(hasPen); + ui->penWidthLabel->setEnabled(hasPen); + + ui->penStyleCombo->setEnabled(hasPen); + ui->penStyleLabel->setEnabled(hasPen); + + ui->penColorCombo->setEnabled(hasPenColor); + ui->penColorLabel->setEnabled(hasPenColor); + ui->selectPenColorButton->setEnabled(hasPenColor); + + ui->brushColorCombo->setEnabled(hasBrush); + ui->brushColorLabel->setEnabled(hasBrush); + ui->selectBrushColorButton->setEnabled(hasBrush); + + ui->fontComboBox->setEnabled(hasText); + ui->fontLabel->setEnabled(hasText); + ui->selectFontButton->setEnabled(hasText); + + for (QRadioButton* radioButton : findChildren()) + { + radioButton->setEnabled(hasText); + } + ui->textAlignmentLabel->setEnabled(hasText); + + QPen pen(Qt::SolidLine); + QBrush brush(Qt::transparent); + QFont font = QGuiApplication::font(); + Qt::Alignment alignment = Qt::AlignCenter; + + if (styledElement) + { + pen = styledElement->getPen(); + brush = styledElement->getBrush(); + } + + if (textElement) + { + font = textElement->getFont(); + alignment = textElement->getAlignment(); + } + + setPen(pen, forceUpdate); + setBrush(brush, forceUpdate); + setFont(font, forceUpdate); + setFontAlignment(alignment, forceUpdate); +} + +void PDFPageContentEditorStyleSettings::setPen(const QPen& pen, bool forceUpdate) +{ + if (m_pen != pen || forceUpdate) + { + const bool oldBlockSignals = blockSignals(true); + + m_pen = pen; + ui->penWidthEdit->setValue(pen.widthF()); + ui->penStyleCombo->setCurrentIndex(ui->penStyleCombo->findData(int(pen.style()))); + setColorToComboBox(ui->penColorCombo, pen.color()); + + blockSignals(oldBlockSignals); + emit penChanged(m_pen); + } +} + +void PDFPageContentEditorStyleSettings::setBrush(const QBrush& brush, bool forceUpdate) +{ + if (m_brush != brush || forceUpdate) + { + const bool oldBlockSignals = blockSignals(true); + + m_brush = brush; + setColorToComboBox(ui->brushColorCombo, brush.color()); + + blockSignals(oldBlockSignals); + emit brushChanged(m_brush); + } +} + +void PDFPageContentEditorStyleSettings::setFont(const QFont& font, bool forceUpdate) +{ + if (m_font != font || forceUpdate) + { + const bool oldBlockSignals = blockSignals(true); + + m_font = font; + ui->fontComboBox->setCurrentFont(m_font); + + blockSignals(oldBlockSignals); + emit fontChanged(m_font); + } +} + +void PDFPageContentEditorStyleSettings::setFontAlignment(Qt::Alignment alignment, bool forceUpdate) +{ + if (m_alignment != alignment || forceUpdate) + { + const bool oldBlockSignals = blockSignals(true); + + for (QRadioButton* radioButton : findChildren()) + { + radioButton->setChecked(false); + } + + QRadioButton* radioButton = qobject_cast(m_alignmentMapper.mapping(int(alignment))); + radioButton->setChecked(true); + + blockSignals(oldBlockSignals); + emit alignmentChanged(m_alignment); + } +} + +QIcon PDFPageContentEditorStyleSettings::getIconForColor(QColor color) const +{ + QIcon icon; + + QSize iconSize = PDFWidgetUtils::scaleDPI(this, QSize(16, 16)); + + QPixmap pixmap(iconSize.width(), iconSize.height()); + pixmap.fill(color); + icon.addPixmap(pixmap); + + return icon; +} + +void PDFPageContentEditorStyleSettings::setColorToComboBox(QComboBox* comboBox, QColor color) +{ + if (!color.isValid()) + { + return; + } + + QString name = color.name(QColor::HexArgb); + + const int index = comboBox->findText(name); + if (index != -1) + { + comboBox->setCurrentIndex(index); + } + else + { + comboBox->addItem(getIconForColor(color), name, color); + comboBox->setCurrentIndex(comboBox->count() - 1); + } +} + +void PDFPageContentEditorStyleSettings::onSelectFontButtonClicked() +{ + bool ok = false; + QFont font = QFontDialog::getFont(&ok, m_font, this, tr("Select Font")); + + if (ok && m_font != font) + { + m_font = font; + ui->fontComboBox->setCurrentFont(m_font); + emit fontChanged(m_font); + } +} + +void PDFPageContentEditorStyleSettings::setPenColor(QColor color) +{ + if (color.isValid() && m_pen.color() != color) + { + m_pen.setColor(color); + setColorToComboBox(ui->penColorCombo, color); + emit penChanged(m_pen); + } +} + +void PDFPageContentEditorStyleSettings::onSelectPenColorButtonClicked() +{ + QColor color = QColorDialog::getColor(m_pen.color(), this, tr("Select Color for Pen"), QColorDialog::ShowAlphaChannel); + setPenColor(color); +} + +void PDFPageContentEditorStyleSettings::setBrushColor(QColor color) +{ + if (color.isValid() && m_brush.color() != color) + { + m_brush.setColor(color); + setColorToComboBox(ui->brushColorCombo, color); + emit brushChanged(m_brush); + } +} + +void PDFPageContentEditorStyleSettings::onSelectBrushColorButtonClicked() +{ + QColor color = QColorDialog::getColor(m_pen.color(), this, tr("Select Color for Brush"), QColorDialog::ShowAlphaChannel); + setBrushColor(color); +} + +void PDFPageContentEditorStyleSettings::onFontChanged(const QFont& font) +{ + if (m_font != font) + { + m_font = font; + emit fontChanged(m_font); + } +} + +} // namespace pdf diff --git a/Pdf4QtLib/sources/pdfpagecontenteditorstylesettings.h b/Pdf4QtLib/sources/pdfpagecontenteditorstylesettings.h new file mode 100644 index 0000000..12afab9 --- /dev/null +++ b/Pdf4QtLib/sources/pdfpagecontenteditorstylesettings.h @@ -0,0 +1,99 @@ +// Copyright (C) 2022 Jakub Melka +// +// This file is part of PDF4QT. +// +// PDF4QT 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 +// with the written consent of the copyright owner, any later version. +// +// PDF4QT 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 PDF4QT. If not, see . + +#ifndef PDFPAGECONTENTEDITORSTYLESETTINGS_H +#define PDFPAGECONTENTEDITORSTYLESETTINGS_H + +#include "pdfglobal.h" + +#include +#include +#include +#include +#include +#include + +namespace Ui +{ +class PDFPageContentEditorStyleSettings; +} + +class QComboBox; + +namespace pdf +{ +class PDFPageContentElement; + +class PDFPageContentEditorStyleSettings : public QWidget +{ + Q_OBJECT + +public: + + enum StyleFeature + { + None = 0, + Pen = 1 << 0, + PenColor = 1 << 1, + Brush = 1 << 2, + Text = 1 << 3 + }; + Q_DECLARE_FLAGS(StyleFeatures, StyleFeature) + + explicit PDFPageContentEditorStyleSettings(QWidget* parent); + virtual ~PDFPageContentEditorStyleSettings() override; + + /// Loads data from element, element can be nullptr + /// \param element Element + void loadFromElement(const PDFPageContentElement* element, bool forceUpdate); + + void setPen(const QPen& pen, bool forceUpdate); + void setBrush(const QBrush& brush, bool forceUpdate); + void setFont(const QFont& font, bool forceUpdate); + void setFontAlignment(Qt::Alignment alignment, bool forceUpdate); + +signals: + void penChanged(const QPen& pen); + void brushChanged(const QBrush& brush); + void fontChanged(const QFont& font); + void alignmentChanged(Qt::Alignment alignment); + +private slots: + void onSelectFontButtonClicked(); + void onSelectPenColorButtonClicked(); + void onSelectBrushColorButtonClicked(); + +private: + Ui::PDFPageContentEditorStyleSettings* ui; + + void onFontChanged(const QFont& font); + void setColorToComboBox(QComboBox* comboBox, QColor color); + QIcon getIconForColor(QColor color) const; + + void setPenColor(QColor color); + void setBrushColor(QColor color); + + QPen m_pen; + QBrush m_brush; + QFont m_font; + Qt::Alignment m_alignment = Qt::AlignCenter; + QSignalMapper m_alignmentMapper; +}; + +} // namespace pdf + +#endif // PDFPAGECONTENTEDITORSTYLESETTINGS_H diff --git a/Pdf4QtLib/sources/pdfpagecontenteditorstylesettings.ui b/Pdf4QtLib/sources/pdfpagecontenteditorstylesettings.ui new file mode 100644 index 0000000..45a9a46 --- /dev/null +++ b/Pdf4QtLib/sources/pdfpagecontenteditorstylesettings.ui @@ -0,0 +1,203 @@ + + + PDFPageContentEditorStyleSettings + + + + 0 + 0 + 344 + 304 + + + + Style Settings + + + + + + Pen Style + + + + + + + Pen Width + + + + + + + true + + + + + + + Brush Color + + + + + + + Font + + + + + + + + + + Qt::Vertical + + + QSizePolicy::MinimumExpanding + + + + 20 + 40 + + + + + + + + Text Alignment + + + + + + + true + + + + + + + Pen Color + + + + + + + ... + + + + + + + ... + + + + + + + ... + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + diff --git a/Pdf4QtLib/sources/pdfpagecontenteditorwidget.cpp b/Pdf4QtLib/sources/pdfpagecontenteditorwidget.cpp index 4830367..b792062 100644 --- a/Pdf4QtLib/sources/pdfpagecontenteditorwidget.cpp +++ b/Pdf4QtLib/sources/pdfpagecontenteditorwidget.cpp @@ -16,6 +16,7 @@ // along with PDF4QT. If not, see . #include "pdfpagecontenteditorwidget.h" +#include "pdfpagecontenteditorstylesettings.h" #include "ui_pdfpagecontenteditorwidget.h" #include "pdfwidgetutils.h" #include "pdfpagecontentelements.h" diff --git a/Pdf4QtLib/sources/pdfpagecontenteditorwidget.ui b/Pdf4QtLib/sources/pdfpagecontenteditorwidget.ui index 0aa8987..167fd80 100644 --- a/Pdf4QtLib/sources/pdfpagecontenteditorwidget.ui +++ b/Pdf4QtLib/sources/pdfpagecontenteditorwidget.ui @@ -17,7 +17,7 @@ Content editor - + @@ -161,6 +161,11 @@ Appearance + + + + + @@ -182,6 +187,14 @@ + + + pdf::PDFPageContentEditorStyleSettings + QWidget +
pdfpagecontenteditorstylesettings.h
+ 1 +
+
From 1325f87a10a6f84f263ce2a27ba82265ca9301cd Mon Sep 17 00:00:00 2001 From: Jakub Melka Date: Sun, 10 Apr 2022 19:30:12 +0200 Subject: [PATCH 23/39] Signature plugin: style settings --- .../pdfpagecontenteditorstylesettings.cpp | 134 +++++++++++++- .../pdfpagecontenteditorstylesettings.h | 10 ++ .../pdfpagecontenteditorstylesettings.ui | 163 ++++++++++-------- .../sources/pdfpagecontenteditortools.cpp | 126 ++++++++++++++ Pdf4QtLib/sources/pdfpagecontenteditortools.h | 35 +++- .../sources/pdfpagecontenteditorwidget.cpp | 20 ++- .../sources/pdfpagecontenteditorwidget.h | 12 ++ .../sources/pdfpagecontenteditorwidget.ui | 17 +- Pdf4QtLib/sources/pdftexteditpseudowidget.cpp | 2 + .../SignaturePlugin/signatureplugin.cpp | 77 +++++++++ .../SignaturePlugin/signatureplugin.h | 8 + 11 files changed, 527 insertions(+), 77 deletions(-) diff --git a/Pdf4QtLib/sources/pdfpagecontenteditorstylesettings.cpp b/Pdf4QtLib/sources/pdfpagecontenteditorstylesettings.cpp index 516cf73..01e69f5 100644 --- a/Pdf4QtLib/sources/pdfpagecontenteditorstylesettings.cpp +++ b/Pdf4QtLib/sources/pdfpagecontenteditorstylesettings.cpp @@ -23,6 +23,7 @@ #include #include +#include namespace pdf { @@ -53,6 +54,13 @@ PDFPageContentEditorStyleSettings::PDFPageContentEditorStyleSettings(QWidget* pa connect(ui->selectPenColorButton, &QToolButton::clicked, this, &PDFPageContentEditorStyleSettings::onSelectPenColorButtonClicked); connect(ui->selectBrushColorButton, &QToolButton::clicked, this, &PDFPageContentEditorStyleSettings::onSelectBrushColorButtonClicked); connect(ui->selectFontButton, &QToolButton::clicked, this, &PDFPageContentEditorStyleSettings::onSelectFontButtonClicked); + connect(ui->penWidthEdit, QOverload::of(&QDoubleSpinBox::valueChanged), this, &PDFPageContentEditorStyleSettings::onPenWidthChanged); + connect(ui->penStyleCombo, QOverload::of(&QComboBox::currentIndexChanged), this, &PDFPageContentEditorStyleSettings::onPenStyleChanged); + connect(ui->textAngleEdit, QOverload::of(&QDoubleSpinBox::valueChanged), this, &PDFPageContentEditorStyleSettings::onTextAngleChanged); + connect(ui->penColorCombo->lineEdit(), &QLineEdit::editingFinished, this, &PDFPageContentEditorStyleSettings::onPenColorComboTextChanged); + connect(ui->penColorCombo, QOverload::of(&QComboBox::currentIndexChanged), this, &PDFPageContentEditorStyleSettings::onPenColorComboIndexChanged); + connect(ui->brushColorCombo->lineEdit(), &QLineEdit::editingFinished, this, &PDFPageContentEditorStyleSettings::onBrushColorComboTextChanged); + connect(ui->brushColorCombo, QOverload::of(&QComboBox::currentIndexChanged), this, &PDFPageContentEditorStyleSettings::onBrushColorComboIndexChanged); m_alignmentMapper.setMapping(ui->al11Button, int(Qt::AlignLeft | Qt::AlignTop)); m_alignmentMapper.setMapping(ui->al12Button, int(Qt::AlignHCenter | Qt::AlignTop)); @@ -64,6 +72,12 @@ PDFPageContentEditorStyleSettings::PDFPageContentEditorStyleSettings(QWidget* pa m_alignmentMapper.setMapping(ui->al32Button, int(Qt::AlignHCenter | Qt::AlignBottom)); m_alignmentMapper.setMapping(ui->al33Button, int(Qt::AlignRight | Qt::AlignBottom)); + for (QRadioButton* radioButton : findChildren()) + { + connect(radioButton, &QRadioButton::clicked, &m_alignmentMapper, QOverload<>::of(&QSignalMapper::map)); + } + connect(&m_alignmentMapper, &QSignalMapper::mappedInt, this, &PDFPageContentEditorStyleSettings::onAlignmentRadioButtonClicked); + loadFromElement(nullptr, true); } @@ -121,10 +135,14 @@ void PDFPageContentEditorStyleSettings::loadFromElement(const PDFPageContentElem } ui->textAlignmentLabel->setEnabled(hasText); + ui->textAngleLabel->setEnabled(hasText); + ui->textAngleEdit->setEnabled(hasText); + QPen pen(Qt::SolidLine); QBrush brush(Qt::transparent); QFont font = QGuiApplication::font(); Qt::Alignment alignment = Qt::AlignCenter; + PDFReal textAngle = 0.0; if (styledElement) { @@ -136,12 +154,14 @@ void PDFPageContentEditorStyleSettings::loadFromElement(const PDFPageContentElem { font = textElement->getFont(); alignment = textElement->getAlignment(); + textAngle = textElement->getAngle(); } setPen(pen, forceUpdate); setBrush(brush, forceUpdate); setFont(font, forceUpdate); setFontAlignment(alignment, forceUpdate); + setTextAngle(textAngle, forceUpdate); } void PDFPageContentEditorStyleSettings::setPen(const QPen& pen, bool forceUpdate) @@ -207,6 +227,17 @@ void PDFPageContentEditorStyleSettings::setFontAlignment(Qt::Alignment alignment } } +void PDFPageContentEditorStyleSettings::setTextAngle(PDFReal angle, bool forceUpdate) +{ + if (ui->textAngleEdit->value() != angle || forceUpdate) + { + const bool oldBlockSignals = blockSignals(true); + ui->textAngleEdit->setValue(angle); + blockSignals(oldBlockSignals); + emit textAngleChanged(ui->textAngleEdit->value()); + } +} + QIcon PDFPageContentEditorStyleSettings::getIconForColor(QColor color) const { QIcon icon; @@ -229,7 +260,14 @@ void PDFPageContentEditorStyleSettings::setColorToComboBox(QComboBox* comboBox, QString name = color.name(QColor::HexArgb); - const int index = comboBox->findText(name); + int index = comboBox->findData(color, Qt::UserRole, Qt::MatchExactly); + + if (index == -1) + { + // Jakub Melka: try to find text (color name) + index = comboBox->findText(name); + } + if (index != -1) { comboBox->setCurrentIndex(index); @@ -286,6 +324,100 @@ void PDFPageContentEditorStyleSettings::onSelectBrushColorButtonClicked() setBrushColor(color); } +void PDFPageContentEditorStyleSettings::onPenWidthChanged(double value) +{ + if (m_pen.widthF() != value) + { + m_pen.setWidthF(value); + emit penChanged(m_pen); + } +} + +void PDFPageContentEditorStyleSettings::onTextAngleChanged(double value) +{ + emit textAngleChanged(value); +} + +void PDFPageContentEditorStyleSettings::onAlignmentRadioButtonClicked(int alignment) +{ + Qt::Alignment alignmentValue = static_cast(alignment); + if (m_alignment != alignmentValue) + { + m_alignment = alignmentValue; + emit alignmentChanged(m_alignment); + } +} + +void PDFPageContentEditorStyleSettings::onPenStyleChanged() +{ + Qt::PenStyle penStyle = static_cast(ui->penStyleCombo->currentData().toInt()); + if (m_pen.style() != penStyle) + { + m_pen.setStyle(penStyle); + emit penChanged(m_pen); + } +} + +void PDFPageContentEditorStyleSettings::onPenColorComboTextChanged() +{ + QColor color(ui->penColorCombo->currentText()); + if (color.isValid()) + { + setColorToComboBox(ui->penColorCombo, color); + + if (m_pen.color() != color) + { + m_pen.setColor(color); + emit penChanged(m_pen); + } + } + else if (ui->penColorCombo->currentIndex() != -1) + { + ui->penColorCombo->setEditText(ui->penColorCombo->itemText(ui->penColorCombo->currentIndex())); + } +} + +void PDFPageContentEditorStyleSettings::onPenColorComboIndexChanged() +{ + const int index = ui->penColorCombo->currentIndex(); + QColor color = ui->penColorCombo->itemData(index, Qt::UserRole).value(); + if (color.isValid() && m_pen.color() != color) + { + m_pen.setColor(color); + emit penChanged(m_pen); + } +} + +void PDFPageContentEditorStyleSettings::onBrushColorComboTextChanged() +{ + QColor color(ui->brushColorCombo->currentText()); + if (color.isValid()) + { + setColorToComboBox(ui->brushColorCombo, color); + + if (m_brush.color() != color) + { + m_brush.setColor(color); + emit brushChanged(m_brush); + } + } + else if (ui->brushColorCombo->currentIndex() != -1) + { + ui->brushColorCombo->setEditText(ui->brushColorCombo->itemText(ui->brushColorCombo->currentIndex())); + } +} + +void PDFPageContentEditorStyleSettings::onBrushColorComboIndexChanged() +{ + const int index = ui->brushColorCombo->currentIndex(); + QColor color = ui->brushColorCombo->itemData(index, Qt::UserRole).value(); + if (color.isValid() && m_brush.color() != color) + { + m_brush.setColor(color); + emit brushChanged(m_brush); + } +} + void PDFPageContentEditorStyleSettings::onFontChanged(const QFont& font) { if (m_font != font) diff --git a/Pdf4QtLib/sources/pdfpagecontenteditorstylesettings.h b/Pdf4QtLib/sources/pdfpagecontenteditorstylesettings.h index 12afab9..1412a09 100644 --- a/Pdf4QtLib/sources/pdfpagecontenteditorstylesettings.h +++ b/Pdf4QtLib/sources/pdfpagecontenteditorstylesettings.h @@ -65,17 +65,27 @@ public: void setBrush(const QBrush& brush, bool forceUpdate); void setFont(const QFont& font, bool forceUpdate); void setFontAlignment(Qt::Alignment alignment, bool forceUpdate); + void setTextAngle(PDFReal angle, bool forceUpdate); signals: void penChanged(const QPen& pen); void brushChanged(const QBrush& brush); void fontChanged(const QFont& font); void alignmentChanged(Qt::Alignment alignment); + void textAngleChanged(pdf::PDFReal angle); private slots: void onSelectFontButtonClicked(); void onSelectPenColorButtonClicked(); void onSelectBrushColorButtonClicked(); + void onPenWidthChanged(double value); + void onTextAngleChanged(double value); + void onAlignmentRadioButtonClicked(int alignment); + void onPenStyleChanged(); + void onPenColorComboTextChanged(); + void onPenColorComboIndexChanged(); + void onBrushColorComboTextChanged(); + void onBrushColorComboIndexChanged(); private: Ui::PDFPageContentEditorStyleSettings* ui; diff --git a/Pdf4QtLib/sources/pdfpagecontenteditorstylesettings.ui b/Pdf4QtLib/sources/pdfpagecontenteditorstylesettings.ui index 45a9a46..c9e792e 100644 --- a/Pdf4QtLib/sources/pdfpagecontenteditorstylesettings.ui +++ b/Pdf4QtLib/sources/pdfpagecontenteditorstylesettings.ui @@ -28,67 +28,9 @@
- - - - true - - - - - - - Brush Color - - - - - - - Font - - - - - - - Qt::Vertical - - - QSizePolicy::MinimumExpanding - - - - 20 - 40 - - - - - - - - Text Alignment - - - - - - - true - - - - - - - Pen Color - - - @@ -96,27 +38,20 @@ - - - - ... - - - - - - - ... - - - + + + + Brush Color + + + - + @@ -196,6 +131,88 @@ + + + + Qt::Vertical + + + QSizePolicy::MinimumExpanding + + + + 20 + 40 + + + + + + + + true + + + + + + + Font + + + + + + + ... + + + + + + + Pen Color + + + + + + + ... + + + + + + + Text Alignment + + + + + + + true + + + + + + + Text Angle + + + + + + + -90.000000000000000 + + + 90.000000000000000 + + +
diff --git a/Pdf4QtLib/sources/pdfpagecontenteditortools.cpp b/Pdf4QtLib/sources/pdfpagecontenteditortools.cpp index 011bb7f..1586688 100644 --- a/Pdf4QtLib/sources/pdfpagecontenteditortools.cpp +++ b/Pdf4QtLib/sources/pdfpagecontenteditortools.cpp @@ -38,6 +38,51 @@ PDFCreatePCElementTool::PDFCreatePCElementTool(PDFDrawWidgetProxy* proxy, } +void PDFCreatePCElementTool::setPen(const QPen& pen) +{ + if (PDFPageContentStyledElement* styledElement = dynamic_cast(getElement())) + { + styledElement->setPen(pen); + emit getProxy()->repaintNeeded(); + } +} + +void PDFCreatePCElementTool::setBrush(const QBrush& brush) +{ + if (PDFPageContentStyledElement* styledElement = dynamic_cast(getElement())) + { + styledElement->setBrush(brush); + emit getProxy()->repaintNeeded(); + } +} + +void PDFCreatePCElementTool::setFont(const QFont& font) +{ + if (PDFPageContentElementTextBox* textBoxElement = dynamic_cast(getElement())) + { + textBoxElement->setFont(font); + emit getProxy()->repaintNeeded(); + } +} + +void PDFCreatePCElementTool::setAlignment(Qt::Alignment alignment) +{ + if (PDFPageContentElementTextBox* textBoxElement = dynamic_cast(getElement())) + { + textBoxElement->setAlignment(alignment); + emit getProxy()->repaintNeeded(); + } +} + +void PDFCreatePCElementTool::setTextAngle(PDFReal angle) +{ + if (PDFPageContentElementTextBox* textBoxElement = dynamic_cast(getElement())) + { + textBoxElement->setAngle(angle); + emit getProxy()->repaintNeeded(); + } +} + QRectF PDFCreatePCElementTool::getRectangleFromPickTool(PDFPickTool* pickTool, const QMatrix& pagePointToDevicePointMatrix) { @@ -120,6 +165,16 @@ void PDFCreatePCElementRectangleTool::drawPage(QPainter* painter, m_element->drawPage(painter, pageIndex, compiledPage, layoutGetter, pagePointToDevicePointMatrix, errors); } +const PDFPageContentElement* PDFCreatePCElementRectangleTool::getElement() const +{ + return m_element; +} + +PDFPageContentElement* PDFCreatePCElementRectangleTool::getElement() +{ + return m_element; +} + void PDFCreatePCElementRectangleTool::onRectanglePicked(PDFInteger pageIndex, QRectF pageRectangle) { if (pageRectangle.isEmpty()) @@ -206,6 +261,16 @@ void PDFCreatePCElementLineTool::drawPage(QPainter* painter, m_element->drawPage(painter, pageIndex, compiledPage, layoutGetter, pagePointToDevicePointMatrix, errors); } +const PDFPageContentElement* PDFCreatePCElementLineTool::getElement() const +{ + return m_element; +} + +PDFPageContentElement* PDFCreatePCElementLineTool::getElement() +{ + return m_element; +} + void PDFCreatePCElementLineTool::clear() { m_startPoint = std::nullopt; @@ -298,6 +363,16 @@ void PDFCreatePCElementSvgTool::drawPage(QPainter* painter, m_element->drawPage(painter, pageIndex, compiledPage, layoutGetter, pagePointToDevicePointMatrix, errors); } +const PDFPageContentElement* PDFCreatePCElementSvgTool::getElement() const +{ + return m_element; +} + +PDFPageContentElement* PDFCreatePCElementSvgTool::getElement() +{ + return m_element; +} + void PDFCreatePCElementSvgTool::onRectanglePicked(PDFInteger pageIndex, QRectF pageRectangle) { if (pageRectangle.isEmpty()) @@ -360,6 +435,16 @@ void PDFCreatePCElementDotTool::drawPage(QPainter* painter, painter->drawPoint(point); } +const PDFPageContentElement* PDFCreatePCElementDotTool::getElement() const +{ + return m_element; +} + +PDFPageContentElement* PDFCreatePCElementDotTool::getElement() +{ + return m_element; +} + void PDFCreatePCElementDotTool::onPointPicked(PDFInteger pageIndex, QPointF pagePoint) { m_element->setPageIndex(pageIndex); @@ -409,6 +494,16 @@ void PDFCreatePCElementFreehandCurveTool::drawPage(QPainter* painter, m_element->drawPage(painter, pageIndex, compiledPage, layoutGetter, pagePointToDevicePointMatrix, errors); } +const PDFPageContentElement* PDFCreatePCElementFreehandCurveTool::getElement() const +{ + return m_element; +} + +PDFPageContentElement* PDFCreatePCElementFreehandCurveTool::getElement() +{ + return m_element; +} + void PDFCreatePCElementFreehandCurveTool::mousePressEvent(QWidget* widget, QMouseEvent* event) { Q_UNUSED(widget); @@ -554,6 +649,16 @@ void PDFCreatePCElementTextTool::drawPage(QPainter* painter, } } +const PDFPageContentElement* PDFCreatePCElementTextTool::getElement() const +{ + return m_element; +} + +PDFPageContentElement* PDFCreatePCElementTextTool::getElement() +{ + return m_element; +} + void PDFCreatePCElementTextTool::resetTool() { m_textEditWidget->setText(QString()); @@ -767,4 +872,25 @@ void PDFCreatePCElementTextTool::wheelEvent(QWidget* widget, QWheelEvent* event) } } +void PDFCreatePCElementTextTool::setFont(const QFont& font) +{ + BaseClass::setFont(font); + m_textEditWidget->setAppearance(font, m_element->getAlignment(), m_element->getRectangle(), std::numeric_limits::max(), m_element->getPen().color()); + emit getProxy()->repaintNeeded(); +} + +void PDFCreatePCElementTextTool::setAlignment(Qt::Alignment alignment) +{ + BaseClass::setAlignment(alignment); + m_textEditWidget->setAppearance(m_element->getFont(), alignment, m_element->getRectangle(), std::numeric_limits::max(), m_element->getPen().color()); + emit getProxy()->repaintNeeded(); +} + +void PDFCreatePCElementTextTool::setPen(const QPen& pen) +{ + BaseClass::setPen(pen); + m_textEditWidget->setAppearance(m_element->getFont(), m_element->getAlignment(), m_element->getRectangle(), std::numeric_limits::max(), pen.color()); + emit getProxy()->repaintNeeded(); +} + } // namespace pdf diff --git a/Pdf4QtLib/sources/pdfpagecontenteditortools.h b/Pdf4QtLib/sources/pdfpagecontenteditortools.h index 959d54a..7f274a2 100644 --- a/Pdf4QtLib/sources/pdfpagecontenteditortools.h +++ b/Pdf4QtLib/sources/pdfpagecontenteditortools.h @@ -24,6 +24,7 @@ namespace pdf { class PDFPageContentScene; +class PDFPageContentElement; class PDFPageContentSvgElement; class PDFPageContentElementDot; class PDFPageContentElementLine; @@ -32,14 +33,24 @@ class PDFPageContentElementRectangle; class PDFPageContentElementFreehandCurve; class PDFTextEditPseudowidget; -class PDFCreatePCElementTool : public PDFWidgetTool +class PDF4QTLIBSHARED_EXPORT PDFCreatePCElementTool : public PDFWidgetTool { + Q_OBJECT public: PDFCreatePCElementTool(PDFDrawWidgetProxy* proxy, PDFPageContentScene* scene, QAction* action, QObject* parent); + virtual const PDFPageContentElement* getElement() const = 0; + virtual PDFPageContentElement* getElement() = 0; + + virtual void setPen(const QPen& pen); + virtual void setBrush(const QBrush& brush); + virtual void setFont(const QFont& font); + virtual void setAlignment(Qt::Alignment alignment); + virtual void setTextAngle(pdf::PDFReal angle); + protected: static QRectF getRectangleFromPickTool(PDFPickTool* pickTool, const QMatrix& pagePointToDevicePointMatrix); @@ -69,6 +80,9 @@ public: const QMatrix& pagePointToDevicePointMatrix, QList& errors) const override; + virtual const PDFPageContentElement* getElement() const override; + virtual PDFPageContentElement* getElement() override; + private: void onRectanglePicked(pdf::PDFInteger pageIndex, QRectF pageRectangle); @@ -99,6 +113,9 @@ public: const QMatrix& pagePointToDevicePointMatrix, QList& errors) const override; + virtual const PDFPageContentElement* getElement() const override; + virtual PDFPageContentElement* getElement() override; + private: void onRectanglePicked(pdf::PDFInteger pageIndex, QRectF pageRectangle); @@ -130,6 +147,9 @@ public: const QMatrix& pagePointToDevicePointMatrix, QList& errors) const override; + virtual const PDFPageContentElement* getElement() const override; + virtual PDFPageContentElement* getElement() override; + private: void clear(); void onPointPicked(pdf::PDFInteger pageIndex, QPointF pagePoint); @@ -161,6 +181,9 @@ public: const QMatrix& pagePointToDevicePointMatrix, QList& errors) const override; + virtual const PDFPageContentElement* getElement() const override; + virtual PDFPageContentElement* getElement() override; + private: void onPointPicked(pdf::PDFInteger pageIndex, QPointF pagePoint); @@ -190,6 +213,9 @@ public: const QMatrix& pagePointToDevicePointMatrix, QList& errors) const override; + virtual const PDFPageContentElement* getElement() const override; + virtual PDFPageContentElement* getElement() override; + virtual void mousePressEvent(QWidget* widget, QMouseEvent* event) override; virtual void mouseReleaseEvent(QWidget* widget, QMouseEvent* event) override; virtual void mouseMoveEvent(QWidget* widget, QMouseEvent* event) override; @@ -225,6 +251,13 @@ public: const QMatrix& pagePointToDevicePointMatrix, QList& errors) const override; + virtual const PDFPageContentElement* getElement() const override; + virtual PDFPageContentElement* getElement() override; + + virtual void setPen(const QPen& pen); + virtual void setFont(const QFont& font) override; + virtual void setAlignment(Qt::Alignment alignment) override; + virtual void setActiveImpl(bool active) override; virtual void shortcutOverrideEvent(QWidget* widget, QKeyEvent* event) override; virtual void keyPressEvent(QWidget* widget, QKeyEvent* event) override; diff --git a/Pdf4QtLib/sources/pdfpagecontenteditorwidget.cpp b/Pdf4QtLib/sources/pdfpagecontenteditorwidget.cpp index b792062..78cf64d 100644 --- a/Pdf4QtLib/sources/pdfpagecontenteditorwidget.cpp +++ b/Pdf4QtLib/sources/pdfpagecontenteditorwidget.cpp @@ -33,7 +33,8 @@ PDFPageContentEditorWidget::PDFPageContentEditorWidget(QWidget* parent) : ui(new Ui::PDFPageContentEditorWidget), m_toolBoxColumnCount(6), m_scene(nullptr), - m_selectionChangeEnabled(true) + m_selectionChangeEnabled(true), + m_updatesEnabled(true) { ui->setupUi(this); @@ -81,6 +82,12 @@ PDFPageContentEditorWidget::PDFPageContentEditorWidget(QWidget* parent) : connect(&m_actionMapper, &QSignalMapper::mappedObject, this, &PDFPageContentEditorWidget::onActionTriggerRequest); connect(&m_operationMapper, &QSignalMapper::mappedInt, this, &PDFPageContentEditorWidget::operationTriggered); connect(ui->itemsListWidget->selectionModel(), &QItemSelectionModel::selectionChanged, this, &PDFPageContentEditorWidget::onItemSelectionChanged); + + connect(ui->appearanceSettingsWidget, &PDFPageContentEditorStyleSettings::penChanged, this, &PDFPageContentEditorWidget::penChanged); + connect(ui->appearanceSettingsWidget, &PDFPageContentEditorStyleSettings::brushChanged, this, &PDFPageContentEditorWidget::brushChanged); + connect(ui->appearanceSettingsWidget, &PDFPageContentEditorStyleSettings::fontChanged, this, &PDFPageContentEditorWidget::fontChanged); + connect(ui->appearanceSettingsWidget, &PDFPageContentEditorStyleSettings::alignmentChanged, this, &PDFPageContentEditorWidget::alignmentChanged); + connect(ui->appearanceSettingsWidget, &PDFPageContentEditorStyleSettings::textAngleChanged, this, &PDFPageContentEditorWidget::textAngleChanged); } PDFPageContentEditorWidget::~PDFPageContentEditorWidget() @@ -133,6 +140,12 @@ QToolButton* PDFPageContentEditorWidget::getToolButtonForOperation(int operation void PDFPageContentEditorWidget::updateItemsInListWidget() { + if (!m_updatesEnabled) + { + return; + } + + pdf::PDFTemporaryValueChange guard(&m_updatesEnabled, false); ui->itemsListWidget->setUpdatesEnabled(false); if (m_scene) @@ -252,4 +265,9 @@ void PDFPageContentEditorWidget::setSelection(const std::set& select } } +void PDFPageContentEditorWidget::loadStyleFromElement(const PDFPageContentElement* element) +{ + ui->appearanceSettingsWidget->loadFromElement(element, false); +} + } // namespace pdf diff --git a/Pdf4QtLib/sources/pdfpagecontenteditorwidget.h b/Pdf4QtLib/sources/pdfpagecontenteditorwidget.h index 2225c91..4e60b7a 100644 --- a/Pdf4QtLib/sources/pdfpagecontenteditorwidget.h +++ b/Pdf4QtLib/sources/pdfpagecontenteditorwidget.h @@ -35,6 +35,7 @@ class PDFPageContentEditorWidget; namespace pdf { class PDFPageContentScene; +class PDFPageContentElement; class PDF4QTLIBSHARED_EXPORT PDFPageContentEditorWidget : public QDockWidget { @@ -58,10 +59,20 @@ public: std::set getSelection() const; void setSelection(const std::set& selection); + /// Loads style from element, element can be nullptr + /// \param element Element + void loadStyleFromElement(const PDFPageContentElement* element); + signals: void operationTriggered(int operation); void itemSelectionChangedByUser(); + void penChanged(const QPen& pen); + void brushChanged(const QBrush& brush); + void fontChanged(const QFont& font); + void alignmentChanged(Qt::Alignment alignment); + void textAngleChanged(pdf::PDFReal angle); + private: void onActionTriggerRequest(QObject* actionObject); void onActionChanged(); @@ -74,6 +85,7 @@ private: QSize m_toolButtonIconSize; PDFPageContentScene* m_scene; bool m_selectionChangeEnabled; + bool m_updatesEnabled; }; } // namespace pdf diff --git a/Pdf4QtLib/sources/pdfpagecontenteditorwidget.ui b/Pdf4QtLib/sources/pdfpagecontenteditorwidget.ui index 167fd80..f9bdde7 100644 --- a/Pdf4QtLib/sources/pdfpagecontenteditorwidget.ui +++ b/Pdf4QtLib/sources/pdfpagecontenteditorwidget.ui @@ -162,8 +162,23 @@ Appearance + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + - + diff --git a/Pdf4QtLib/sources/pdftexteditpseudowidget.cpp b/Pdf4QtLib/sources/pdftexteditpseudowidget.cpp index 8fdceda..668dfb8 100644 --- a/Pdf4QtLib/sources/pdftexteditpseudowidget.cpp +++ b/Pdf4QtLib/sources/pdftexteditpseudowidget.cpp @@ -428,6 +428,7 @@ void PDFTextEditPseudowidget::setAppearance(const PDFAnnotationDefaultAppearance m_maxTextLength = maxTextLength; m_widgetRect = rect; + updateTextLayout(); } void PDFTextEditPseudowidget::setAppearance(const QFont& font, @@ -453,6 +454,7 @@ void PDFTextEditPseudowidget::setAppearance(const QFont& font, m_maxTextLength = maxTextLength; m_widgetRect = rect; + updateTextLayout(); } void PDFTextEditPseudowidget::performCut() diff --git a/Pdf4QtViewerPlugins/SignaturePlugin/signatureplugin.cpp b/Pdf4QtViewerPlugins/SignaturePlugin/signatureplugin.cpp index 9f445f1..c11057f 100644 --- a/Pdf4QtViewerPlugins/SignaturePlugin/signatureplugin.cpp +++ b/Pdf4QtViewerPlugins/SignaturePlugin/signatureplugin.cpp @@ -139,6 +139,7 @@ void SignaturePlugin::setWidget(pdf::PDFWidget* widget) for (pdf::PDFWidgetTool* tool : m_tools) { toolManager->addTool(tool); + connect(tool, &pdf::PDFWidgetTool::toolActivityChanged, this, &SignaturePlugin::onToolActivityChanged); } m_widget->addInputInterface(&m_scene); @@ -206,6 +207,76 @@ void SignaturePlugin::onWidgetSelectionChanged() m_scene.setSelectedElementIds(m_editorWidget->getSelection()); } +pdf::PDFWidgetTool* SignaturePlugin::getActiveTool() +{ + for (pdf::PDFWidgetTool* currentTool : m_tools) + { + if (currentTool->isActive()) + { + return currentTool; + } + } + + return nullptr; +} + +void SignaturePlugin::onToolActivityChanged() +{ + if (m_editorWidget) + { + pdf::PDFWidgetTool* activeTool = getActiveTool(); + + const pdf::PDFPageContentElement* element = nullptr; + pdf::PDFCreatePCElementTool* tool = qobject_cast(activeTool); + if (tool) + { + element = tool->getElement(); + } + + m_editorWidget->loadStyleFromElement(element); + } +} + +void SignaturePlugin::onPenChanged(const QPen& pen) +{ + if (pdf::PDFCreatePCElementTool* activeTool = qobject_cast(getActiveTool())) + { + activeTool->setPen(pen); + } +} + +void SignaturePlugin::onBrushChanged(const QBrush& brush) +{ + if (pdf::PDFCreatePCElementTool* activeTool = qobject_cast(getActiveTool())) + { + activeTool->setBrush(brush); + } +} + +void SignaturePlugin::onFontChanged(const QFont& font) +{ + if (pdf::PDFCreatePCElementTool* activeTool = qobject_cast(getActiveTool())) + { + activeTool->setFont(font); + } +} + +void SignaturePlugin::onAlignmentChanged(Qt::Alignment alignment) +{ + if (pdf::PDFCreatePCElementTool* activeTool = qobject_cast(getActiveTool())) + { + activeTool->setAlignment(alignment); + } +} + +void SignaturePlugin::onTextAngleChanged(pdf::PDFReal angle) +{ + if (pdf::PDFCreatePCElementTool* activeTool = qobject_cast(getActiveTool())) + { + activeTool->setTextAngle(angle); + } +} + void SignaturePlugin::setActive(bool active) { if (m_scene.isActive() != active) @@ -325,6 +396,12 @@ void SignaturePlugin::updateDockWidget() { m_editorWidget->addAction(action); } + + connect(m_editorWidget, &pdf::PDFPageContentEditorWidget::penChanged, this, &SignaturePlugin::onPenChanged); + connect(m_editorWidget, &pdf::PDFPageContentEditorWidget::brushChanged, this, &SignaturePlugin::onBrushChanged); + connect(m_editorWidget, &pdf::PDFPageContentEditorWidget::fontChanged, this, &SignaturePlugin::onFontChanged); + connect(m_editorWidget, &pdf::PDFPageContentEditorWidget::alignmentChanged, this, &SignaturePlugin::onAlignmentChanged); + connect(m_editorWidget, &pdf::PDFPageContentEditorWidget::textAngleChanged, this, &SignaturePlugin::onTextAngleChanged); } } diff --git a/Pdf4QtViewerPlugins/SignaturePlugin/signatureplugin.h b/Pdf4QtViewerPlugins/SignaturePlugin/signatureplugin.h index 7acdcc0..cc6a156 100644 --- a/Pdf4QtViewerPlugins/SignaturePlugin/signatureplugin.h +++ b/Pdf4QtViewerPlugins/SignaturePlugin/signatureplugin.h @@ -51,6 +51,13 @@ private: void onSceneChanged(bool graphicsOnly); void onSceneSelectionChanged(); void onWidgetSelectionChanged(); + void onToolActivityChanged(); + + void onPenChanged(const QPen& pen); + void onBrushChanged(const QBrush& brush); + void onFontChanged(const QFont& font); + void onAlignmentChanged(Qt::Alignment alignment); + void onTextAngleChanged(pdf::PDFReal angle); enum Action { @@ -106,6 +113,7 @@ private: pdf::PDFPageContentScene m_scene; bool m_sceneSelectionChangeEnabled; + pdf::PDFWidgetTool* getActiveTool(); }; } // namespace pdfplugin From 72f08c7588d9da854e12a7c1c517c20313c3733b Mon Sep 17 00:00:00 2001 From: Jakub Melka Date: Thu, 14 Apr 2022 19:37:21 +0200 Subject: [PATCH 24/39] Signature plugin: icons --- Pdf4QtDocDiff/app-icon.ico | Bin 4463 -> 3813 bytes Pdf4QtDocDiff/app-icon.svg | 18 ++ .../pdfpagecontenteditorstylesettings.cpp | 23 +++ .../pdfpagecontenteditorstylesettings.h | 1 + .../pdfpagecontenteditorstylesettings.ui | 152 ++++++++-------- Pdf4QtViewer/resources/pce-align-bottom.svg | 69 ++------ Pdf4QtViewer/resources/pce-align-h-center.svg | 71 ++------ Pdf4QtViewer/resources/pce-align-left.svg | 69 ++------ Pdf4QtViewer/resources/pce-align-right.svg | 69 ++------ Pdf4QtViewer/resources/pce-align-top.svg | 71 ++------ Pdf4QtViewer/resources/pce-align-v-center.svg | 71 ++------ Pdf4QtViewer/resources/pce-apply-changes.svg | 68 +++++++ Pdf4QtViewer/resources/pce-center-h.svg | 77 +++----- Pdf4QtViewer/resources/pce-center-v.svg | 77 +++----- Pdf4QtViewer/resources/pce-center-vh.svg | 81 +++------ Pdf4QtViewer/resources/pce-layout-form.svg | 75 ++------ Pdf4QtViewer/resources/pce-layout-grid.svg | 120 +++++++------ Pdf4QtViewer/resources/pce-layout-h.svg | 79 +++------ Pdf4QtViewer/resources/pce-layout-v.svg | 77 +++----- Pdf4QtViewer/resources/pce-same-height.svg | 74 ++------ Pdf4QtViewer/resources/pce-same-size.svg | 85 +++------ Pdf4QtViewer/resources/pce-same-width.svg | 74 ++------ Pdf4QtViewer/resources/pce-select-brush.svg | 47 +++++ Pdf4QtViewer/resources/pce-select-color.svg | 28 +++ Pdf4QtViewer/resources/pce-select-font.svg | 36 ++++ Pdf4QtViewer/resources/pce-select-pen.svg | 50 ++++++ Pdf4QtViewerLite/app-icon.ico | Bin 7341 -> 8101 bytes Pdf4QtViewerLite/app-icon.svg | 29 +++ .../SignaturePlugin/activate.svg | 92 ++++------ .../SignaturePlugin/certificates.svg | 137 +++++++++------ Pdf4QtViewerPlugins/SignaturePlugin/clear.svg | 108 ++++++------ .../SignaturePlugin/create-dot.svg | 73 ++------ .../SignaturePlugin/create-freehand-curve.svg | 71 ++------ .../create-horizontal-line.svg | 76 ++------ .../SignaturePlugin/create-line.svg | 73 ++------ .../SignaturePlugin/create-no-mark.svg | 85 +++------ .../SignaturePlugin/create-rectangle.svg | 71 ++------ .../create-rounded-rectangle.svg | 71 ++------ .../SignaturePlugin/create-svg-image.svg | 80 +++------ .../SignaturePlugin/create-text.svg | 80 +++------ .../SignaturePlugin/create-vertical-line.svg | 76 ++------ .../SignaturePlugin/create-yes-mark.svg | 78 +++----- .../SignaturePlugin/sign-digitally.svg | 166 ++++++++++++------ .../SignaturePlugin/sign-electronically.svg | 101 +++++------ Pdf4QtViewerProfi/app-icon.ico | Bin 7341 -> 9895 bytes Pdf4QtViewerProfi/app-icon.svg | 48 +++++ 46 files changed, 1282 insertions(+), 1895 deletions(-) create mode 100644 Pdf4QtDocDiff/app-icon.svg create mode 100644 Pdf4QtViewer/resources/pce-apply-changes.svg create mode 100644 Pdf4QtViewer/resources/pce-select-brush.svg create mode 100644 Pdf4QtViewer/resources/pce-select-color.svg create mode 100644 Pdf4QtViewer/resources/pce-select-font.svg create mode 100644 Pdf4QtViewer/resources/pce-select-pen.svg create mode 100644 Pdf4QtViewerLite/app-icon.svg create mode 100644 Pdf4QtViewerProfi/app-icon.svg diff --git a/Pdf4QtDocDiff/app-icon.ico b/Pdf4QtDocDiff/app-icon.ico index e6d9d631721cf91f82a6e85af6421f4dae44aba8..b5a62d6da5b776bbc63357d9c07a0fd45319d89b 100644 GIT binary patch literal 3813 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 W|H?|>8uZHqm>69!EIH$J|Gxlo{ukN+ diff --git a/Pdf4QtDocDiff/app-icon.svg b/Pdf4QtDocDiff/app-icon.svg new file mode 100644 index 0000000..3c05198 --- /dev/null +++ b/Pdf4QtDocDiff/app-icon.svg @@ -0,0 +1,18 @@ + + + + + + + + + + + + + diff --git a/Pdf4QtLib/sources/pdfpagecontenteditorstylesettings.cpp b/Pdf4QtLib/sources/pdfpagecontenteditorstylesettings.cpp index 01e69f5..268086a 100644 --- a/Pdf4QtLib/sources/pdfpagecontenteditorstylesettings.cpp +++ b/Pdf4QtLib/sources/pdfpagecontenteditorstylesettings.cpp @@ -50,12 +50,21 @@ PDFPageContentEditorStyleSettings::PDFPageContentEditorStyleSettings(QWidget* pa ui->penStyleCombo->addItem(tr("Dash-dot"), int(Qt::DashDotLine)); ui->penStyleCombo->addItem(tr("Dash-dot-dot"), int(Qt::DashDotDotLine)); + ui->brushStyleCombo->addItem(tr("None"), int(Qt::NoBrush)); + ui->brushStyleCombo->addItem(tr("Solid"), int(Qt::SolidPattern)); + ui->brushStyleCombo->addItem(tr("Horizontal"), int(Qt::HorPattern)); + ui->brushStyleCombo->addItem(tr("Vertical"), int(Qt::VerPattern)); + ui->brushStyleCombo->addItem(tr("B-Diagonal"), int(Qt::BDiagPattern)); + ui->brushStyleCombo->addItem(tr("F-Diagonal"), int(Qt::FDiagPattern)); + ui->brushStyleCombo->addItem(tr("Cross"), int(Qt::CrossPattern)); + connect(ui->fontComboBox, &QFontComboBox::currentFontChanged, this, &PDFPageContentEditorStyleSettings::onFontChanged); connect(ui->selectPenColorButton, &QToolButton::clicked, this, &PDFPageContentEditorStyleSettings::onSelectPenColorButtonClicked); connect(ui->selectBrushColorButton, &QToolButton::clicked, this, &PDFPageContentEditorStyleSettings::onSelectBrushColorButtonClicked); connect(ui->selectFontButton, &QToolButton::clicked, this, &PDFPageContentEditorStyleSettings::onSelectFontButtonClicked); connect(ui->penWidthEdit, QOverload::of(&QDoubleSpinBox::valueChanged), this, &PDFPageContentEditorStyleSettings::onPenWidthChanged); connect(ui->penStyleCombo, QOverload::of(&QComboBox::currentIndexChanged), this, &PDFPageContentEditorStyleSettings::onPenStyleChanged); + connect(ui->brushStyleCombo, QOverload::of(&QComboBox::currentIndexChanged), this, &PDFPageContentEditorStyleSettings::onBrushStyleChanged); connect(ui->textAngleEdit, QOverload::of(&QDoubleSpinBox::valueChanged), this, &PDFPageContentEditorStyleSettings::onTextAngleChanged); connect(ui->penColorCombo->lineEdit(), &QLineEdit::editingFinished, this, &PDFPageContentEditorStyleSettings::onPenColorComboTextChanged); connect(ui->penColorCombo, QOverload::of(&QComboBox::currentIndexChanged), this, &PDFPageContentEditorStyleSettings::onPenColorComboIndexChanged); @@ -121,6 +130,9 @@ void PDFPageContentEditorStyleSettings::loadFromElement(const PDFPageContentElem ui->penColorLabel->setEnabled(hasPenColor); ui->selectPenColorButton->setEnabled(hasPenColor); + ui->brushStyleLabel->setEnabled(hasBrush); + ui->brushStyleCombo->setEnabled(hasBrush); + ui->brushColorCombo->setEnabled(hasBrush); ui->brushColorLabel->setEnabled(hasBrush); ui->selectBrushColorButton->setEnabled(hasBrush); @@ -187,6 +199,7 @@ void PDFPageContentEditorStyleSettings::setBrush(const QBrush& brush, bool force const bool oldBlockSignals = blockSignals(true); m_brush = brush; + ui->brushStyleCombo->setCurrentIndex(ui->brushStyleCombo->findData(int(brush.style()))); setColorToComboBox(ui->brushColorCombo, brush.color()); blockSignals(oldBlockSignals); @@ -358,6 +371,16 @@ void PDFPageContentEditorStyleSettings::onPenStyleChanged() } } +void PDFPageContentEditorStyleSettings::onBrushStyleChanged() +{ + Qt::BrushStyle brushStyle = static_cast(ui->brushStyleCombo->currentData().toInt()); + if (m_brush.style() != brushStyle) + { + m_brush.setStyle(brushStyle); + emit brushChanged(m_brush); + } +} + void PDFPageContentEditorStyleSettings::onPenColorComboTextChanged() { QColor color(ui->penColorCombo->currentText()); diff --git a/Pdf4QtLib/sources/pdfpagecontenteditorstylesettings.h b/Pdf4QtLib/sources/pdfpagecontenteditorstylesettings.h index 1412a09..517edce 100644 --- a/Pdf4QtLib/sources/pdfpagecontenteditorstylesettings.h +++ b/Pdf4QtLib/sources/pdfpagecontenteditorstylesettings.h @@ -82,6 +82,7 @@ private slots: void onTextAngleChanged(double value); void onAlignmentRadioButtonClicked(int alignment); void onPenStyleChanged(); + void onBrushStyleChanged(); void onPenColorComboTextChanged(); void onPenColorComboIndexChanged(); void onBrushColorComboTextChanged(); diff --git a/Pdf4QtLib/sources/pdfpagecontenteditorstylesettings.ui b/Pdf4QtLib/sources/pdfpagecontenteditorstylesettings.ui index c9e792e..ccab31d 100644 --- a/Pdf4QtLib/sources/pdfpagecontenteditorstylesettings.ui +++ b/Pdf4QtLib/sources/pdfpagecontenteditorstylesettings.ui @@ -7,13 +7,20 @@ 0 0 344 - 304 + 310 Style Settings + + + + Text Alignment + + + @@ -21,6 +28,42 @@ + + + + Qt::Vertical + + + QSizePolicy::MinimumExpanding + + + + 20 + 40 + + + + + + + + true + + + + + + + + + + + + + true + + + @@ -28,30 +71,14 @@ - - - - - - - ... - - - - - - - + Brush Color - - - - + @@ -131,38 +158,18 @@ - - - - Qt::Vertical + + + + -90.000000000000000 - - QSizePolicy::MinimumExpanding - - - - 20 - 40 - - - - - - - - true + + 90.000000000000000 - - - - Font - - - - - + + ... @@ -175,44 +182,47 @@ - - + + - ... + Font + + + - - - Text Alignment - - - - - - - true - - - - Text Angle - - - - -90.000000000000000 - - - 90.000000000000000 + + + + ... + + + + ... + + + + + + + Brush Style + + + + + + diff --git a/Pdf4QtViewer/resources/pce-align-bottom.svg b/Pdf4QtViewer/resources/pce-align-bottom.svg index d2a3670..9b67948 100644 --- a/Pdf4QtViewer/resources/pce-align-bottom.svg +++ b/Pdf4QtViewer/resources/pce-align-bottom.svg @@ -1,57 +1,14 @@ - + - -image/svg+xml - -? - \ No newline at end of file + + + + + + + + diff --git a/Pdf4QtViewer/resources/pce-align-h-center.svg b/Pdf4QtViewer/resources/pce-align-h-center.svg index d2a3670..3c55698 100644 --- a/Pdf4QtViewer/resources/pce-align-h-center.svg +++ b/Pdf4QtViewer/resources/pce-align-h-center.svg @@ -1,57 +1,16 @@ - + - -image/svg+xml - -? - \ No newline at end of file + + + + + + + + + + diff --git a/Pdf4QtViewer/resources/pce-align-left.svg b/Pdf4QtViewer/resources/pce-align-left.svg index d2a3670..100ab7c 100644 --- a/Pdf4QtViewer/resources/pce-align-left.svg +++ b/Pdf4QtViewer/resources/pce-align-left.svg @@ -1,57 +1,14 @@ - + - -image/svg+xml - -? - \ No newline at end of file + + + + + + + + diff --git a/Pdf4QtViewer/resources/pce-align-right.svg b/Pdf4QtViewer/resources/pce-align-right.svg index d2a3670..9dcd851 100644 --- a/Pdf4QtViewer/resources/pce-align-right.svg +++ b/Pdf4QtViewer/resources/pce-align-right.svg @@ -1,57 +1,14 @@ - + - -image/svg+xml - -? - \ No newline at end of file + + + + + + + + diff --git a/Pdf4QtViewer/resources/pce-align-top.svg b/Pdf4QtViewer/resources/pce-align-top.svg index d2a3670..5eac6ef 100644 --- a/Pdf4QtViewer/resources/pce-align-top.svg +++ b/Pdf4QtViewer/resources/pce-align-top.svg @@ -1,57 +1,16 @@ - + - -image/svg+xml - -? - \ No newline at end of file + + + + + + + + + + diff --git a/Pdf4QtViewer/resources/pce-align-v-center.svg b/Pdf4QtViewer/resources/pce-align-v-center.svg index d2a3670..609e684 100644 --- a/Pdf4QtViewer/resources/pce-align-v-center.svg +++ b/Pdf4QtViewer/resources/pce-align-v-center.svg @@ -1,57 +1,16 @@ - + - -image/svg+xml - -? - \ No newline at end of file + + + + + + + + + + diff --git a/Pdf4QtViewer/resources/pce-apply-changes.svg b/Pdf4QtViewer/resources/pce-apply-changes.svg new file mode 100644 index 0000000..44dc6a6 --- /dev/null +++ b/Pdf4QtViewer/resources/pce-apply-changes.svg @@ -0,0 +1,68 @@ + + + + + + + + + + + + + + + + + + + diff --git a/Pdf4QtViewer/resources/pce-center-h.svg b/Pdf4QtViewer/resources/pce-center-h.svg index d2a3670..2a4907c 100644 --- a/Pdf4QtViewer/resources/pce-center-h.svg +++ b/Pdf4QtViewer/resources/pce-center-h.svg @@ -1,57 +1,22 @@ - + - -image/svg+xml - -? - \ No newline at end of file + + + + + + + + + diff --git a/Pdf4QtViewer/resources/pce-center-v.svg b/Pdf4QtViewer/resources/pce-center-v.svg index d2a3670..316a762 100644 --- a/Pdf4QtViewer/resources/pce-center-v.svg +++ b/Pdf4QtViewer/resources/pce-center-v.svg @@ -1,57 +1,22 @@ - + - -image/svg+xml - -? - \ No newline at end of file + + + + + + + + + diff --git a/Pdf4QtViewer/resources/pce-center-vh.svg b/Pdf4QtViewer/resources/pce-center-vh.svg index d2a3670..6ec89fc 100644 --- a/Pdf4QtViewer/resources/pce-center-vh.svg +++ b/Pdf4QtViewer/resources/pce-center-vh.svg @@ -1,57 +1,26 @@ - + - -image/svg+xml - -? - \ No newline at end of file + + + + + + + + + + + + diff --git a/Pdf4QtViewer/resources/pce-layout-form.svg b/Pdf4QtViewer/resources/pce-layout-form.svg index d2a3670..e4550fe 100644 --- a/Pdf4QtViewer/resources/pce-layout-form.svg +++ b/Pdf4QtViewer/resources/pce-layout-form.svg @@ -1,57 +1,20 @@ - + - -image/svg+xml - -? - \ No newline at end of file + + + + + + + + diff --git a/Pdf4QtViewer/resources/pce-layout-grid.svg b/Pdf4QtViewer/resources/pce-layout-grid.svg index d2a3670..6001bba 100644 --- a/Pdf4QtViewer/resources/pce-layout-grid.svg +++ b/Pdf4QtViewer/resources/pce-layout-grid.svg @@ -1,57 +1,65 @@ - + - -image/svg+xml - -? - \ No newline at end of file + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Pdf4QtViewer/resources/pce-layout-h.svg b/Pdf4QtViewer/resources/pce-layout-h.svg index d2a3670..91a63b2 100644 --- a/Pdf4QtViewer/resources/pce-layout-h.svg +++ b/Pdf4QtViewer/resources/pce-layout-h.svg @@ -1,57 +1,24 @@ - + - -image/svg+xml - -? - \ No newline at end of file + + + + + + + + + + + diff --git a/Pdf4QtViewer/resources/pce-layout-v.svg b/Pdf4QtViewer/resources/pce-layout-v.svg index d2a3670..059febe 100644 --- a/Pdf4QtViewer/resources/pce-layout-v.svg +++ b/Pdf4QtViewer/resources/pce-layout-v.svg @@ -1,57 +1,22 @@ - + - -image/svg+xml - -? - \ No newline at end of file + + + + + + + + + diff --git a/Pdf4QtViewer/resources/pce-same-height.svg b/Pdf4QtViewer/resources/pce-same-height.svg index d2a3670..0c4ec18 100644 --- a/Pdf4QtViewer/resources/pce-same-height.svg +++ b/Pdf4QtViewer/resources/pce-same-height.svg @@ -1,57 +1,19 @@ - + - -image/svg+xml - -? - \ No newline at end of file + + + + + + + + diff --git a/Pdf4QtViewer/resources/pce-same-size.svg b/Pdf4QtViewer/resources/pce-same-size.svg index d2a3670..a43a441 100644 --- a/Pdf4QtViewer/resources/pce-same-size.svg +++ b/Pdf4QtViewer/resources/pce-same-size.svg @@ -1,57 +1,30 @@ - + - -image/svg+xml - -? - \ No newline at end of file + + + + + + + + + + + diff --git a/Pdf4QtViewer/resources/pce-same-width.svg b/Pdf4QtViewer/resources/pce-same-width.svg index d2a3670..e4525f0 100644 --- a/Pdf4QtViewer/resources/pce-same-width.svg +++ b/Pdf4QtViewer/resources/pce-same-width.svg @@ -1,57 +1,19 @@ - + - -image/svg+xml - -? - \ No newline at end of file + + + + + + + + diff --git a/Pdf4QtViewer/resources/pce-select-brush.svg b/Pdf4QtViewer/resources/pce-select-brush.svg new file mode 100644 index 0000000..6a450c1 --- /dev/null +++ b/Pdf4QtViewer/resources/pce-select-brush.svg @@ -0,0 +1,47 @@ + + + + + + + + + + + + + diff --git a/Pdf4QtViewer/resources/pce-select-color.svg b/Pdf4QtViewer/resources/pce-select-color.svg new file mode 100644 index 0000000..df8183d --- /dev/null +++ b/Pdf4QtViewer/resources/pce-select-color.svg @@ -0,0 +1,28 @@ + + + + + + + + + diff --git a/Pdf4QtViewer/resources/pce-select-font.svg b/Pdf4QtViewer/resources/pce-select-font.svg new file mode 100644 index 0000000..f2979d1 --- /dev/null +++ b/Pdf4QtViewer/resources/pce-select-font.svg @@ -0,0 +1,36 @@ + + + + + + + + + + + diff --git a/Pdf4QtViewer/resources/pce-select-pen.svg b/Pdf4QtViewer/resources/pce-select-pen.svg new file mode 100644 index 0000000..fb5fece --- /dev/null +++ b/Pdf4QtViewer/resources/pce-select-pen.svg @@ -0,0 +1,50 @@ + + + + + + + + + + + + + diff --git a/Pdf4QtViewerLite/app-icon.ico b/Pdf4QtViewerLite/app-icon.ico index dedae02e1241eacad8c51405488f875e11c4357f..06c4c455dca86260b1b906818cdc920e38ecd689 100644 GIT binary patch literal 8101 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 literal 7341 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 A*#H0l diff --git a/Pdf4QtViewerLite/app-icon.svg b/Pdf4QtViewerLite/app-icon.svg new file mode 100644 index 0000000..3921568 --- /dev/null +++ b/Pdf4QtViewerLite/app-icon.svg @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + diff --git a/Pdf4QtViewerPlugins/SignaturePlugin/activate.svg b/Pdf4QtViewerPlugins/SignaturePlugin/activate.svg index a4f2e18..4869237 100644 --- a/Pdf4QtViewerPlugins/SignaturePlugin/activate.svg +++ b/Pdf4QtViewerPlugins/SignaturePlugin/activate.svg @@ -1,59 +1,35 @@ - + - -image/svg+xml - - - -? - \ No newline at end of file + + + + + + + + + + + diff --git a/Pdf4QtViewerPlugins/SignaturePlugin/certificates.svg b/Pdf4QtViewerPlugins/SignaturePlugin/certificates.svg index a4f2e18..c534a8b 100644 --- a/Pdf4QtViewerPlugins/SignaturePlugin/certificates.svg +++ b/Pdf4QtViewerPlugins/SignaturePlugin/certificates.svg @@ -1,59 +1,80 @@ - + - -image/svg+xml - - - -? - \ No newline at end of file + + + + + + + + + + + diff --git a/Pdf4QtViewerPlugins/SignaturePlugin/clear.svg b/Pdf4QtViewerPlugins/SignaturePlugin/clear.svg index a4f2e18..9fbb320 100644 --- a/Pdf4QtViewerPlugins/SignaturePlugin/clear.svg +++ b/Pdf4QtViewerPlugins/SignaturePlugin/clear.svg @@ -1,59 +1,51 @@ - + - -image/svg+xml - - - -? - \ No newline at end of file + + + + + + + + + + + + + + + diff --git a/Pdf4QtViewerPlugins/SignaturePlugin/create-dot.svg b/Pdf4QtViewerPlugins/SignaturePlugin/create-dot.svg index a4f2e18..97427e1 100644 --- a/Pdf4QtViewerPlugins/SignaturePlugin/create-dot.svg +++ b/Pdf4QtViewerPlugins/SignaturePlugin/create-dot.svg @@ -1,59 +1,16 @@ - + - -image/svg+xml - - - -? - \ No newline at end of file + + + + + + + + + + diff --git a/Pdf4QtViewerPlugins/SignaturePlugin/create-freehand-curve.svg b/Pdf4QtViewerPlugins/SignaturePlugin/create-freehand-curve.svg index a4f2e18..3b8e3b4 100644 --- a/Pdf4QtViewerPlugins/SignaturePlugin/create-freehand-curve.svg +++ b/Pdf4QtViewerPlugins/SignaturePlugin/create-freehand-curve.svg @@ -1,59 +1,14 @@ - + - -image/svg+xml - - - -? - \ No newline at end of file + + + + + + + + diff --git a/Pdf4QtViewerPlugins/SignaturePlugin/create-horizontal-line.svg b/Pdf4QtViewerPlugins/SignaturePlugin/create-horizontal-line.svg index a4f2e18..34272e6 100644 --- a/Pdf4QtViewerPlugins/SignaturePlugin/create-horizontal-line.svg +++ b/Pdf4QtViewerPlugins/SignaturePlugin/create-horizontal-line.svg @@ -1,59 +1,19 @@ - + - -image/svg+xml - - - -? - \ No newline at end of file + + + + + + + + + + + diff --git a/Pdf4QtViewerPlugins/SignaturePlugin/create-line.svg b/Pdf4QtViewerPlugins/SignaturePlugin/create-line.svg index a4f2e18..ff400fb 100644 --- a/Pdf4QtViewerPlugins/SignaturePlugin/create-line.svg +++ b/Pdf4QtViewerPlugins/SignaturePlugin/create-line.svg @@ -1,59 +1,16 @@ - + - -image/svg+xml - - - -? - \ No newline at end of file + + + + + + + + + + diff --git a/Pdf4QtViewerPlugins/SignaturePlugin/create-no-mark.svg b/Pdf4QtViewerPlugins/SignaturePlugin/create-no-mark.svg index a4f2e18..a5f20ea 100644 --- a/Pdf4QtViewerPlugins/SignaturePlugin/create-no-mark.svg +++ b/Pdf4QtViewerPlugins/SignaturePlugin/create-no-mark.svg @@ -1,59 +1,28 @@ - + - -image/svg+xml - - - -? - \ No newline at end of file + + + + + + + + + + + diff --git a/Pdf4QtViewerPlugins/SignaturePlugin/create-rectangle.svg b/Pdf4QtViewerPlugins/SignaturePlugin/create-rectangle.svg index a4f2e18..5b3c056 100644 --- a/Pdf4QtViewerPlugins/SignaturePlugin/create-rectangle.svg +++ b/Pdf4QtViewerPlugins/SignaturePlugin/create-rectangle.svg @@ -1,59 +1,14 @@ - + - -image/svg+xml - - - -? - \ No newline at end of file + + + + + + + + + diff --git a/Pdf4QtViewerPlugins/SignaturePlugin/create-rounded-rectangle.svg b/Pdf4QtViewerPlugins/SignaturePlugin/create-rounded-rectangle.svg index a4f2e18..397628a 100644 --- a/Pdf4QtViewerPlugins/SignaturePlugin/create-rounded-rectangle.svg +++ b/Pdf4QtViewerPlugins/SignaturePlugin/create-rounded-rectangle.svg @@ -1,59 +1,14 @@ - + - -image/svg+xml - - - -? - \ No newline at end of file + + + + + + + + diff --git a/Pdf4QtViewerPlugins/SignaturePlugin/create-svg-image.svg b/Pdf4QtViewerPlugins/SignaturePlugin/create-svg-image.svg index a4f2e18..0d46519 100644 --- a/Pdf4QtViewerPlugins/SignaturePlugin/create-svg-image.svg +++ b/Pdf4QtViewerPlugins/SignaturePlugin/create-svg-image.svg @@ -1,59 +1,23 @@ - + - -image/svg+xml - - - -? - \ No newline at end of file + + + + + + + + + + diff --git a/Pdf4QtViewerPlugins/SignaturePlugin/create-text.svg b/Pdf4QtViewerPlugins/SignaturePlugin/create-text.svg index a4f2e18..8e4173a 100644 --- a/Pdf4QtViewerPlugins/SignaturePlugin/create-text.svg +++ b/Pdf4QtViewerPlugins/SignaturePlugin/create-text.svg @@ -1,59 +1,23 @@ - + - -image/svg+xml - - - -? - \ No newline at end of file + + + + + + + + + + + + diff --git a/Pdf4QtViewerPlugins/SignaturePlugin/create-vertical-line.svg b/Pdf4QtViewerPlugins/SignaturePlugin/create-vertical-line.svg index a4f2e18..6b4239f 100644 --- a/Pdf4QtViewerPlugins/SignaturePlugin/create-vertical-line.svg +++ b/Pdf4QtViewerPlugins/SignaturePlugin/create-vertical-line.svg @@ -1,59 +1,19 @@ - + - -image/svg+xml - - - -? - \ No newline at end of file + + + + + + + + + + + diff --git a/Pdf4QtViewerPlugins/SignaturePlugin/create-yes-mark.svg b/Pdf4QtViewerPlugins/SignaturePlugin/create-yes-mark.svg index a4f2e18..d9c53c8 100644 --- a/Pdf4QtViewerPlugins/SignaturePlugin/create-yes-mark.svg +++ b/Pdf4QtViewerPlugins/SignaturePlugin/create-yes-mark.svg @@ -1,59 +1,21 @@ - + - -image/svg+xml - - - -? - \ No newline at end of file + + + + + + + + + + + diff --git a/Pdf4QtViewerPlugins/SignaturePlugin/sign-digitally.svg b/Pdf4QtViewerPlugins/SignaturePlugin/sign-digitally.svg index a4f2e18..5fdaee5 100644 --- a/Pdf4QtViewerPlugins/SignaturePlugin/sign-digitally.svg +++ b/Pdf4QtViewerPlugins/SignaturePlugin/sign-digitally.svg @@ -1,59 +1,109 @@ - + - -image/svg+xml - - - -? - \ No newline at end of file + + + + + + + + + + + + + + + + + + + diff --git a/Pdf4QtViewerPlugins/SignaturePlugin/sign-electronically.svg b/Pdf4QtViewerPlugins/SignaturePlugin/sign-electronically.svg index a4f2e18..e1286f9 100644 --- a/Pdf4QtViewerPlugins/SignaturePlugin/sign-electronically.svg +++ b/Pdf4QtViewerPlugins/SignaturePlugin/sign-electronically.svg @@ -1,59 +1,44 @@ - + - -image/svg+xml - - - -? - \ No newline at end of file + + + + + + + + + + + + + + + diff --git a/Pdf4QtViewerProfi/app-icon.ico b/Pdf4QtViewerProfi/app-icon.ico index dedae02e1241eacad8c51405488f875e11c4357f..d4f4e1fb20c10f8f481a680c893e6c3f741ddefb 100644 GIT binary patch literal 9895 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 abPI8}EE+?qmhl4Mp(Lj+TP|((?tcKX%rT1q literal 7341 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 A*#H0l diff --git a/Pdf4QtViewerProfi/app-icon.svg b/Pdf4QtViewerProfi/app-icon.svg new file mode 100644 index 0000000..c325e3a --- /dev/null +++ b/Pdf4QtViewerProfi/app-icon.svg @@ -0,0 +1,48 @@ + + + + + + + + + + + + + + + + + + From ad30920bdbb469f9b64cceada113f4dbec2d6922 Mon Sep 17 00:00:00 2001 From: Jakub Melka Date: Fri, 15 Apr 2022 17:59:24 +0200 Subject: [PATCH 25/39] Signature plugin: Svg/Png custom image selection, bugfixing --- .../pdfpagecontenteditorstylesettings.cpp | 1 + .../sources/pdfpagecontenteditortools.cpp | 82 ++++++++++++++++-- Pdf4QtLib/sources/pdfpagecontenteditortools.h | 27 +++--- Pdf4QtLib/sources/pdfpagecontentelements.cpp | 83 +++++++++++++------ Pdf4QtLib/sources/pdfpagecontentelements.h | 9 +- Pdf4QtViewer/resources/pce-layout-h.svg | 30 ++++--- Pdf4QtViewer/resources/pce-layout-v.svg | 30 +++---- .../SignaturePlugin/signatureplugin.cpp | 7 +- .../SignaturePlugin/signatureplugin.h | 1 + 9 files changed, 187 insertions(+), 83 deletions(-) diff --git a/Pdf4QtLib/sources/pdfpagecontenteditorstylesettings.cpp b/Pdf4QtLib/sources/pdfpagecontenteditorstylesettings.cpp index 268086a..45793fd 100644 --- a/Pdf4QtLib/sources/pdfpagecontenteditorstylesettings.cpp +++ b/Pdf4QtLib/sources/pdfpagecontenteditorstylesettings.cpp @@ -232,6 +232,7 @@ void PDFPageContentEditorStyleSettings::setFontAlignment(Qt::Alignment alignment radioButton->setChecked(false); } + m_alignment = alignment; QRadioButton* radioButton = qobject_cast(m_alignmentMapper.mapping(int(alignment))); radioButton->setChecked(true); diff --git a/Pdf4QtLib/sources/pdfpagecontenteditortools.cpp b/Pdf4QtLib/sources/pdfpagecontenteditortools.cpp index 1586688..0ba7779 100644 --- a/Pdf4QtLib/sources/pdfpagecontenteditortools.cpp +++ b/Pdf4QtLib/sources/pdfpagecontenteditortools.cpp @@ -19,10 +19,13 @@ #include "pdfpagecontentelements.h" #include "pdfpainterutils.h" #include "pdftexteditpseudowidget.h" +#include "pdfdrawwidget.h" #include #include #include +#include +#include #include namespace pdf @@ -303,32 +306,34 @@ void PDFCreatePCElementLineTool::onPointPicked(PDFInteger pageIndex, QPointF pag setActive(false); } -PDFCreatePCElementSvgTool::PDFCreatePCElementSvgTool(PDFDrawWidgetProxy* proxy, +PDFCreatePCElementImageTool::PDFCreatePCElementImageTool(PDFDrawWidgetProxy* proxy, PDFPageContentScene* scene, QAction* action, QByteArray content, + bool askSelectImage, QObject* parent) : BaseClass(proxy, scene, action, parent), m_pickTool(nullptr), - m_element(nullptr) + m_element(nullptr), + m_askSelectImage(askSelectImage) { m_pickTool = new PDFPickTool(proxy, PDFPickTool::Mode::Rectangles, this); m_pickTool->setDrawSelectionRectangle(false); addTool(m_pickTool); - connect(m_pickTool, &PDFPickTool::rectanglePicked, this, &PDFCreatePCElementSvgTool::onRectanglePicked); + connect(m_pickTool, &PDFPickTool::rectanglePicked, this, &PDFCreatePCElementImageTool::onRectanglePicked); - m_element = new PDFPageContentSvgElement(); + m_element = new PDFPageContentImageElement(); m_element->setContent(content); updateActions(); } -PDFCreatePCElementSvgTool::~PDFCreatePCElementSvgTool() +PDFCreatePCElementImageTool::~PDFCreatePCElementImageTool() { delete m_element; } -void PDFCreatePCElementSvgTool::drawPage(QPainter* painter, +void PDFCreatePCElementImageTool::drawPage(QPainter* painter, PDFInteger pageIndex, const PDFPrecompiledPage* compiledPage, PDFTextLayoutGetter& layoutGetter, @@ -363,17 +368,76 @@ void PDFCreatePCElementSvgTool::drawPage(QPainter* painter, m_element->drawPage(painter, pageIndex, compiledPage, layoutGetter, pagePointToDevicePointMatrix, errors); } -const PDFPageContentElement* PDFCreatePCElementSvgTool::getElement() const +const PDFPageContentElement* PDFCreatePCElementImageTool::getElement() const { return m_element; } -PDFPageContentElement* PDFCreatePCElementSvgTool::getElement() +PDFPageContentElement* PDFCreatePCElementImageTool::getElement() { return m_element; } -void PDFCreatePCElementSvgTool::onRectanglePicked(PDFInteger pageIndex, QRectF pageRectangle) +void PDFCreatePCElementImageTool::setActiveImpl(bool active) +{ + BaseClass::setActiveImpl(active); + + if (active && m_askSelectImage) + { + QTimer::singleShot(0, this, &PDFCreatePCElementImageTool::selectImage); + } +} + +void PDFCreatePCElementImageTool::selectImage() +{ + if (m_imageDirectory.isEmpty()) + { + QStringList pictureDirectiories = QStandardPaths::standardLocations(QStandardPaths::PicturesLocation); + if (!pictureDirectiories.isEmpty()) + { + m_imageDirectory = pictureDirectiories.last(); + } + else + { + m_imageDirectory = QDir::currentPath(); + } + } + + QList mimeTypes = QImageReader::supportedMimeTypes(); + QStringList mimeTypeFilters; + for (const QByteArray& mimeType : mimeTypes) + { + mimeTypeFilters.append(mimeType); + } + + QFileDialog dialog(getProxy()->getWidget(), tr("Select Image")); + dialog.setDirectory(m_imageDirectory); + dialog.setMimeTypeFilters(mimeTypeFilters); + dialog.selectMimeTypeFilter("image/svg+xml"); + dialog.setAcceptMode(QFileDialog::AcceptOpen); + dialog.setFileMode(QFileDialog::ExistingFile); + + if (dialog.exec() == QFileDialog::Accepted) + { + QString fileName = dialog.selectedFiles().constFirst(); + QFile file(fileName); + if (file.open(QFile::ReadOnly)) + { + m_element->setContent(file.readAll()); + file.close(); + } + else + { + setActive(false); + } + } + else + { + setActive(false); + } +} + +void PDFCreatePCElementImageTool::onRectanglePicked(PDFInteger pageIndex, QRectF pageRectangle) { if (pageRectangle.isEmpty()) { diff --git a/Pdf4QtLib/sources/pdfpagecontenteditortools.h b/Pdf4QtLib/sources/pdfpagecontenteditortools.h index 7f274a2..568887b 100644 --- a/Pdf4QtLib/sources/pdfpagecontenteditortools.h +++ b/Pdf4QtLib/sources/pdfpagecontenteditortools.h @@ -25,7 +25,7 @@ namespace pdf class PDFPageContentScene; class PDFPageContentElement; -class PDFPageContentSvgElement; +class PDFPageContentImageElement; class PDFPageContentElementDot; class PDFPageContentElementLine; class PDFPageContentElementTextBox; @@ -90,8 +90,8 @@ private: PDFPageContentElementRectangle* m_element; }; -/// Tool that displays SVG image -class PDF4QTLIBSHARED_EXPORT PDFCreatePCElementSvgTool : public PDFCreatePCElementTool +/// Tool that displays SVG image (or raster image) +class PDF4QTLIBSHARED_EXPORT PDFCreatePCElementImageTool : public PDFCreatePCElementTool { Q_OBJECT @@ -99,12 +99,13 @@ private: using BaseClass = PDFCreatePCElementTool; public: - explicit PDFCreatePCElementSvgTool(PDFDrawWidgetProxy* proxy, - PDFPageContentScene* scene, - QAction* action, - QByteArray content, - QObject* parent); - virtual ~PDFCreatePCElementSvgTool() override; + explicit PDFCreatePCElementImageTool(PDFDrawWidgetProxy* proxy, + PDFPageContentScene* scene, + QAction* action, + QByteArray content, + bool askSelectImage, + QObject* parent); + virtual ~PDFCreatePCElementImageTool() override; virtual void drawPage(QPainter* painter, PDFInteger pageIndex, @@ -116,11 +117,17 @@ public: virtual const PDFPageContentElement* getElement() const override; virtual PDFPageContentElement* getElement() override; +protected: + virtual void setActiveImpl(bool active) override; + private: + void selectImage(); void onRectanglePicked(pdf::PDFInteger pageIndex, QRectF pageRectangle); PDFPickTool* m_pickTool; - PDFPageContentSvgElement* m_element; + PDFPageContentImageElement* m_element; + bool m_askSelectImage; + QString m_imageDirectory; }; /// Tool that creates line element. diff --git a/Pdf4QtLib/sources/pdfpagecontentelements.cpp b/Pdf4QtLib/sources/pdfpagecontentelements.cpp index 800875d..c79f92f 100644 --- a/Pdf4QtLib/sources/pdfpagecontentelements.cpp +++ b/Pdf4QtLib/sources/pdfpagecontentelements.cpp @@ -27,6 +27,7 @@ #include #include #include +#include namespace pdf { @@ -1066,20 +1067,20 @@ void PDFPageContentElementLine::setLine(const QLineF& newLine) } } -PDFPageContentSvgElement::PDFPageContentSvgElement() : +PDFPageContentImageElement::PDFPageContentImageElement() : m_renderer(std::make_unique()) { } -PDFPageContentSvgElement::~PDFPageContentSvgElement() +PDFPageContentImageElement::~PDFPageContentImageElement() { } -PDFPageContentSvgElement* PDFPageContentSvgElement::clone() const +PDFPageContentImageElement* PDFPageContentImageElement::clone() const { - PDFPageContentSvgElement* copy = new PDFPageContentSvgElement(); + PDFPageContentImageElement* copy = new PDFPageContentImageElement(); copy->setElementId(getElementId()); copy->setPageIndex(getPageIndex()); copy->setRectangle(getRectangle()); @@ -1087,7 +1088,7 @@ PDFPageContentSvgElement* PDFPageContentSvgElement::clone() const return copy; } -void PDFPageContentSvgElement::drawPage(QPainter* painter, +void PDFPageContentImageElement::drawPage(QPainter* painter, PDFInteger pageIndex, const PDFPrecompiledPage* compiledPage, PDFTextLayoutGetter& layoutGetter, @@ -1107,70 +1108,98 @@ void PDFPageContentSvgElement::drawPage(QPainter* painter, painter->setWorldMatrix(pagePointToDevicePointMatrix, true); painter->setRenderHint(QPainter::Antialiasing); - QRectF viewBox = m_renderer->viewBoxF(); - if (!viewBox.isValid()) + if (m_renderer->isValid()) { - return; + QRectF viewBox = m_renderer->viewBoxF(); + if (!viewBox.isValid()) + { + return; + } + + QRectF renderBox = getRectangle(); + QSizeF viewBoxSize = viewBox.size(); + QSizeF renderBoxSize = viewBoxSize.scaled(renderBox.size(), Qt::KeepAspectRatio); + QRectF targetRenderBox = QRectF(QPointF(), renderBoxSize); + targetRenderBox.moveCenter(renderBox.center()); + + painter->translate(targetRenderBox.bottomLeft()); + painter->scale(1.0, -1.0); + targetRenderBox.moveTopLeft(QPointF(0, 0)); + + m_renderer->render(painter, targetRenderBox); } + else if (!m_image.isNull()) + { + QRectF viewBox(QPointF(0, 0), m_image.size()); - QRectF renderBox = getRectangle(); - QSizeF viewBoxSize = viewBox.size(); - QSizeF renderBoxSize = viewBoxSize.scaled(renderBox.size(), Qt::KeepAspectRatio); - QRectF targetRenderBox = QRectF(QPointF(), renderBoxSize); - targetRenderBox.moveCenter(renderBox.center()); + QRectF renderBox = getRectangle(); + QSizeF viewBoxSize = viewBox.size(); + QSizeF renderBoxSize = viewBoxSize.scaled(renderBox.size(), Qt::KeepAspectRatio); + QRectF targetRenderBox = QRectF(QPointF(), renderBoxSize); + targetRenderBox.moveCenter(renderBox.center()); - painter->translate(targetRenderBox.bottomLeft()); - painter->scale(1.0, -1.0); - targetRenderBox.moveTopLeft(QPointF(0, 0)); + painter->translate(targetRenderBox.bottomLeft()); + painter->scale(1.0, -1.0); + targetRenderBox.moveTopLeft(QPointF(0, 0)); - m_renderer->render(painter, targetRenderBox); + painter->drawImage(targetRenderBox, m_image); + } } -uint PDFPageContentSvgElement::getManipulationMode(const QPointF& point, PDFReal snapPointDistanceThreshold) const +uint PDFPageContentImageElement::getManipulationMode(const QPointF& point, PDFReal snapPointDistanceThreshold) const { return getRectangleManipulationMode(getRectangle(), point, snapPointDistanceThreshold); } -void PDFPageContentSvgElement::performManipulation(uint mode, const QPointF& offset) +void PDFPageContentImageElement::performManipulation(uint mode, const QPointF& offset) { performRectangleManipulation(m_rectangle, mode, offset); } -QRectF PDFPageContentSvgElement::getBoundingBox() const +QRectF PDFPageContentImageElement::getBoundingBox() const { return getRectangle(); } -void PDFPageContentSvgElement::setSize(QSizeF size) +void PDFPageContentImageElement::setSize(QSizeF size) { performRectangleSetSize(m_rectangle, size); } -QString PDFPageContentSvgElement::getDescription() const +QString PDFPageContentImageElement::getDescription() const { return formatDescription(PDFTranslationContext::tr("SVG image")); } -const QByteArray& PDFPageContentSvgElement::getContent() const +const QByteArray& PDFPageContentImageElement::getContent() const { return m_content; } -void PDFPageContentSvgElement::setContent(const QByteArray& newContent) +void PDFPageContentImageElement::setContent(const QByteArray& newContent) { if (m_content != newContent) { m_content = newContent; - m_renderer->load(m_content); + if (!m_renderer->load(m_content)) + { + QByteArray imageData = m_content; + QBuffer buffer(&imageData); + buffer.open(QBuffer::ReadOnly); + + QImageReader reader(&buffer); + m_image = reader.read(); + buffer.close();; + } } } -const QRectF& PDFPageContentSvgElement::getRectangle() const +const QRectF& PDFPageContentImageElement::getRectangle() const { return m_rectangle; } -void PDFPageContentSvgElement::setRectangle(const QRectF& newRectangle) +void PDFPageContentImageElement::setRectangle(const QRectF& newRectangle) { m_rectangle = newRectangle; } diff --git a/Pdf4QtLib/sources/pdfpagecontentelements.h b/Pdf4QtLib/sources/pdfpagecontentelements.h index 20d3078..554b7ae 100644 --- a/Pdf4QtLib/sources/pdfpagecontentelements.h +++ b/Pdf4QtLib/sources/pdfpagecontentelements.h @@ -270,13 +270,13 @@ private: QPainterPath m_curve; }; -class PDF4QTLIBSHARED_EXPORT PDFPageContentSvgElement : public PDFPageContentElement +class PDF4QTLIBSHARED_EXPORT PDFPageContentImageElement : public PDFPageContentElement { public: - PDFPageContentSvgElement(); - virtual ~PDFPageContentSvgElement(); + PDFPageContentImageElement(); + virtual ~PDFPageContentImageElement(); - virtual PDFPageContentSvgElement* clone() const override; + virtual PDFPageContentImageElement* clone() const override; virtual void drawPage(QPainter* painter, PDFInteger pageIndex, @@ -302,6 +302,7 @@ public: private: QRectF m_rectangle; QByteArray m_content; + QImage m_image; std::unique_ptr m_renderer; }; diff --git a/Pdf4QtViewer/resources/pce-layout-h.svg b/Pdf4QtViewer/resources/pce-layout-h.svg index 91a63b2..059febe 100644 --- a/Pdf4QtViewer/resources/pce-layout-h.svg +++ b/Pdf4QtViewer/resources/pce-layout-h.svg @@ -3,22 +3,20 @@ + - - - - - - + + + diff --git a/Pdf4QtViewer/resources/pce-layout-v.svg b/Pdf4QtViewer/resources/pce-layout-v.svg index 059febe..91a63b2 100644 --- a/Pdf4QtViewer/resources/pce-layout-v.svg +++ b/Pdf4QtViewer/resources/pce-layout-v.svg @@ -3,20 +3,22 @@ - - - - + + + + + + diff --git a/Pdf4QtViewerPlugins/SignaturePlugin/signatureplugin.cpp b/Pdf4QtViewerPlugins/SignaturePlugin/signatureplugin.cpp index c11057f..8e50da4 100644 --- a/Pdf4QtViewerPlugins/SignaturePlugin/signatureplugin.cpp +++ b/Pdf4QtViewerPlugins/SignaturePlugin/signatureplugin.cpp @@ -126,14 +126,15 @@ void SignaturePlugin::setWidget(pdf::PDFWidget* widget) m_tools[TextTool] = new pdf::PDFCreatePCElementTextTool(widget->getDrawWidgetProxy(), &m_scene, createTextAction, this); m_tools[FreehandCurveTool] = new pdf::PDFCreatePCElementFreehandCurveTool(widget->getDrawWidgetProxy(), &m_scene, createFreehandCurveAction, this); - m_tools[AcceptMarkTool] = new pdf::PDFCreatePCElementSvgTool(widget->getDrawWidgetProxy(), &m_scene, createAcceptMarkAction, acceptMarkContent, this); - m_tools[RejectMarkTool] = new pdf::PDFCreatePCElementSvgTool(widget->getDrawWidgetProxy(), &m_scene, createRejectMarkAction, rejectMarkContent, this); + m_tools[AcceptMarkTool] = new pdf::PDFCreatePCElementImageTool(widget->getDrawWidgetProxy(), &m_scene, createAcceptMarkAction, acceptMarkContent, false, this); + m_tools[RejectMarkTool] = new pdf::PDFCreatePCElementImageTool(widget->getDrawWidgetProxy(), &m_scene, createRejectMarkAction, rejectMarkContent, false, this); m_tools[RectangleTool] = new pdf::PDFCreatePCElementRectangleTool(widget->getDrawWidgetProxy(), &m_scene, createRectangleAction, false, this); m_tools[RoundedRectangleTool] = new pdf::PDFCreatePCElementRectangleTool(widget->getDrawWidgetProxy(), &m_scene, createRoundedRectangleAction, true, this); m_tools[HorizontalLineTool] = new pdf::PDFCreatePCElementLineTool(widget->getDrawWidgetProxy(), &m_scene, createHorizontalLineAction, true, false, this); m_tools[VerticalLineTool] = new pdf::PDFCreatePCElementLineTool(widget->getDrawWidgetProxy(), &m_scene, createVerticalLineAction, false, true, this); m_tools[LineTool] = new pdf::PDFCreatePCElementLineTool(widget->getDrawWidgetProxy(), &m_scene, createLineAction, false, false, this); m_tools[DotTool] = new pdf::PDFCreatePCElementDotTool(widget->getDrawWidgetProxy(), &m_scene, createDotAction, this); + m_tools[ImageTool] = new pdf::PDFCreatePCElementImageTool(widget->getDrawWidgetProxy(), &m_scene, createSvgImageAction, QByteArray(), true, this); pdf::PDFToolManager* toolManager = widget->getToolManager(); for (pdf::PDFWidgetTool* tool : m_tools) @@ -287,7 +288,7 @@ void SignaturePlugin::setActive(bool active) if (pdf::PDFWidgetTool* tool = m_widget->getToolManager()->getActiveTool()) { auto it = std::find(m_tools.cbegin(), m_tools.cend(), tool); - if (it == m_tools.cend()) + if (it != m_tools.cend()) { m_widget->getToolManager()->setActiveTool(nullptr); } diff --git a/Pdf4QtViewerPlugins/SignaturePlugin/signatureplugin.h b/Pdf4QtViewerPlugins/SignaturePlugin/signatureplugin.h index cc6a156..cf25766 100644 --- a/Pdf4QtViewerPlugins/SignaturePlugin/signatureplugin.h +++ b/Pdf4QtViewerPlugins/SignaturePlugin/signatureplugin.h @@ -98,6 +98,7 @@ private: VerticalLineTool, LineTool, DotTool, + ImageTool, LastTool }; From 4fae8a7405b39cbad27e991a9a6f194c9172ae6e Mon Sep 17 00:00:00 2001 From: Jakub Melka Date: Sat, 16 Apr 2022 17:53:55 +0200 Subject: [PATCH 26/39] Signature plugin: Text box draw fix --- Pdf4QtLib/sources/pdfpagecontentelements.cpp | 59 +++++++++++++++++++- 1 file changed, 57 insertions(+), 2 deletions(-) diff --git a/Pdf4QtLib/sources/pdfpagecontentelements.cpp b/Pdf4QtLib/sources/pdfpagecontentelements.cpp index c79f92f..1b27681 100644 --- a/Pdf4QtLib/sources/pdfpagecontentelements.cpp +++ b/Pdf4QtLib/sources/pdfpagecontentelements.cpp @@ -2355,12 +2355,67 @@ void PDFPageContentElementTextBox::drawPage(QPainter* painter, painter->setClipRect(rect, Qt::IntersectClip); painter->translate(rect.center()); painter->scale(1.0, -1.0); - painter->rotate(getAngle()); QTextOption option; option.setAlignment(getAlignment()); + QRectF textRect(-rect.width() * 0.5, -rect.height() * 0.5, rect.width(), rect.height()); - painter->drawText(textRect, getText(), option); + if (qFuzzyIsNull(getAngle())) + { + painter->drawText(textRect, getText(), option); + } + else + { + QRectF textBoundingRect = painter->boundingRect(textRect, getText(), option); + + QMatrix matrix; + matrix.rotate(getAngle()); + QRectF mappedTextBoundingRect = matrix.mapRect(textBoundingRect); + + switch (getAlignment() & Qt::AlignHorizontal_Mask) + { + case Qt::AlignLeft: + mappedTextBoundingRect.moveLeft(textRect.left()); + break; + + case Qt::AlignHCenter: + mappedTextBoundingRect.moveLeft(textRect.left() + textRect.width() * 0.5 - mappedTextBoundingRect.width() * 0.5); + break; + + case Qt::AlignRight: + mappedTextBoundingRect.moveRight(textRect.right()); + break; + + default: + Q_ASSERT(false); + break; + } + + switch (getAlignment() & Qt::AlignVertical_Mask) + { + case Qt::AlignTop: + mappedTextBoundingRect.moveTop(textRect.top()); + break; + + case Qt::AlignVCenter: + mappedTextBoundingRect.moveTop(textRect.top() + textRect.height() * 0.5 - mappedTextBoundingRect.height() * 0.5); + break; + + case Qt::AlignBottom: + mappedTextBoundingRect.moveBottom(textRect.bottom()); + break; + + default: + Q_ASSERT(false); + break; + } + + painter->translate(mappedTextBoundingRect.center()); + painter->rotate(getAngle()); + + textBoundingRect.moveCenter(QPointF(0, 0)); + painter->drawText(textBoundingRect, getText(), option); + } } uint PDFPageContentElementTextBox::getManipulationMode(const QPointF& point, From a08d49e8d661d77fd2f7fa1d01f15cccb6e4941f Mon Sep 17 00:00:00 2001 From: Jakub Melka Date: Sun, 17 Apr 2022 18:11:07 +0200 Subject: [PATCH 27/39] Signature plugin: edit item via double click --- .../pdfpagecontenteditorstylesettings.cpp | 94 +++++++++++++++++++ .../pdfpagecontenteditorstylesettings.h | 10 +- Pdf4QtLib/sources/pdfpagecontentelements.cpp | 6 ++ Pdf4QtLib/sources/pdfpagecontentelements.h | 3 + .../SignaturePlugin/signatureplugin.cpp | 30 ++++++ .../SignaturePlugin/signatureplugin.h | 1 + 6 files changed, 143 insertions(+), 1 deletion(-) diff --git a/Pdf4QtLib/sources/pdfpagecontenteditorstylesettings.cpp b/Pdf4QtLib/sources/pdfpagecontenteditorstylesettings.cpp index 45793fd..7ec14cd 100644 --- a/Pdf4QtLib/sources/pdfpagecontenteditorstylesettings.cpp +++ b/Pdf4QtLib/sources/pdfpagecontenteditorstylesettings.cpp @@ -24,6 +24,10 @@ #include #include #include +#include +#include +#include +#include namespace pdf { @@ -252,6 +256,71 @@ void PDFPageContentEditorStyleSettings::setTextAngle(PDFReal angle, bool forceUp } } +bool PDFPageContentEditorStyleSettings::showEditElementStyleDialog(QWidget* parent, + PDFPageContentElement* element) +{ + QDialog dialog(parent); + dialog.setWindowTitle(tr("Edit Item")); + dialog.setLayout(new QVBoxLayout()); + + QTextEdit* textEdit = nullptr; + PDFPageContentStyledElement* styledElement = dynamic_cast(element); + PDFPageContentElementTextBox* textElement = dynamic_cast(element); + if (textElement) + { + QGroupBox* contentGroupBox = new QGroupBox(&dialog); + textEdit = new QTextEdit(textElement->getText(), contentGroupBox); + textEdit->setFont(textElement->getFont()); + textEdit->setAlignment(textElement->getAlignment()); + textEdit->setTextColor(textElement->getPen().color()); + contentGroupBox->setTitle(tr("Content")); + contentGroupBox->setLayout(new QVBoxLayout()); + contentGroupBox->layout()->addWidget(textEdit); + dialog.layout()->addWidget(contentGroupBox); + } + + PDFPageContentEditorStyleSettings* appearanceWidget = new PDFPageContentEditorStyleSettings(&dialog); + appearanceWidget->loadFromElement(element, true); + if (textEdit) + { + connect(appearanceWidget, &PDFPageContentEditorStyleSettings::alignmentChanged, textEdit, &QTextEdit::setAlignment); + connect(appearanceWidget, &PDFPageContentEditorStyleSettings::fontChanged, textEdit, &QTextEdit::setFont); + connect(appearanceWidget, &PDFPageContentEditorStyleSettings::penChanged, textEdit, [textEdit](const QPen& pen) { textEdit->setTextColor(pen.color()); }); + } + + QGroupBox* appearanceGroupBox = new QGroupBox(&dialog); + appearanceGroupBox->setTitle(tr("Appearance")); + appearanceGroupBox->setLayout(new QVBoxLayout()); + appearanceGroupBox->layout()->addWidget(appearanceWidget); + dialog.layout()->addWidget(appearanceGroupBox); + + QDialogButtonBox* dialogButtonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, &dialog); + connect(dialogButtonBox, &QDialogButtonBox::accepted, &dialog, &QDialog::accept); + connect(dialogButtonBox, &QDialogButtonBox::rejected, &dialog, &QDialog::reject); + dialog.layout()->addWidget(dialogButtonBox); + + if (dialog.exec() == QDialog::Accepted) + { + if (styledElement) + { + styledElement->setPen(appearanceWidget->getPen()); + styledElement->setBrush(appearanceWidget->getBrush()); + } + + if (textElement) + { + textElement->setText(textEdit->toPlainText()); + textElement->setFont(appearanceWidget->getFont()); + textElement->setAlignment(appearanceWidget->getAlignment()); + textElement->setAngle(appearanceWidget->getTextAngle()); + } + + return true; + } + + return false; +} + QIcon PDFPageContentEditorStyleSettings::getIconForColor(QColor color) const { QIcon icon; @@ -332,6 +401,31 @@ void PDFPageContentEditorStyleSettings::setBrushColor(QColor color) } } +Qt::Alignment PDFPageContentEditorStyleSettings::getAlignment() const +{ + return m_alignment; +} + +PDFReal PDFPageContentEditorStyleSettings::getTextAngle() const +{ + return ui->textAngleEdit->value(); +} + +const QFont& PDFPageContentEditorStyleSettings::getFont() const +{ + return m_font; +} + +const QBrush& PDFPageContentEditorStyleSettings::getBrush() const +{ + return m_brush; +} + +const QPen& PDFPageContentEditorStyleSettings::getPen() const +{ + return m_pen; +} + void PDFPageContentEditorStyleSettings::onSelectBrushColorButtonClicked() { QColor color = QColorDialog::getColor(m_pen.color(), this, tr("Select Color for Brush"), QColorDialog::ShowAlphaChannel); diff --git a/Pdf4QtLib/sources/pdfpagecontenteditorstylesettings.h b/Pdf4QtLib/sources/pdfpagecontenteditorstylesettings.h index 517edce..04b0a61 100644 --- a/Pdf4QtLib/sources/pdfpagecontenteditorstylesettings.h +++ b/Pdf4QtLib/sources/pdfpagecontenteditorstylesettings.h @@ -38,7 +38,7 @@ namespace pdf { class PDFPageContentElement; -class PDFPageContentEditorStyleSettings : public QWidget +class PDF4QTLIBSHARED_EXPORT PDFPageContentEditorStyleSettings : public QWidget { Q_OBJECT @@ -67,6 +67,14 @@ public: void setFontAlignment(Qt::Alignment alignment, bool forceUpdate); void setTextAngle(PDFReal angle, bool forceUpdate); + static bool showEditElementStyleDialog(QWidget* parent, PDFPageContentElement* element); + + const QPen& getPen() const; + const QBrush& getBrush() const; + const QFont& getFont() const; + Qt::Alignment getAlignment() const; + PDFReal getTextAngle() const; + signals: void penChanged(const QPen& pen); void brushChanged(const QBrush& brush); diff --git a/Pdf4QtLib/sources/pdfpagecontentelements.cpp b/Pdf4QtLib/sources/pdfpagecontentelements.cpp index 1b27681..78046ba 100644 --- a/Pdf4QtLib/sources/pdfpagecontentelements.cpp +++ b/Pdf4QtLib/sources/pdfpagecontentelements.cpp @@ -504,6 +504,12 @@ void PDFPageContentScene::mouseDoubleClickEvent(QWidget* widget, QMouseEvent* ev return; } + MouseEventInfo info = getMouseEventInfo(widget, event->pos()); + if (info.isValid()) + { + emit editElementRequest(info.hoveredElementIds); + } + // If mouse is grabbed, then event is accepted always (because // we get Press event, when we grabbed the mouse, then we will // wait for corresponding release event while all mouse move events diff --git a/Pdf4QtLib/sources/pdfpagecontentelements.h b/Pdf4QtLib/sources/pdfpagecontentelements.h index 554b7ae..35211a4 100644 --- a/Pdf4QtLib/sources/pdfpagecontentelements.h +++ b/Pdf4QtLib/sources/pdfpagecontentelements.h @@ -548,6 +548,9 @@ signals: void selectionChanged(); + /// Request to edit the elements + void editElementRequest(const std::set& elements); + private: struct MouseEventInfo diff --git a/Pdf4QtViewerPlugins/SignaturePlugin/signatureplugin.cpp b/Pdf4QtViewerPlugins/SignaturePlugin/signatureplugin.cpp index 8e50da4..8b4473a 100644 --- a/Pdf4QtViewerPlugins/SignaturePlugin/signatureplugin.cpp +++ b/Pdf4QtViewerPlugins/SignaturePlugin/signatureplugin.cpp @@ -19,6 +19,7 @@ #include "pdfdrawwidget.h" #include "pdfutils.h" #include "pdfpagecontenteditorwidget.h" +#include "pdfpagecontenteditorstylesettings.h" #include #include @@ -148,6 +149,7 @@ void SignaturePlugin::setWidget(pdf::PDFWidget* widget) m_scene.setWidget(m_widget); connect(&m_scene, &pdf::PDFPageContentScene::sceneChanged, this, &SignaturePlugin::onSceneChanged); connect(&m_scene, &pdf::PDFPageContentScene::selectionChanged, this, &SignaturePlugin::onSceneSelectionChanged); + connect(&m_scene, &pdf::PDFPageContentScene::editElementRequest, this, &SignaturePlugin::onSceneEditElement); connect(clearAction, &QAction::triggered, &m_scene, &pdf::PDFPageContentScene::clear); connect(activateAction, &QAction::triggered, this, &SignaturePlugin::setActive); @@ -238,6 +240,34 @@ void SignaturePlugin::onToolActivityChanged() } } +void SignaturePlugin::onSceneEditElement(const std::set& elements) +{ + if (elements.empty()) + { + return; + } + + pdf::PDFPageContentElement* element = nullptr; + for (pdf::PDFInteger id : elements) + { + element = m_scene.getElementById(id); + if (element) + { + break; + } + } + + if (!element) + { + return; + } + + if (pdf::PDFPageContentEditorStyleSettings::showEditElementStyleDialog(m_dataExchangeInterface->getMainWindow(), element)) + { + updateGraphics(); + } +} + void SignaturePlugin::onPenChanged(const QPen& pen) { if (pdf::PDFCreatePCElementTool* activeTool = qobject_cast(getActiveTool())) diff --git a/Pdf4QtViewerPlugins/SignaturePlugin/signatureplugin.h b/Pdf4QtViewerPlugins/SignaturePlugin/signatureplugin.h index cf25766..c0ddc14 100644 --- a/Pdf4QtViewerPlugins/SignaturePlugin/signatureplugin.h +++ b/Pdf4QtViewerPlugins/SignaturePlugin/signatureplugin.h @@ -52,6 +52,7 @@ private: void onSceneSelectionChanged(); void onWidgetSelectionChanged(); void onToolActivityChanged(); + void onSceneEditElement(const std::set& elements); void onPenChanged(const QPen& pen); void onBrushChanged(const QBrush& brush); From b2a26ada19a12fab2579013519c64be1c94df284 Mon Sep 17 00:00:00 2001 From: Jakub Melka Date: Mon, 18 Apr 2022 13:54:58 +0200 Subject: [PATCH 28/39] Signature plugin: Sign electronically --- Pdf4QtLib/sources/pdfdocumentbuilder.cpp | 95 ++++++++++++++++--- Pdf4QtLib/sources/pdfdocumentbuilder.h | 17 +++- Pdf4QtLib/sources/pdfpagecontentelements.cpp | 12 +++ Pdf4QtLib/sources/pdfpagecontentelements.h | 3 + Pdf4QtLib/sources/pdfwidgettool.cpp | 2 +- .../SignaturePlugin/signatureplugin.cpp | 33 +++++++ .../SignaturePlugin/signatureplugin.h | 1 + 7 files changed, 147 insertions(+), 16 deletions(-) diff --git a/Pdf4QtLib/sources/pdfdocumentbuilder.cpp b/Pdf4QtLib/sources/pdfdocumentbuilder.cpp index a56ebf2..3a169d8 100644 --- a/Pdf4QtLib/sources/pdfdocumentbuilder.cpp +++ b/Pdf4QtLib/sources/pdfdocumentbuilder.cpp @@ -703,9 +703,13 @@ std::array PDFDocumentBuilder::getAnnotationReductionRectangle(const return { qAbs(innerRect.left() - boundingRect.left()), qAbs(boundingRect.bottom() - innerRect.bottom()), qAbs(boundingRect.right() - innerRect.right()), qAbs(boundingRect.top() - innerRect.top()) }; } -PDFPageContentStreamBuilder::PDFPageContentStreamBuilder(PDFDocumentBuilder* builder) : +PDFPageContentStreamBuilder::PDFPageContentStreamBuilder(PDFDocumentBuilder* builder, + PDFContentStreamBuilder::CoordinateSystem coordinateSystem, + Mode mode) : m_documentBuilder(builder), - m_contentStreamBuilder(nullptr) + m_contentStreamBuilder(nullptr), + m_coordinateSystem(coordinateSystem), + m_mode(mode) { } @@ -733,7 +737,7 @@ QPainter* PDFPageContentStreamBuilder::begin(PDFObjectReference page) } m_pageReference = page; - m_contentStreamBuilder = new PDFContentStreamBuilder(mediaBox.size(), PDFContentStreamBuilder::CoordinateSystem::Qt); + m_contentStreamBuilder = new PDFContentStreamBuilder(mediaBox.size(), m_coordinateSystem); return m_contentStreamBuilder->begin(); } @@ -762,21 +766,86 @@ void PDFPageContentStreamBuilder::end(QPainter* painter) PDFObjectReference resourcesReference = copiedObjects[0].getReference(); PDFObjectReference contentsReference = copiedObjects[1].getReference(); - PDFObjectFactory pageUpdateFactory; + if (m_mode == Mode::Replace) + { + PDFObjectFactory pageUpdateFactory; - pageUpdateFactory.beginDictionary(); + pageUpdateFactory.beginDictionary(); - pageUpdateFactory.beginDictionaryItem("Contents"); - pageUpdateFactory << contentsReference; - pageUpdateFactory.endDictionaryItem(); + pageUpdateFactory.beginDictionaryItem("Contents"); + pageUpdateFactory << contentsReference; + pageUpdateFactory.endDictionaryItem(); - pageUpdateFactory.beginDictionaryItem("Resources"); - pageUpdateFactory << resourcesReference; - pageUpdateFactory.endDictionaryItem(); + pageUpdateFactory.beginDictionaryItem("Resources"); + pageUpdateFactory << resourcesReference; + pageUpdateFactory.endDictionaryItem(); - pageUpdateFactory.endDictionary(); + pageUpdateFactory.endDictionary(); - m_documentBuilder->mergeTo(m_pageReference, pageUpdateFactory.takeObject()); + m_documentBuilder->mergeTo(m_pageReference, pageUpdateFactory.takeObject()); + } + else + { + std::vector contentReferences; + PDFObject pageObject = m_documentBuilder->getObjectByReference(m_pageReference); + + if (pageObject.isDictionary()) + { + const PDFDictionary* pageDictionary = pageObject.getDictionary(); + const PDFObject& oldContents = pageDictionary->get("Contents"); + const PDFObject& oldContentsObject = m_documentBuilder->getObject(oldContents); + + if (oldContentsObject.isStream()) + { + if (oldContents.isReference()) + { + contentReferences.push_back(oldContents.getReference()); + } + } + else if (oldContentsObject.isArray()) + { + const PDFArray* contentsArray = oldContentsObject.getArray(); + for (const PDFObject& object : *contentsArray) + { + if (object.isReference()) + { + contentReferences.push_back(object.getReference()); + } + } + } + } + + switch (m_mode) + { + case Mode::PlaceBefore: + contentReferences.insert(contentReferences.begin(), contentsReference); + break; + + case Mode::PlaceAfter: + contentReferences.push_back(contentsReference); + break; + + default: + Q_ASSERT(false); + break; + } + + PDFObjectFactory pageUpdateFactory; + + pageUpdateFactory.beginDictionary(); + + pageUpdateFactory.beginDictionaryItem("Contents"); + pageUpdateFactory << contentReferences; + pageUpdateFactory.endDictionaryItem(); + + pageUpdateFactory.beginDictionaryItem("Resources"); + pageUpdateFactory << resourcesReference; + pageUpdateFactory.endDictionaryItem(); + + pageUpdateFactory.endDictionary(); + + m_documentBuilder->mergeTo(m_pageReference, pageUpdateFactory.takeObject()); + } } void PDFDocumentBuilder::updateAnnotationAppearanceStreams(PDFObjectReference annotationReference) diff --git a/Pdf4QtLib/sources/pdfdocumentbuilder.h b/Pdf4QtLib/sources/pdfdocumentbuilder.h index 845bad1..209aea0 100644 --- a/Pdf4QtLib/sources/pdfdocumentbuilder.h +++ b/Pdf4QtLib/sources/pdfdocumentbuilder.h @@ -261,9 +261,20 @@ private: class PDF4QTLIBSHARED_EXPORT PDFPageContentStreamBuilder { public: - PDFPageContentStreamBuilder(PDFDocumentBuilder* builder); - /// Starts painting onto the page. Old page content is erased. This + enum class Mode + { + Replace, + PlaceBefore, + PlaceAfter + }; + + /// Vytvoří nový builder, který vytváří obsah stránek. + PDFPageContentStreamBuilder(PDFDocumentBuilder* builder, + PDFContentStreamBuilder::CoordinateSystem coordinateSystem = PDFContentStreamBuilder::CoordinateSystem::Qt, + Mode mode = Mode::Replace); + + /// Starts painting onto the page. Old page content is erased (in Replace mode). This /// function returns painter, onto which can be graphics drawn. Painter /// uses Qt's coordinate system. Calling begin multiple times, without /// subsequent calls to end function, is invalid and can result @@ -287,6 +298,8 @@ private: PDFDocumentBuilder* m_documentBuilder; PDFContentStreamBuilder* m_contentStreamBuilder; PDFObjectReference m_pageReference; + PDFContentStreamBuilder::CoordinateSystem m_coordinateSystem; + Mode m_mode; }; class PDF4QTLIBSHARED_EXPORT PDFDocumentBuilder diff --git a/Pdf4QtLib/sources/pdfpagecontentelements.cpp b/Pdf4QtLib/sources/pdfpagecontentelements.cpp index 78046ba..3bc5219 100644 --- a/Pdf4QtLib/sources/pdfpagecontentelements.cpp +++ b/Pdf4QtLib/sources/pdfpagecontentelements.cpp @@ -868,6 +868,18 @@ std::set PDFPageContentScene::getSelectedElementIds() const return result; } +std::set PDFPageContentScene::getPageIndices() const +{ + std::set result; + + for (const auto& element : m_elements) + { + result.insert(element->getPageIndex()); + } + + return result; +} + void PDFPageContentScene::setSelectedElementIds(const std::set& selectedElementIds) { m_manipulator.selectNew(selectedElementIds); diff --git a/Pdf4QtLib/sources/pdfpagecontentelements.h b/Pdf4QtLib/sources/pdfpagecontentelements.h index 35211a4..13cb9fd 100644 --- a/Pdf4QtLib/sources/pdfpagecontentelements.h +++ b/Pdf4QtLib/sources/pdfpagecontentelements.h @@ -504,6 +504,9 @@ public: /// Returns set of selected element ids std::set getSelectedElementIds() const; + /// Returns set of involved pages + std::set getPageIndices() const; + /// Set selected items void setSelectedElementIds(const std::set& selectedElementIds); diff --git a/Pdf4QtLib/sources/pdfwidgettool.cpp b/Pdf4QtLib/sources/pdfwidgettool.cpp index 2201acb..06871b5 100644 --- a/Pdf4QtLib/sources/pdfwidgettool.cpp +++ b/Pdf4QtLib/sources/pdfwidgettool.cpp @@ -101,7 +101,7 @@ void PDFWidgetTool::setActive(bool active) setActiveImpl(active); updateActions(); - m_proxy->repaintNeeded(); + emit m_proxy->repaintNeeded(); emit toolActivityChanged(active); } } diff --git a/Pdf4QtViewerPlugins/SignaturePlugin/signatureplugin.cpp b/Pdf4QtViewerPlugins/SignaturePlugin/signatureplugin.cpp index 8b4473a..7fbc9b8 100644 --- a/Pdf4QtViewerPlugins/SignaturePlugin/signatureplugin.cpp +++ b/Pdf4QtViewerPlugins/SignaturePlugin/signatureplugin.cpp @@ -20,10 +20,12 @@ #include "pdfutils.h" #include "pdfpagecontenteditorwidget.h" #include "pdfpagecontenteditorstylesettings.h" +#include "pdfdocumentbuilder.h" #include #include #include +#include namespace pdfplugin { @@ -152,6 +154,7 @@ void SignaturePlugin::setWidget(pdf::PDFWidget* widget) connect(&m_scene, &pdf::PDFPageContentScene::editElementRequest, this, &SignaturePlugin::onSceneEditElement); connect(clearAction, &QAction::triggered, &m_scene, &pdf::PDFPageContentScene::clear); connect(activateAction, &QAction::triggered, this, &SignaturePlugin::setActive); + connect(signElectronicallyAction, &QAction::triggered, this, &SignaturePlugin::onSignElectronically); updateActions(); } @@ -268,6 +271,36 @@ void SignaturePlugin::onSceneEditElement(const std::set& elemen } } +void SignaturePlugin::onSignElectronically() +{ + Q_ASSERT(m_document); + Q_ASSERT(!m_scene.isEmpty()); + + if (QMessageBox::question(m_dataExchangeInterface->getMainWindow(), tr("Confirm Signature"), tr("Document will be signed electronically. Do you want to continue?"), QMessageBox::Yes, QMessageBox::No) == QMessageBox::Yes) + { + pdf::PDFDocumentModifier modifier(m_document); + + std::set pageIndices = m_scene.getPageIndices(); + for (pdf::PDFInteger pageIndex : pageIndices) + { + const pdf::PDFPage* page = m_document->getCatalog()->getPage(pageIndex); + pdf::PDFPageContentStreamBuilder pageContentStreamBuilder(modifier.getBuilder(), + pdf::PDFContentStreamBuilder::CoordinateSystem::PDF, + pdf::PDFPageContentStreamBuilder::Mode::PlaceAfter); + QPainter* painter = pageContentStreamBuilder.begin(page->getPageReference()); + QList errors; + pdf::PDFTextLayoutGetter nullGetter(nullptr, pageIndex); + m_scene.drawPage(painter, pageIndex, nullptr, nullGetter, QMatrix(), errors); + pageContentStreamBuilder.end(painter); + } + + if (modifier.finalize()) + { + emit m_widget->getToolManager()->documentModified(pdf::PDFModifiedDocument(modifier.getDocument(), nullptr, modifier.getFlags())); + } + } +} + void SignaturePlugin::onPenChanged(const QPen& pen) { if (pdf::PDFCreatePCElementTool* activeTool = qobject_cast(getActiveTool())) diff --git a/Pdf4QtViewerPlugins/SignaturePlugin/signatureplugin.h b/Pdf4QtViewerPlugins/SignaturePlugin/signatureplugin.h index c0ddc14..e6cf7cd 100644 --- a/Pdf4QtViewerPlugins/SignaturePlugin/signatureplugin.h +++ b/Pdf4QtViewerPlugins/SignaturePlugin/signatureplugin.h @@ -53,6 +53,7 @@ private: void onWidgetSelectionChanged(); void onToolActivityChanged(); void onSceneEditElement(const std::set& elements); + void onSignElectronically(); void onPenChanged(const QPen& pen); void onBrushChanged(const QBrush& brush); From c6cfb4cdc87a037aabcd5f441510ae82e1819474 Mon Sep 17 00:00:00 2001 From: Jakub Melka Date: Sat, 23 Apr 2022 20:16:22 +0200 Subject: [PATCH 29/39] Signature plugin: Electronic signature --- Pdf4QtLib/sources/pdfdocument.h | 10 +- Pdf4QtLib/sources/pdfdocumentbuilder.cpp | 144 ++++++++++++++++++ Pdf4QtLib/sources/pdfdocumentbuilder.h | 10 ++ Pdf4QtLib/sources/pdfdocumentwriter.cpp | 5 +- Pdf4QtLib/sources/pdfdrawspacecontroller.cpp | 4 +- Pdf4QtLib/sources/pdffont.cpp | 2 +- Pdf4QtLib/sources/pdfitemmodels.cpp | 2 +- Pdf4QtLib/sources/pdfpagecontentelements.cpp | 8 +- Pdf4QtLib/sources/pdfstreamfilters.cpp | 11 +- Pdf4QtLib/sources/pdfstreamfilters.h | 4 + Pdf4QtViewer/pdfadvancedfindwidget.cpp | 2 +- .../SignaturePlugin/signatureplugin.cpp | 2 + 12 files changed, 190 insertions(+), 14 deletions(-) diff --git a/Pdf4QtLib/sources/pdfdocument.h b/Pdf4QtLib/sources/pdfdocument.h index d476ece..3d529bb 100644 --- a/Pdf4QtLib/sources/pdfdocument.h +++ b/Pdf4QtLib/sources/pdfdocument.h @@ -501,10 +501,11 @@ public: { None = 0x0000, ///< No flag Reset = 0x0001, ///< Whole document content is changed (for example, new document is being set) - Annotation = 0x0002, ///< Annotations changed - FormField = 0x0004, ///< Form field content changed - Authorization = 0x0008, ///< Authorization has changed (for example, old document is granted user access, but for new, owner access) - XFA_Pagination = 0x0010, ///< XFA pagination has been performed (this flag can be set only when Reset flag has been set and not any other flag) + PageContents = 0x0002, ///< Page contents changed (page graphics, not annotations) + Annotation = 0x0004, ///< Annotations changed + FormField = 0x0008, ///< Form field content changed + Authorization = 0x0010, ///< Authorization has changed (for example, old document is granted user access, but for new, owner access) + XFA_Pagination = 0x0020, ///< XFA pagination has been performed (this flag can be set only when Reset flag has been set and not any other flag) }; Q_DECLARE_FLAGS(ModificationFlags, ModificationFlag) @@ -550,6 +551,7 @@ public: ModificationFlags getFlags() const { return m_flags; } bool hasReset() const { return m_flags.testFlag(Reset); } + bool hasPageContentsChanged() const { return m_flags.testFlag(PageContents); } bool hasFlag(ModificationFlag flag) const { return m_flags.testFlag(flag); } operator PDFDocument*() const { return m_document; } diff --git a/Pdf4QtLib/sources/pdfdocumentbuilder.cpp b/Pdf4QtLib/sources/pdfdocumentbuilder.cpp index 3a169d8..190a51b 100644 --- a/Pdf4QtLib/sources/pdfdocumentbuilder.cpp +++ b/Pdf4QtLib/sources/pdfdocumentbuilder.cpp @@ -22,6 +22,8 @@ #include "pdfobjectutils.h" #include "pdfnametreeloader.h" #include "pdfdbgheap.h" +#include "pdfparser.h" +#include "pdfstreamfilters.h" #include #include @@ -698,6 +700,11 @@ PDFDocument PDFDocumentBuilder::build() return PDFDocument(PDFObjectStorage(m_storage), m_version); } +QByteArray PDFDocumentBuilder::getDecodedStream(const PDFStream* stream) const +{ + return m_storage.getDecodedStream(stream); +} + std::array PDFDocumentBuilder::getAnnotationReductionRectangle(const QRectF& boundingRect, const QRectF& innerRect) const { return { qAbs(innerRect.left() - boundingRect.left()), qAbs(boundingRect.bottom() - innerRect.bottom()), qAbs(boundingRect.right() - innerRect.right()), qAbs(boundingRect.top() - innerRect.top()) }; @@ -813,6 +820,10 @@ void PDFPageContentStreamBuilder::end(QPainter* painter) } } } + + PDFObject oldResourcesObject = pageDictionary->get("Resources"); + replaceResources(contentsReference, resourcesReference, oldResourcesObject); + m_documentBuilder->mergeTo(resourcesReference, m_documentBuilder->getObject(oldResourcesObject)); } switch (m_mode) @@ -848,6 +859,139 @@ void PDFPageContentStreamBuilder::end(QPainter* painter) } } +void PDFPageContentStreamBuilder::replaceResources(PDFObjectReference contentStreamReference, + PDFObjectReference resourcesReference, + PDFObject oldResources) +{ + PDFObject newResources = m_documentBuilder->getObjectByReference(resourcesReference); + oldResources = m_documentBuilder->getObject(oldResources); + PDFObject contentStreamObject = m_documentBuilder->getObjectByReference(contentStreamReference); + + std::vector> renamings; + + if (oldResources.isDictionary() && newResources.isDictionary()) + { + PDFObjectFactory renamedResourcesDictionary; + + renamedResourcesDictionary.beginDictionary(); + + const PDFDictionary* oldResourcesDictionary = oldResources.getDictionary(); + const PDFDictionary* newResourcesDictionary = newResources.getDictionary(); + const size_t count = newResourcesDictionary->getCount(); + for (size_t i = 0; i < count; ++i) + { + const PDFInplaceOrMemoryString& key = newResourcesDictionary->getKey(i); + QByteArray keyString = key.getString(); + + // Process current resource key + renamedResourcesDictionary.beginDictionaryItem(keyString); + + if (oldResourcesDictionary->hasKey(keyString)) + { + const PDFObject& newResourcesSubdictionaryObject = m_documentBuilder->getObject(newResourcesDictionary->getValue(i)); + const PDFObject& oldResourcesSubdictionaryObject = m_documentBuilder->getObject(oldResourcesDictionary->get(keyString)); + if (oldResourcesSubdictionaryObject.isDictionary() && newResourcesSubdictionaryObject.isDictionary()) + { + // Jakub Melka: Rename items, which are in both dictionaries + const PDFDictionary* oldSd = oldResourcesSubdictionaryObject.getDictionary(); + const PDFDictionary* newSd = newResourcesSubdictionaryObject.getDictionary(); + + renamedResourcesDictionary.beginDictionary(); + + const size_t subcount = newSd->getCount(); + for (size_t j = 0; j < subcount; ++j) + { + const PDFInplaceOrMemoryString& subkey = newSd->getKey(j); + QByteArray subkeyString = subkey.getString(); + if (oldSd->hasKey(subkeyString)) + { + // Jakub Melka: we must rename the item + QByteArray newSubkeyString = subkeyString; + PDFInteger k = 0; + while (oldSd->hasKey(newSubkeyString)) + { + newSubkeyString = subkeyString + "_" + QByteArray::number(++k); + } + renamings.emplace_back(std::make_pair(subkeyString, newSubkeyString)); + subkeyString = newSubkeyString; + } + + renamedResourcesDictionary.beginDictionaryItem(subkeyString); + renamedResourcesDictionary << newSd->getValue(j); + renamedResourcesDictionary.endDictionaryItem(); + } + + renamedResourcesDictionary.endDictionary(); + } + else + { + renamedResourcesDictionary << newResourcesDictionary->getValue(i); + } + } + else + { + renamedResourcesDictionary << newResourcesDictionary->getValue(i); + } + + renamedResourcesDictionary.endDictionaryItem(); + } + + renamedResourcesDictionary.endDictionary(); + m_documentBuilder->setObject(resourcesReference, renamedResourcesDictionary.takeObject()); + } + + if (contentStreamObject.isStream()) + { + QByteArray decodedStream = m_documentBuilder->getDecodedStream(contentStreamObject.getStream()); + decodedStream = decodedStream.trimmed(); + + // Append save/restore state + if (!decodedStream.startsWith("q ")) + { + decodedStream.prepend("q "); + decodedStream.append(" Q"); + } + + // Replace all occurences in the stream + for (const auto& item : renamings) + { + QByteArray oldName = item.first; + QByteArray newName = item.second; + + oldName.prepend('/'); + newName.prepend('/'); + + int currentPos = 0; + int oldNameIndex = decodedStream.indexOf(oldName, currentPos); + while (oldNameIndex != -1) + { + const int whiteSpacePosition = oldNameIndex + oldName.size(); + if (whiteSpacePosition < decodedStream.size() && !PDFLexicalAnalyzer::isWhitespace(decodedStream[whiteSpacePosition])) + { + currentPos = oldNameIndex + 1; + oldNameIndex = decodedStream.indexOf(oldName, currentPos); + continue; + } + + decodedStream.replace(oldNameIndex, oldName.length(), newName); + currentPos = oldNameIndex + 1; + oldNameIndex = decodedStream.indexOf(oldName, currentPos); + } + } + + PDFArray array; + array.appendItem(PDFObject::createName("FlateDecode")); + + // Compress the content stream + QByteArray compressedData = PDFFlateDecodeFilter::compress(decodedStream); + PDFDictionary updatedDictionary = *contentStreamObject.getStream()->getDictionary(); + updatedDictionary.setEntry(PDFInplaceOrMemoryString("Length"), PDFObject::createInteger(compressedData.size())); + updatedDictionary.setEntry(PDFInplaceOrMemoryString("Filters"), PDFObject::createArray(std::make_shared(qMove(array)))); + PDFObject newContentStream = PDFObject::createStream(std::make_shared(qMove(updatedDictionary), qMove(compressedData))); + m_documentBuilder->setObject(contentStreamReference, std::move(newContentStream)); + } +} + void PDFDocumentBuilder::updateAnnotationAppearanceStreams(PDFObjectReference annotationReference) { PDFAnnotationPtr annotation = PDFAnnotation::parse(&m_storage, annotationReference); diff --git a/Pdf4QtLib/sources/pdfdocumentbuilder.h b/Pdf4QtLib/sources/pdfdocumentbuilder.h index 209aea0..0f1f7ea 100644 --- a/Pdf4QtLib/sources/pdfdocumentbuilder.h +++ b/Pdf4QtLib/sources/pdfdocumentbuilder.h @@ -295,6 +295,10 @@ public: void end(QPainter* painter); private: + void replaceResources(PDFObjectReference contentStreamReference, + PDFObjectReference resourcesReference, + PDFObject oldResources); + PDFDocumentBuilder* m_documentBuilder; PDFContentStreamBuilder* m_contentStreamBuilder; PDFObjectReference m_pageReference; @@ -341,6 +345,11 @@ public: /// is returned (no exception is thrown). const PDFObject& getObjectByReference(PDFObjectReference reference) const; + /// Returns the decoded stream. If stream data cannot be decoded, + /// then empty byte array is returned. + /// \param stream Stream to be decoded + QByteArray getDecodedStream(const PDFStream* stream) const; + /// Returns annotation bounding rectangle std::array getAnnotationReductionRectangle(const QRectF& boundingRect, const QRectF& innerRect) const; @@ -1526,6 +1535,7 @@ public: PDFModifiedDocument::ModificationFlags getFlags() const { return m_modificationFlags; } void markReset() { m_modificationFlags.setFlag(PDFModifiedDocument::Reset); } + void markPageContentsChanged() { m_modificationFlags.setFlag(PDFModifiedDocument::PageContents); } void markAnnotationsChanged() { m_modificationFlags.setFlag(PDFModifiedDocument::Annotation); } void markFormFieldChanged() { m_modificationFlags.setFlag(PDFModifiedDocument::FormField); } void markXFAPagination() { m_modificationFlags.setFlag(PDFModifiedDocument::XFA_Pagination); } diff --git a/Pdf4QtLib/sources/pdfdocumentwriter.cpp b/Pdf4QtLib/sources/pdfdocumentwriter.cpp index a9e67e0..df1d6fb 100644 --- a/Pdf4QtLib/sources/pdfdocumentwriter.cpp +++ b/Pdf4QtLib/sources/pdfdocumentwriter.cpp @@ -192,7 +192,10 @@ PDFOperationResult PDFDocumentWriter::write(const QString& fileName, const PDFDo PDFOperationResult result = write(&file, document); if (result) { - file.commit(); + if (!file.commit()) + { + return tr("File '%1' can't be opened for writing. %2").arg(fileName, file.errorString()); + } } else { diff --git a/Pdf4QtLib/sources/pdfdrawspacecontroller.cpp b/Pdf4QtLib/sources/pdfdrawspacecontroller.cpp index 9900839..d1bbeb4 100644 --- a/Pdf4QtLib/sources/pdfdrawspacecontroller.cpp +++ b/Pdf4QtLib/sources/pdfdrawspacecontroller.cpp @@ -488,8 +488,8 @@ void PDFDrawWidgetProxy::setDocument(const PDFModifiedDocument& document) if (getDocument() != document) { m_cacheClearTimer->stop(); - m_compiler->stop(document.hasReset()); - m_textLayoutCompiler->stop(document.hasReset()); + m_compiler->stop(document.hasReset() || document.hasPageContentsChanged()); + m_textLayoutCompiler->stop(document.hasReset() || document.hasPageContentsChanged()); m_controller->setDocument(document); if (PDFOptionalContentActivity* optionalContentActivity = document.getOptionalContentActivity()) diff --git a/Pdf4QtLib/sources/pdffont.cpp b/Pdf4QtLib/sources/pdffont.cpp index 98c15aa..d47025c 100644 --- a/Pdf4QtLib/sources/pdffont.cpp +++ b/Pdf4QtLib/sources/pdffont.cpp @@ -1836,7 +1836,7 @@ void PDFFontCache::setDocument(const PDFModifiedDocument& document) // Jakub Melka: If document has not reset flag, then fonts of the // document remains the same. So it is not needed to clear font cache. - if (document.hasReset()) + if (document.hasReset() || document.hasPageContentsChanged()) { m_fontCache.clear(); m_realizedFontCache.clear(); diff --git a/Pdf4QtLib/sources/pdfitemmodels.cpp b/Pdf4QtLib/sources/pdfitemmodels.cpp index 279373e..2a89f40 100644 --- a/Pdf4QtLib/sources/pdfitemmodels.cpp +++ b/Pdf4QtLib/sources/pdfitemmodels.cpp @@ -738,7 +738,7 @@ void PDFThumbnailsItemModel::setDocument(const PDFModifiedDocument& document) { if (m_document != document) { - if (document.hasReset() || document.hasFlag(PDFModifiedDocument::Annotation)) + if (document.hasReset() || document.hasPageContentsChanged() || document.hasFlag(PDFModifiedDocument::Annotation)) { beginResetModel(); m_thumbnailCache.clear(); diff --git a/Pdf4QtLib/sources/pdfpagecontentelements.cpp b/Pdf4QtLib/sources/pdfpagecontentelements.cpp index 3bc5219..2a183a8 100644 --- a/Pdf4QtLib/sources/pdfpagecontentelements.cpp +++ b/Pdf4QtLib/sources/pdfpagecontentelements.cpp @@ -2363,12 +2363,18 @@ void PDFPageContentElementTextBox::drawPage(QPainter* painter, } QRectF rect = getRectangle(); + QFont font = getFont(); + font.setHintingPreference(QFont::PreferNoHinting); + if (font.pointSizeF() > 0.0) + { + font.setPixelSize(qCeil(font.pointSizeF())); + } PDFPainterStateGuard guard(painter); painter->setWorldMatrix(pagePointToDevicePointMatrix, true); painter->setPen(getPen()); painter->setBrush(getBrush()); - painter->setFont(getFont()); + painter->setFont(font); painter->setRenderHint(QPainter::Antialiasing); painter->setClipRect(rect, Qt::IntersectClip); painter->translate(rect.center()); diff --git a/Pdf4QtLib/sources/pdfstreamfilters.cpp b/Pdf4QtLib/sources/pdfstreamfilters.cpp index 22d48d6..c25cc63 100644 --- a/Pdf4QtLib/sources/pdfstreamfilters.cpp +++ b/Pdf4QtLib/sources/pdfstreamfilters.cpp @@ -383,10 +383,9 @@ QByteArray PDFFlateDecodeFilter::apply(const QByteArray& data, return predictor.apply(uncompress(data)); } -QByteArray PDFFlateDecodeFilter::recompress(const QByteArray& data) +QByteArray PDFFlateDecodeFilter::compress(const QByteArray& decompressedData) { QByteArray result; - QByteArray decompressedData = uncompress(data); z_stream stream = { }; stream.next_in = const_cast(convertByteArrayToUcharPtr(decompressedData)); @@ -431,13 +430,19 @@ QByteArray PDFFlateDecodeFilter::recompress(const QByteArray& data) errorMessage = PDFTranslationContext::tr("zlib code: %1").arg(error); } - throw PDFException(PDFTranslationContext::tr("Error decompressing by flate method: %1").arg(errorMessage)); + throw PDFException(PDFTranslationContext::tr("Error compressing by flate method: %1").arg(errorMessage)); } } return result; } +QByteArray PDFFlateDecodeFilter::recompress(const QByteArray& data) +{ + QByteArray decompressedData = uncompress(data); + return compress(decompressedData); +} + PDFInteger PDFFlateDecodeFilter::getStreamDataLength(const QByteArray& data, PDFInteger offset) const { if (offset < 0 || offset >= data.size()) diff --git a/Pdf4QtLib/sources/pdfstreamfilters.h b/Pdf4QtLib/sources/pdfstreamfilters.h index 3e23bdc..e89c740 100644 --- a/Pdf4QtLib/sources/pdfstreamfilters.h +++ b/Pdf4QtLib/sources/pdfstreamfilters.h @@ -215,6 +215,10 @@ public: /// Recompresses data. So, first, data are decompressed, and then /// recompressed again with maximal compress ratio possible. + /// \param data Uncompressed data to be compressed + static QByteArray compress(const QByteArray& decompressedData); + + /// Compress data with maximal compress ratio possible. /// \param data Compressed data to be recompressed static QByteArray recompress(const QByteArray& data); diff --git a/Pdf4QtViewer/pdfadvancedfindwidget.cpp b/Pdf4QtViewer/pdfadvancedfindwidget.cpp index e5ac8a2..f2c082a 100644 --- a/Pdf4QtViewer/pdfadvancedfindwidget.cpp +++ b/Pdf4QtViewer/pdfadvancedfindwidget.cpp @@ -58,7 +58,7 @@ void PDFAdvancedFindWidget::setDocument(const pdf::PDFModifiedDocument& document // If document is not being reset, then page text should remain the same, // so, there is no need to clear the results. - if (document.hasReset()) + if (document.hasReset() || document.hasPageContentsChanged()) { m_findResults.clear(); updateUI(); diff --git a/Pdf4QtViewerPlugins/SignaturePlugin/signatureplugin.cpp b/Pdf4QtViewerPlugins/SignaturePlugin/signatureplugin.cpp index 7fbc9b8..ce15a1b 100644 --- a/Pdf4QtViewerPlugins/SignaturePlugin/signatureplugin.cpp +++ b/Pdf4QtViewerPlugins/SignaturePlugin/signatureplugin.cpp @@ -292,7 +292,9 @@ void SignaturePlugin::onSignElectronically() pdf::PDFTextLayoutGetter nullGetter(nullptr, pageIndex); m_scene.drawPage(painter, pageIndex, nullptr, nullGetter, QMatrix(), errors); pageContentStreamBuilder.end(painter); + modifier.markPageContentsChanged(); } + m_scene.clear(); if (modifier.finalize()) { From f77e7611828cc73d988c81e240ce585757231d59 Mon Sep 17 00:00:00 2001 From: Jakub Melka Date: Sun, 24 Apr 2022 14:47:39 +0200 Subject: [PATCH 30/39] Signature plugin: bugfixing --- .../sources/pdfpagecontenteditortools.cpp | 10 +++++- Pdf4QtLib/sources/pdfpagecontentelements.cpp | 31 ++++++++++++------- Pdf4QtLib/sources/pdfpagecontentelements.h | 10 ++++-- .../SignaturePlugin/signatureplugin.cpp | 2 +- 4 files changed, 38 insertions(+), 15 deletions(-) diff --git a/Pdf4QtLib/sources/pdfpagecontenteditortools.cpp b/Pdf4QtLib/sources/pdfpagecontenteditortools.cpp index 0ba7779..232ffde 100644 --- a/Pdf4QtLib/sources/pdfpagecontenteditortools.cpp +++ b/Pdf4QtLib/sources/pdfpagecontenteditortools.cpp @@ -953,7 +953,15 @@ void PDFCreatePCElementTextTool::setAlignment(Qt::Alignment alignment) void PDFCreatePCElementTextTool::setPen(const QPen& pen) { BaseClass::setPen(pen); - m_textEditWidget->setAppearance(m_element->getFont(), m_element->getAlignment(), m_element->getRectangle(), std::numeric_limits::max(), pen.color()); + + QFont font = m_element->getFont(); + font.setHintingPreference(QFont::PreferNoHinting); + if (font.pointSizeF() > 0.0) + { + font.setPixelSize(qRound(font.pointSizeF())); + } + + m_textEditWidget->setAppearance(font, m_element->getAlignment(), m_element->getRectangle(), std::numeric_limits::max(), pen.color()); emit getProxy()->repaintNeeded(); } diff --git a/Pdf4QtLib/sources/pdfpagecontentelements.cpp b/Pdf4QtLib/sources/pdfpagecontentelements.cpp index 2a183a8..41023d1 100644 --- a/Pdf4QtLib/sources/pdfpagecontentelements.cpp +++ b/Pdf4QtLib/sources/pdfpagecontentelements.cpp @@ -638,6 +638,24 @@ int PDFPageContentScene::getInputPriority() const return ToolPriority + 1; } +void PDFPageContentScene::drawElements(QPainter* painter, + PDFInteger pageIndex, + PDFTextLayoutGetter& layoutGetter, + const QMatrix& pagePointToDevicePointMatrix, + const PDFPrecompiledPage* compiledPage, + QList& errors) const +{ + for (const auto& element : m_elements) + { + if (element->getPageIndex() != pageIndex) + { + continue; + } + + element->drawPage(painter, pageIndex, compiledPage, layoutGetter, pagePointToDevicePointMatrix, errors); + } +} + void PDFPageContentScene::drawPage(QPainter* painter, PDFInteger pageIndex, const PDFPrecompiledPage* compiledPage, @@ -650,16 +668,7 @@ void PDFPageContentScene::drawPage(QPainter* painter, return; } - for (const auto& element : m_elements) - { - if (element->getPageIndex() != pageIndex) - { - continue; - } - - element->drawPage(painter, pageIndex, compiledPage, layoutGetter, pagePointToDevicePointMatrix, errors); - } - + drawElements(painter, pageIndex, layoutGetter, pagePointToDevicePointMatrix, compiledPage, errors); m_manipulator.drawPage(painter, pageIndex, compiledPage, layoutGetter, pagePointToDevicePointMatrix, errors); } @@ -2367,7 +2376,7 @@ void PDFPageContentElementTextBox::drawPage(QPainter* painter, font.setHintingPreference(QFont::PreferNoHinting); if (font.pointSizeF() > 0.0) { - font.setPixelSize(qCeil(font.pointSizeF())); + font.setPixelSize(qRound(font.pointSizeF())); } PDFPainterStateGuard guard(painter); diff --git a/Pdf4QtLib/sources/pdfpagecontentelements.h b/Pdf4QtLib/sources/pdfpagecontentelements.h index 13cb9fd..7785417 100644 --- a/Pdf4QtLib/sources/pdfpagecontentelements.h +++ b/Pdf4QtLib/sources/pdfpagecontentelements.h @@ -534,17 +534,23 @@ public: virtual const std::optional& getCursor() const override; virtual int getInputPriority() const override; - // IDocumentDrawInterface interface -public: virtual void drawPage(QPainter* painter, PDFInteger pageIndex, const PDFPrecompiledPage* compiledPage, PDFTextLayoutGetter& layoutGetter, const QMatrix& pagePointToDevicePointMatrix, QList& errors) const override; + PDFWidget* widget() const; void setWidget(PDFWidget* newWidget); + void drawElements(QPainter* painter, + PDFInteger pageIndex, + PDFTextLayoutGetter& layoutGetter, + const QMatrix& pagePointToDevicePointMatrix, + const PDFPrecompiledPage* compiledPage, + QList& errors) const; + signals: /// This signal is emitted when scene has changed (including graphics) void sceneChanged(bool graphicsOnly); diff --git a/Pdf4QtViewerPlugins/SignaturePlugin/signatureplugin.cpp b/Pdf4QtViewerPlugins/SignaturePlugin/signatureplugin.cpp index ce15a1b..9cb4f6f 100644 --- a/Pdf4QtViewerPlugins/SignaturePlugin/signatureplugin.cpp +++ b/Pdf4QtViewerPlugins/SignaturePlugin/signatureplugin.cpp @@ -290,7 +290,7 @@ void SignaturePlugin::onSignElectronically() QPainter* painter = pageContentStreamBuilder.begin(page->getPageReference()); QList errors; pdf::PDFTextLayoutGetter nullGetter(nullptr, pageIndex); - m_scene.drawPage(painter, pageIndex, nullptr, nullGetter, QMatrix(), errors); + m_scene.drawElements(painter, pageIndex, nullGetter, QMatrix(), nullptr, errors); pageContentStreamBuilder.end(painter); modifier.markPageContentsChanged(); } From 7fc3e01960a5a845ab6cad0508e0f9eaee2f43d4 Mon Sep 17 00:00:00 2001 From: Jakub Melka Date: Sat, 30 Apr 2022 18:12:14 +0200 Subject: [PATCH 31/39] Signature plugin: Create certificate --- .../SignaturePlugin/SignaturePlugin.pro | 17 ++ .../SignaturePlugin/certificatemanager.cpp | 112 ++++++++++++ .../SignaturePlugin/certificatemanager.h | 53 ++++++ .../certificatemanagerdialog.cpp | 61 +++++++ .../certificatemanagerdialog.h | 54 ++++++ .../certificatemanagerdialog.ui | 71 ++++++++ .../createcertificatedialog.cpp | 156 ++++++++++++++++ .../SignaturePlugin/createcertificatedialog.h | 59 ++++++ .../createcertificatedialog.ui | 172 ++++++++++++++++++ .../SignaturePlugin/signatureplugin.cpp | 8 + .../SignaturePlugin/signatureplugin.h | 1 + 11 files changed, 764 insertions(+) create mode 100644 Pdf4QtViewerPlugins/SignaturePlugin/certificatemanager.cpp create mode 100644 Pdf4QtViewerPlugins/SignaturePlugin/certificatemanager.h create mode 100644 Pdf4QtViewerPlugins/SignaturePlugin/certificatemanagerdialog.cpp create mode 100644 Pdf4QtViewerPlugins/SignaturePlugin/certificatemanagerdialog.h create mode 100644 Pdf4QtViewerPlugins/SignaturePlugin/certificatemanagerdialog.ui create mode 100644 Pdf4QtViewerPlugins/SignaturePlugin/createcertificatedialog.cpp create mode 100644 Pdf4QtViewerPlugins/SignaturePlugin/createcertificatedialog.h create mode 100644 Pdf4QtViewerPlugins/SignaturePlugin/createcertificatedialog.ui diff --git a/Pdf4QtViewerPlugins/SignaturePlugin/SignaturePlugin.pro b/Pdf4QtViewerPlugins/SignaturePlugin/SignaturePlugin.pro index cdf7725..9f4e2c9 100644 --- a/Pdf4QtViewerPlugins/SignaturePlugin/SignaturePlugin.pro +++ b/Pdf4QtViewerPlugins/SignaturePlugin/SignaturePlugin.pro @@ -32,10 +32,23 @@ DESTDIR = $$OUT_PWD/../../pdfplugins CONFIG += c++11 +Pdf4Qt_OPENSSL_PATH = $$absolute_path(../../Tools, $$[QT_INSTALL_PREFIX]) + +# Link OpenSSL +LIBS += -L$$Pdf4Qt_OPENSSL_PATH/OpenSSL/Win_x64/bin -L$$Pdf4Qt_OPENSSL_PATH/OpenSSL/Win_x64/lib -llibcrypto -llibssl +INCLUDEPATH += $$Pdf4Qt_OPENSSL_PATH/OpenSSL/Win_x64/include +DEPENDPATH += $$Pdf4Qt_OPENSSL_PATH/OpenSSL/Win_x64/include + SOURCES += \ + certificatemanager.cpp \ + certificatemanagerdialog.cpp \ + createcertificatedialog.cpp \ signatureplugin.cpp HEADERS += \ + certificatemanager.h \ + certificatemanagerdialog.h \ + createcertificatedialog.h \ signatureplugin.h CONFIG += force_debug_info @@ -46,3 +59,7 @@ DISTFILES += \ RESOURCES += \ icons.qrc +FORMS += \ + certificatemanagerdialog.ui \ + createcertificatedialog.ui + diff --git a/Pdf4QtViewerPlugins/SignaturePlugin/certificatemanager.cpp b/Pdf4QtViewerPlugins/SignaturePlugin/certificatemanager.cpp new file mode 100644 index 0000000..8158178 --- /dev/null +++ b/Pdf4QtViewerPlugins/SignaturePlugin/certificatemanager.cpp @@ -0,0 +1,112 @@ +// Copyright (C) 2022 Jakub Melka +// +// This file is part of PDF4QT. +// +// PDF4QT 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 +// with the written consent of the copyright owner, any later version. +// +// PDF4QT 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 PDF4QT. If not, see . + +#include "certificatemanager.h" + +#include +#include +#include +#include +#include +#include +#include + +#include + +namespace pdfplugin +{ + +CertificateManager::CertificateManager() +{ + +} + +template +using openssl_ptr = std::unique_ptr; + +void CertificateManager::createCertificate(const NewCertificateInfo& info) +{ + openssl_ptr certificateBuffer(BIO_new(BIO_s_mem()), &BIO_free_all); + openssl_ptr privateKeyBuffer(BIO_new(BIO_s_mem()), &BIO_free_all); + + if (certificateBuffer && privateKeyBuffer) + { + openssl_ptr bignumber(BN_new(), &BN_free); + openssl_ptr rsaKey(RSA_new(), &RSA_free); + + BN_set_word(bignumber.get(), RSA_F4); + const int rsaResult = RSA_generate_key_ex(rsaKey.get(), info.rsaKeyLength, bignumber.get(), nullptr); + if (rsaResult) + { + openssl_ptr certificate(X509_new(), &X509_free); + openssl_ptr privateKey(EVP_PKEY_new(), &EVP_PKEY_free); + + EVP_PKEY_set1_RSA(privateKey.get(), rsaKey.get()); + ASN1_INTEGER* serialNumber = X509_get_serialNumber(certificate.get()); + ASN1_INTEGER_set(serialNumber, info.serialNumber); + + // Set validity of the certificate + X509_gmtime_adj(X509_getm_notBefore(certificate.get()), 0); + X509_gmtime_adj(X509_getm_notBefore(certificate.get()), info.validityInSeconds); + + // Set name + X509_NAME* name = X509_get_subject_name(certificate.get()); + + auto addString = [name](const char* identifier, QString string) + { + if (string.isEmpty()) + { + return; + } + + QByteArray stringUtf8 = string.toUtf8(); + X509_NAME_add_entry_by_txt(name, identifier, MBSTRING_UTF8, reinterpret_cast(stringUtf8.constData()), stringUtf8.length(), -1, 0); + }; + addString("C", info.certCountryCode); + addString("O", info.certOrganization); + addString("OU", info.certOrganizationUnit); + addString("CN", info.certCommonName); + addString("E", info.certEmail); + + X509_EXTENSION* extension = nullptr; + X509V3_CTX context; + X509V3_set_ctx_nodb(&context); + X509V3_set_ctx(&context, certificate.get(), certificate.get(), nullptr, nullptr, 0); + extension = X509V3_EXT_conf_nid (NULL, &context, NID_key_usage, "digitalSignature, keyAgreement"); + + X509_set_issuer_name(certificate.get(), name); + + // Set public key + X509_set_pubkey(certificate.get(), privateKey.get()); + X509_sign(certificate.get(), privateKey.get(), EVP_sha512()); + + // Private key password + QByteArray privateKeyPaswordUtf8 = info.privateKeyPasword.toUtf8(); + + // Write the data + const int retWritePrivateKey = PEM_write_bio_PKCS8PrivateKey(privateKeyBuffer.get(), privateKey.get(), EVP_aes_256_cbc(), privateKeyPaswordUtf8.data(), privateKeyPaswordUtf8.size(), nullptr, nullptr); + const int retWriteCertificate = PEM_write_bio_X509(certificateBuffer.get(), certificate.get()); + + if (retWritePrivateKey == 1 && retWriteCertificate == 1) + { + + } + } + } +} + +} // namespace pdfplugin diff --git a/Pdf4QtViewerPlugins/SignaturePlugin/certificatemanager.h b/Pdf4QtViewerPlugins/SignaturePlugin/certificatemanager.h new file mode 100644 index 0000000..2aa706a --- /dev/null +++ b/Pdf4QtViewerPlugins/SignaturePlugin/certificatemanager.h @@ -0,0 +1,53 @@ +// Copyright (C) 2022 Jakub Melka +// +// This file is part of PDF4QT. +// +// PDF4QT 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 +// with the written consent of the copyright owner, any later version. +// +// PDF4QT 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 PDF4QT. If not, see . + +#ifndef CERTIFICATEMANAGER_H +#define CERTIFICATEMANAGER_H + +#include + +namespace pdfplugin +{ + +class CertificateManager +{ +public: + CertificateManager(); + + struct NewCertificateInfo + { + QString certificateFileName; + QString privateKeyFileName; + QString privateKeyPasword; + + QString certCountryCode; + QString certOrganization; + QString certOrganizationUnit; + QString certCommonName; + QString certEmail; + + int rsaKeyLength = 1024; + int validityInSeconds = 2 * 365 * 24 * 3600; + long serialNumber = 1; + }; + + void createCertificate(const NewCertificateInfo& info); +}; + +} // namespace pdfplugin + +#endif // CERTIFICATEMANAGER_H diff --git a/Pdf4QtViewerPlugins/SignaturePlugin/certificatemanagerdialog.cpp b/Pdf4QtViewerPlugins/SignaturePlugin/certificatemanagerdialog.cpp new file mode 100644 index 0000000..946f61b --- /dev/null +++ b/Pdf4QtViewerPlugins/SignaturePlugin/certificatemanagerdialog.cpp @@ -0,0 +1,61 @@ +// Copyright (C) 2022 Jakub Melka +// +// This file is part of PDF4QT. +// +// PDF4QT 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 +// with the written consent of the copyright owner, any later version. +// +// PDF4QT 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 PDF4QT. If not, see . + +#include "certificatemanagerdialog.h" +#include "ui_certificatemanagerdialog.h" +#include "createcertificatedialog.h" + +#include "pdfwidgetutils.h" + +#include +#include + +namespace pdfplugin +{ + +CertificateManagerDialog::CertificateManagerDialog(QWidget *parent) : + QDialog(parent), + ui(new Ui::CertificateManagerDialog), + m_newCertificateButton(nullptr), + m_openCertificateDirectoryButton(nullptr) +{ + ui->setupUi(this); + + m_newCertificateButton = ui->buttonBox->addButton(tr("Create Certificate"), QDialogButtonBox::ActionRole); + m_openCertificateDirectoryButton = ui->buttonBox->addButton(tr("Show Certificate Directory"), QDialogButtonBox::ActionRole); + + connect(m_newCertificateButton, &QPushButton::clicked, this, &CertificateManagerDialog::onNewCertificateClicked); + + setMinimumSize(pdf::PDFWidgetUtils::scaleDPI(this, QSize(640, 480))); +} + +CertificateManagerDialog::~CertificateManagerDialog() +{ + delete ui; +} + +void CertificateManagerDialog::onNewCertificateClicked() +{ + CreateCertificateDialog dialog(this); + if (dialog.exec() == CreateCertificateDialog::Accepted) + { + const CertificateManager::NewCertificateInfo info = dialog.getNewCertificateInfo(); + m_certificateManager.createCertificate(info); + } +} + +} // namespace pdfplugin diff --git a/Pdf4QtViewerPlugins/SignaturePlugin/certificatemanagerdialog.h b/Pdf4QtViewerPlugins/SignaturePlugin/certificatemanagerdialog.h new file mode 100644 index 0000000..ac74b45 --- /dev/null +++ b/Pdf4QtViewerPlugins/SignaturePlugin/certificatemanagerdialog.h @@ -0,0 +1,54 @@ +// Copyright (C) 2022 Jakub Melka +// +// This file is part of PDF4QT. +// +// PDF4QT 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 +// with the written consent of the copyright owner, any later version. +// +// PDF4QT 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 PDF4QT. If not, see . + +#ifndef CERTIFICATEMANAGERDIALOG_H +#define CERTIFICATEMANAGERDIALOG_H + +#include "certificatemanager.h" + +#include + +class QAction; + +namespace Ui +{ +class CertificateManagerDialog; +} + +namespace pdfplugin +{ + +class CertificateManagerDialog : public QDialog +{ + Q_OBJECT + +public: + explicit CertificateManagerDialog(QWidget* parent); + virtual ~CertificateManagerDialog() override; + +private: + void onNewCertificateClicked(); + + Ui::CertificateManagerDialog* ui; + CertificateManager m_certificateManager; + QPushButton* m_newCertificateButton; + QPushButton* m_openCertificateDirectoryButton; +}; + +} // namespace pdfplugin + +#endif // CERTIFICATEMANAGERDIALOG_H diff --git a/Pdf4QtViewerPlugins/SignaturePlugin/certificatemanagerdialog.ui b/Pdf4QtViewerPlugins/SignaturePlugin/certificatemanagerdialog.ui new file mode 100644 index 0000000..5ff7748 --- /dev/null +++ b/Pdf4QtViewerPlugins/SignaturePlugin/certificatemanagerdialog.ui @@ -0,0 +1,71 @@ + + + CertificateManagerDialog + + + + 0 + 0 + 789 + 511 + + + + Certificate Manager + + + + + + Certificates + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + + buttonBox + accepted() + CertificateManagerDialog + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + CertificateManagerDialog + reject() + + + 316 + 260 + + + 286 + 274 + + + + + diff --git a/Pdf4QtViewerPlugins/SignaturePlugin/createcertificatedialog.cpp b/Pdf4QtViewerPlugins/SignaturePlugin/createcertificatedialog.cpp new file mode 100644 index 0000000..f4bbb74 --- /dev/null +++ b/Pdf4QtViewerPlugins/SignaturePlugin/createcertificatedialog.cpp @@ -0,0 +1,156 @@ +// Copyright (C) 2022 Jakub Melka +// +// This file is part of PDF4QT. +// +// PDF4QT 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 +// with the written consent of the copyright owner, any later version. +// +// PDF4QT 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 PDF4QT. If not, see . + +#include "createcertificatedialog.h" +#include "ui_createcertificatedialog.h" + +#include +#include +#include +#include + +namespace pdfplugin +{ + +CreateCertificateDialog::CreateCertificateDialog(QWidget *parent) : + QDialog(parent), + ui(new Ui::CreateCertificateDialog) +{ + ui->setupUi(this); + + ui->keyLengthCombo->addItem(tr("1024 bits"), 1024); + ui->keyLengthCombo->addItem(tr("2048 bits"), 2048); + ui->keyLengthCombo->addItem(tr("4096 bits"), 4096); + ui->keyLengthCombo->setCurrentIndex(ui->keyLengthCombo->findData(2048)); + + QList locales = QLocale::matchingLocales(QLocale::AnyLanguage, QLocale::AnyScript, QLocale::AnyCountry); + std::sort(locales.begin(), locales.end(), [](const QLocale& left, const QLocale& right) { return QString::compare(left.nativeCountryName(), right.nativeCountryName(), Qt::CaseInsensitive) < 0; }); + + int currentIndex = 0; + QLocale currentLocale = QLocale::system(); + + for (const QLocale& locale : locales) + { + if (locale.country() == QLocale::AnyCountry) + { + continue; + } + + if (locale.nativeCountryName().isEmpty()) + { + continue; + } + + QString localeName = locale.name(); + QString countryCode = localeName.split(QChar('_')).back(); + QString text = QString("%1 | %2").arg(countryCode, locale.nativeCountryName()); + + if (ui->countryCombo->findText(text) >= 0) + { + continue; + } + + if (locale.bcp47Name() == currentLocale.bcp47Name()) + { + currentIndex = ui->countryCombo->count(); + } + + ui->countryCombo->addItem(text, countryCode); + } + + ui->countryCombo->setCurrentIndex(currentIndex); + ui->countryCombo->setMaxVisibleItems(25); + + QDate minDate = QDate::currentDate(); + ui->validTillEdit->setMinimumDate(minDate); + + QDate selectedDate = minDate; + selectedDate = selectedDate.addYears(5, ui->validTillEdit->calendar()); + ui->validTillEdit->setSelectedDate(selectedDate); +} + +CreateCertificateDialog::~CreateCertificateDialog() +{ + delete ui; +} + +void CreateCertificateDialog::accept() +{ + if (validate()) + { + bool ok = false; + QString password1 = QInputDialog::getText(this, tr("Certificate Protection"), tr("Enter password to protect your certificate."), QLineEdit::Password, QString(), &ok); + + if (!ok) + { + return; + } + + QString password2 = QInputDialog::getText(this, tr("Certificate Protection"), tr("Enter password again to verify password text."), QLineEdit::Password, QString(), &ok); + + if (password1 != password2) + { + QMessageBox::critical(this, tr("Error"), tr("Reentered password is not equal to the first one!")); + return; + } + + QDate date = ui->validTillEdit->selectedDate(); + QDate currentDate = QDate::currentDate(); + int days = currentDate.daysTo(date); + + // Fill certificate info + m_newCertificateInfo.privateKeyPasword = password1; + m_newCertificateInfo.certCountryCode = ui->countryCombo->currentData().toString(); + m_newCertificateInfo.certOrganization = ui->organizationEdit->text(); + m_newCertificateInfo.certOrganizationUnit = ui->organizationUnitEdit->text(); + m_newCertificateInfo.certCommonName = ui->commonNameEdit->text(); + m_newCertificateInfo.certEmail = ui->emailEdit->text(); + m_newCertificateInfo.rsaKeyLength = ui->keyLengthCombo->currentData().toInt(); + m_newCertificateInfo.validityInSeconds = days * 24 * 3600; + + BaseClass::accept(); + } +} + +bool CreateCertificateDialog::validate() +{ + // validate empty text fields + if (ui->commonNameEdit->text().isEmpty()) + { + QMessageBox::critical(this, tr("Error"), tr("Please enter a name!")); + ui->commonNameEdit->setFocus(); + return false; + } + + if (ui->organizationEdit->text().isEmpty()) + { + QMessageBox::critical(this, tr("Error"), tr("Please enter an organization name!")); + ui->organizationEdit->setFocus(); + return false; + } + + if (ui->emailEdit->text().isEmpty()) + { + QMessageBox::critical(this, tr("Error"), tr("Please enter an email address!")); + ui->emailEdit->setFocus(); + return false; + } + + return true; +} + +} // namespace plugin diff --git a/Pdf4QtViewerPlugins/SignaturePlugin/createcertificatedialog.h b/Pdf4QtViewerPlugins/SignaturePlugin/createcertificatedialog.h new file mode 100644 index 0000000..173323f --- /dev/null +++ b/Pdf4QtViewerPlugins/SignaturePlugin/createcertificatedialog.h @@ -0,0 +1,59 @@ +// Copyright (C) 2022 Jakub Melka +// +// This file is part of PDF4QT. +// +// PDF4QT 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 +// with the written consent of the copyright owner, any later version. +// +// PDF4QT 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 PDF4QT. If not, see . + +#ifndef CREATECERTIFICATEDIALOG_H +#define CREATECERTIFICATEDIALOG_H + +#include "certificatemanager.h" + +#include + +namespace Ui +{ +class CreateCertificateDialog; +} + +namespace pdfplugin +{ + +class CreateCertificateDialog : public QDialog +{ + Q_OBJECT + +private: + using BaseClass = QDialog; + +public: + explicit CreateCertificateDialog(QWidget* parent); + virtual ~CreateCertificateDialog() override; + + const CertificateManager::NewCertificateInfo& getNewCertificateInfo() const { return m_newCertificateInfo; } + +public slots: + virtual void accept() override; + +private: + bool validate(); + + CertificateManager::NewCertificateInfo m_newCertificateInfo; + + Ui::CreateCertificateDialog* ui; +}; + +} // namespace plugin + +#endif // CREATECERTIFICATEDIALOG_H diff --git a/Pdf4QtViewerPlugins/SignaturePlugin/createcertificatedialog.ui b/Pdf4QtViewerPlugins/SignaturePlugin/createcertificatedialog.ui new file mode 100644 index 0000000..0ea5165 --- /dev/null +++ b/Pdf4QtViewerPlugins/SignaturePlugin/createcertificatedialog.ui @@ -0,0 +1,172 @@ + + + CreateCertificateDialog + + + + 0 + 0 + 514 + 488 + + + + Create Certificate + + + + + + Create Self Signed Certificate + + + + + + Name + + + + + + + true + + + + + + + Organization + + + + + + + Organization Unit + + + + + + + Email + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + true + + + + + + + true + + + + + + + true + + + + + + + Country + + + + + + + Key length + + + + + + + Valid till + + + + + + + + + + + + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + + buttonBox + accepted() + CreateCertificateDialog + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + CreateCertificateDialog + reject() + + + 316 + 260 + + + 286 + 274 + + + + + diff --git a/Pdf4QtViewerPlugins/SignaturePlugin/signatureplugin.cpp b/Pdf4QtViewerPlugins/SignaturePlugin/signatureplugin.cpp index 9cb4f6f..d23beb2 100644 --- a/Pdf4QtViewerPlugins/SignaturePlugin/signatureplugin.cpp +++ b/Pdf4QtViewerPlugins/SignaturePlugin/signatureplugin.cpp @@ -21,6 +21,7 @@ #include "pdfpagecontenteditorwidget.h" #include "pdfpagecontenteditorstylesettings.h" #include "pdfdocumentbuilder.h" +#include "certificatemanagerdialog.h" #include #include @@ -155,6 +156,7 @@ void SignaturePlugin::setWidget(pdf::PDFWidget* widget) connect(clearAction, &QAction::triggered, &m_scene, &pdf::PDFPageContentScene::clear); connect(activateAction, &QAction::triggered, this, &SignaturePlugin::setActive); connect(signElectronicallyAction, &QAction::triggered, this, &SignaturePlugin::onSignElectronically); + connect(certificatesAction, &QAction::triggered, this, &SignaturePlugin::onOpenCertificatesManager); updateActions(); } @@ -303,6 +305,12 @@ void SignaturePlugin::onSignElectronically() } } +void SignaturePlugin::onOpenCertificatesManager() +{ + CertificateManagerDialog dialog(m_dataExchangeInterface->getMainWindow()); + dialog.exec(); +} + void SignaturePlugin::onPenChanged(const QPen& pen) { if (pdf::PDFCreatePCElementTool* activeTool = qobject_cast(getActiveTool())) diff --git a/Pdf4QtViewerPlugins/SignaturePlugin/signatureplugin.h b/Pdf4QtViewerPlugins/SignaturePlugin/signatureplugin.h index e6cf7cd..8e69b2a 100644 --- a/Pdf4QtViewerPlugins/SignaturePlugin/signatureplugin.h +++ b/Pdf4QtViewerPlugins/SignaturePlugin/signatureplugin.h @@ -54,6 +54,7 @@ private: void onToolActivityChanged(); void onSceneEditElement(const std::set& elements); void onSignElectronically(); + void onOpenCertificatesManager(); void onPenChanged(const QPen& pen); void onBrushChanged(const QBrush& brush); From 148cec5aecbf9839f8d99baafd70bd1b6820355b Mon Sep 17 00:00:00 2001 From: Jakub Melka Date: Tue, 3 May 2022 20:00:47 +0200 Subject: [PATCH 32/39] Signature plugin: write certificate --- .../SignaturePlugin/certificatemanager.cpp | 64 ++++++++++++++++--- .../SignaturePlugin/certificatemanager.h | 6 +- .../certificatemanagerdialog.cpp | 2 + .../createcertificatedialog.cpp | 3 + .../createcertificatedialog.ui | 40 +++++++----- 5 files changed, 90 insertions(+), 25 deletions(-) diff --git a/Pdf4QtViewerPlugins/SignaturePlugin/certificatemanager.cpp b/Pdf4QtViewerPlugins/SignaturePlugin/certificatemanager.cpp index 8158178..ce3c123 100644 --- a/Pdf4QtViewerPlugins/SignaturePlugin/certificatemanager.cpp +++ b/Pdf4QtViewerPlugins/SignaturePlugin/certificatemanager.cpp @@ -17,6 +17,10 @@ #include "certificatemanager.h" +#include +#include +#include + #include #include #include @@ -24,6 +28,7 @@ #include #include #include +#include #include @@ -40,10 +45,9 @@ using openssl_ptr = std::unique_ptr; void CertificateManager::createCertificate(const NewCertificateInfo& info) { - openssl_ptr certificateBuffer(BIO_new(BIO_s_mem()), &BIO_free_all); - openssl_ptr privateKeyBuffer(BIO_new(BIO_s_mem()), &BIO_free_all); + openssl_ptr pksBuffer(BIO_new(BIO_s_mem()), &BIO_free_all); - if (certificateBuffer && privateKeyBuffer) + if (pksBuffer) { openssl_ptr bignumber(BN_new(), &BN_free); openssl_ptr rsaKey(RSA_new(), &RSA_free); @@ -83,10 +87,12 @@ void CertificateManager::createCertificate(const NewCertificateInfo& info) addString("E", info.certEmail); X509_EXTENSION* extension = nullptr; - X509V3_CTX context; + X509V3_CTX context = { }; X509V3_set_ctx_nodb(&context); X509V3_set_ctx(&context, certificate.get(), certificate.get(), nullptr, nullptr, 0); extension = X509V3_EXT_conf_nid (NULL, &context, NID_key_usage, "digitalSignature, keyAgreement"); + X509_add_ext(certificate.get(), extension, -1); + X509_EXTENSION_free(extension); X509_set_issuer_name(certificate.get(), name); @@ -98,15 +104,57 @@ void CertificateManager::createCertificate(const NewCertificateInfo& info) QByteArray privateKeyPaswordUtf8 = info.privateKeyPasword.toUtf8(); // Write the data - const int retWritePrivateKey = PEM_write_bio_PKCS8PrivateKey(privateKeyBuffer.get(), privateKey.get(), EVP_aes_256_cbc(), privateKeyPaswordUtf8.data(), privateKeyPaswordUtf8.size(), nullptr, nullptr); - const int retWriteCertificate = PEM_write_bio_X509(certificateBuffer.get(), certificate.get()); + PKCS12* pkcs12 = PKCS12_create(privateKeyPaswordUtf8.constData(), + nullptr, + privateKey.get(), + certificate.get(), + nullptr, + 0, + 0, + PKCS12_DEFAULT_ITER, + PKCS12_DEFAULT_ITER, + 0); + i2d_PKCS12_bio(pksBuffer.get(), pkcs12); + PKCS12_free(pkcs12); - if (retWritePrivateKey == 1 && retWriteCertificate == 1) + BUF_MEM* pksMemoryBuffer = nullptr; + BIO_get_mem_ptr(pksBuffer.get(), &pksMemoryBuffer); + + if (!info.fileName.isEmpty()) { - + QFile file(info.fileName); + if (file.open(QFile::WriteOnly | QFile::Truncate)) + { + int datac = file.write(pksMemoryBuffer->data, pksMemoryBuffer->length); + file.close(); + } } } } } +QString CertificateManager::getCertificateDirectory() +{ + QDir directory(QStandardPaths::standardLocations(QStandardPaths::AppDataLocation).front() + "/certificates/"); + return directory.absolutePath(); +} + +QString CertificateManager::generateCertificateFileName() +{ + QString directoryString = getCertificateDirectory(); + QDir directory(directoryString); + + int certificateIndex = 1; + while (true) + { + QString fileName = directory.absoluteFilePath(QString("cert_%1.pfx").arg(certificateIndex++)); + if (!QFile::exists(fileName)) + { + return fileName; + } + } + + return QString(); +} + } // namespace pdfplugin diff --git a/Pdf4QtViewerPlugins/SignaturePlugin/certificatemanager.h b/Pdf4QtViewerPlugins/SignaturePlugin/certificatemanager.h index 2aa706a..47b8a92 100644 --- a/Pdf4QtViewerPlugins/SignaturePlugin/certificatemanager.h +++ b/Pdf4QtViewerPlugins/SignaturePlugin/certificatemanager.h @@ -30,8 +30,7 @@ public: struct NewCertificateInfo { - QString certificateFileName; - QString privateKeyFileName; + QString fileName; QString privateKeyPasword; QString certCountryCode; @@ -46,6 +45,9 @@ public: }; void createCertificate(const NewCertificateInfo& info); + + static QString getCertificateDirectory(); + static QString generateCertificateFileName(); }; } // namespace pdfplugin diff --git a/Pdf4QtViewerPlugins/SignaturePlugin/certificatemanagerdialog.cpp b/Pdf4QtViewerPlugins/SignaturePlugin/certificatemanagerdialog.cpp index 946f61b..afafd5a 100644 --- a/Pdf4QtViewerPlugins/SignaturePlugin/certificatemanagerdialog.cpp +++ b/Pdf4QtViewerPlugins/SignaturePlugin/certificatemanagerdialog.cpp @@ -53,6 +53,8 @@ void CertificateManagerDialog::onNewCertificateClicked() CreateCertificateDialog dialog(this); if (dialog.exec() == CreateCertificateDialog::Accepted) { + QDir::root().mkpath(CertificateManager::getCertificateDirectory()); + const CertificateManager::NewCertificateInfo info = dialog.getNewCertificateInfo(); m_certificateManager.createCertificate(info); } diff --git a/Pdf4QtViewerPlugins/SignaturePlugin/createcertificatedialog.cpp b/Pdf4QtViewerPlugins/SignaturePlugin/createcertificatedialog.cpp index f4bbb74..42e83c7 100644 --- a/Pdf4QtViewerPlugins/SignaturePlugin/createcertificatedialog.cpp +++ b/Pdf4QtViewerPlugins/SignaturePlugin/createcertificatedialog.cpp @@ -18,6 +18,8 @@ #include "createcertificatedialog.h" #include "ui_createcertificatedialog.h" +#include "certificatemanager.h" + #include #include #include @@ -113,6 +115,7 @@ void CreateCertificateDialog::accept() int days = currentDate.daysTo(date); // Fill certificate info + m_newCertificateInfo.fileName = CertificateManager::generateCertificateFileName(); m_newCertificateInfo.privateKeyPasword = password1; m_newCertificateInfo.certCountryCode = ui->countryCombo->currentData().toString(); m_newCertificateInfo.certOrganization = ui->organizationEdit->text(); diff --git a/Pdf4QtViewerPlugins/SignaturePlugin/createcertificatedialog.ui b/Pdf4QtViewerPlugins/SignaturePlugin/createcertificatedialog.ui index 0ea5165..6ec52d9 100644 --- a/Pdf4QtViewerPlugins/SignaturePlugin/createcertificatedialog.ui +++ b/Pdf4QtViewerPlugins/SignaturePlugin/createcertificatedialog.ui @@ -20,42 +20,42 @@ Create Self Signed Certificate - + Name - + true - + Organization - + Organization Unit - + Email - + Qt::Vertical @@ -68,57 +68,67 @@ - + true - + true - + true - + Country - + Key length - + Valid till - + - + - + + + + + + + + Certificate file + + +
From 27bed729b50e19caa250d84cd0262c7cc448316a Mon Sep 17 00:00:00 2001 From: Jakub Melka Date: Sat, 7 May 2022 17:36:19 +0200 Subject: [PATCH 33/39] Signature plugin: Certificate management --- .../SignaturePlugin/certificatemanager.cpp | 27 +++---- .../certificatemanagerdialog.cpp | 80 +++++++++++++++++-- .../certificatemanagerdialog.h | 7 ++ .../certificatemanagerdialog.ui | 7 +- .../createcertificatedialog.cpp | 5 +- 5 files changed, 105 insertions(+), 21 deletions(-) diff --git a/Pdf4QtViewerPlugins/SignaturePlugin/certificatemanager.cpp b/Pdf4QtViewerPlugins/SignaturePlugin/certificatemanager.cpp index ce3c123..f18f9ed 100644 --- a/Pdf4QtViewerPlugins/SignaturePlugin/certificatemanager.cpp +++ b/Pdf4QtViewerPlugins/SignaturePlugin/certificatemanager.cpp @@ -65,7 +65,7 @@ void CertificateManager::createCertificate(const NewCertificateInfo& info) // Set validity of the certificate X509_gmtime_adj(X509_getm_notBefore(certificate.get()), 0); - X509_gmtime_adj(X509_getm_notBefore(certificate.get()), info.validityInSeconds); + X509_gmtime_adj(X509_getm_notAfter(certificate.get()), info.validityInSeconds); // Set name X509_NAME* name = X509_get_subject_name(certificate.get()); @@ -104,18 +104,17 @@ void CertificateManager::createCertificate(const NewCertificateInfo& info) QByteArray privateKeyPaswordUtf8 = info.privateKeyPasword.toUtf8(); // Write the data - PKCS12* pkcs12 = PKCS12_create(privateKeyPaswordUtf8.constData(), - nullptr, - privateKey.get(), - certificate.get(), - nullptr, - 0, - 0, - PKCS12_DEFAULT_ITER, - PKCS12_DEFAULT_ITER, - 0); - i2d_PKCS12_bio(pksBuffer.get(), pkcs12); - PKCS12_free(pkcs12); + openssl_ptr pkcs12(PKCS12_create(privateKeyPaswordUtf8.constData(), + nullptr, + privateKey.get(), + certificate.get(), + nullptr, + 0, + 0, + PKCS12_DEFAULT_ITER, + PKCS12_DEFAULT_ITER, + 0), &PKCS12_free); + i2d_PKCS12_bio(pksBuffer.get(), pkcs12.get()); BUF_MEM* pksMemoryBuffer = nullptr; BIO_get_mem_ptr(pksBuffer.get(), &pksMemoryBuffer); @@ -125,7 +124,7 @@ void CertificateManager::createCertificate(const NewCertificateInfo& info) QFile file(info.fileName); if (file.open(QFile::WriteOnly | QFile::Truncate)) { - int datac = file.write(pksMemoryBuffer->data, pksMemoryBuffer->length); + file.write(pksMemoryBuffer->data, pksMemoryBuffer->length); file.close(); } } diff --git a/Pdf4QtViewerPlugins/SignaturePlugin/certificatemanagerdialog.cpp b/Pdf4QtViewerPlugins/SignaturePlugin/certificatemanagerdialog.cpp index afafd5a..a8c1954 100644 --- a/Pdf4QtViewerPlugins/SignaturePlugin/certificatemanagerdialog.cpp +++ b/Pdf4QtViewerPlugins/SignaturePlugin/certificatemanagerdialog.cpp @@ -23,6 +23,10 @@ #include #include +#include +#include +#include +#include namespace pdfplugin { @@ -31,14 +35,29 @@ CertificateManagerDialog::CertificateManagerDialog(QWidget *parent) : QDialog(parent), ui(new Ui::CertificateManagerDialog), m_newCertificateButton(nullptr), - m_openCertificateDirectoryButton(nullptr) + m_openCertificateDirectoryButton(nullptr), + m_deleteCertificateButton(nullptr), + m_importCertificateButton(nullptr), + m_certificateFileModel(nullptr) { ui->setupUi(this); - m_newCertificateButton = ui->buttonBox->addButton(tr("Create Certificate"), QDialogButtonBox::ActionRole); - m_openCertificateDirectoryButton = ui->buttonBox->addButton(tr("Show Certificate Directory"), QDialogButtonBox::ActionRole); + QDir::root().mkpath(CertificateManager::getCertificateDirectory()); + + m_certificateFileModel = new QFileSystemModel(this); + QModelIndex rootIndex = m_certificateFileModel->setRootPath(CertificateManager::getCertificateDirectory()); + ui->fileView->setModel(m_certificateFileModel); + ui->fileView->setRootIndex(rootIndex); + + m_newCertificateButton = ui->buttonBox->addButton(tr("Create"), QDialogButtonBox::ActionRole); + m_openCertificateDirectoryButton = ui->buttonBox->addButton(tr("Open Directory"), QDialogButtonBox::ActionRole); + m_deleteCertificateButton = ui->buttonBox->addButton(tr("Delete"), QDialogButtonBox::ActionRole); + m_importCertificateButton = ui->buttonBox->addButton(tr("Import"), QDialogButtonBox::ActionRole); connect(m_newCertificateButton, &QPushButton::clicked, this, &CertificateManagerDialog::onNewCertificateClicked); + connect(m_openCertificateDirectoryButton, &QPushButton::clicked, this, &CertificateManagerDialog::onOpenCertificateDirectoryClicked); + connect(m_deleteCertificateButton, &QPushButton::clicked, this, &CertificateManagerDialog::onDeleteCertificateClicked); + connect(m_importCertificateButton, &QPushButton::clicked, this, &CertificateManagerDialog::onImportCertificateClicked); setMinimumSize(pdf::PDFWidgetUtils::scaleDPI(this, QSize(640, 480))); } @@ -53,11 +72,62 @@ void CertificateManagerDialog::onNewCertificateClicked() CreateCertificateDialog dialog(this); if (dialog.exec() == CreateCertificateDialog::Accepted) { - QDir::root().mkpath(CertificateManager::getCertificateDirectory()); - const CertificateManager::NewCertificateInfo info = dialog.getNewCertificateInfo(); m_certificateManager.createCertificate(info); } } +void CertificateManagerDialog::onOpenCertificateDirectoryClicked() +{ + QDesktopServices::openUrl(QString("file:///%1").arg(CertificateManager::getCertificateDirectory(), QUrl::TolerantMode)); +} + +void CertificateManagerDialog::onDeleteCertificateClicked() +{ + QFileInfo fileInfo = m_certificateFileModel->fileInfo(ui->fileView->currentIndex()); + if (fileInfo.exists()) + { + if (QMessageBox::question(this, tr("Confirm delete"), tr("Do you want to delete certificate '%1'?").arg(fileInfo.fileName()), QMessageBox::No, QMessageBox::Yes) == QMessageBox::Yes) + { + QFile file(fileInfo.filePath()); + if (!file.remove()) + { + QMessageBox::critical(this, tr("Error"), tr("Cannot delete certificate '%1'").arg(fileInfo.fileName())); + } + } + } +} + +void CertificateManagerDialog::onImportCertificateClicked() +{ + QString selectedFile = QFileDialog::getOpenFileName(this, tr("Import Certificate"), QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation), tr("Certificate file (*.pfx);;All files (*.*)")); + + if (selectedFile.isEmpty()) + { + return; + } + + QFile file(selectedFile); + if (file.exists()) + { + QString path = CertificateManager::getCertificateDirectory(); + QString targetFile = QString("%1/%2").arg(path, QFileInfo(file).fileName()); + if (QFile::exists(targetFile)) + { + QMessageBox::critical(this, tr("Error"), tr("Target file exists. Please rename the certificate file to import.")); + } + else + { + if (file.copy(targetFile)) + { + QMessageBox::information(this, tr("Import Certificate"), tr("Certificate '%1' was successfully imported.").arg(file.fileName())); + } + else + { + QMessageBox::critical(this, tr("Import Certificate"), tr("Error occured during certificate '%1' import.").arg(file.fileName())); + } + } + } +} + } // namespace pdfplugin diff --git a/Pdf4QtViewerPlugins/SignaturePlugin/certificatemanagerdialog.h b/Pdf4QtViewerPlugins/SignaturePlugin/certificatemanagerdialog.h index ac74b45..1fcdfb5 100644 --- a/Pdf4QtViewerPlugins/SignaturePlugin/certificatemanagerdialog.h +++ b/Pdf4QtViewerPlugins/SignaturePlugin/certificatemanagerdialog.h @@ -23,6 +23,7 @@ #include class QAction; +class QFileSystemModel; namespace Ui { @@ -42,11 +43,17 @@ public: private: void onNewCertificateClicked(); + void onOpenCertificateDirectoryClicked(); + void onDeleteCertificateClicked(); + void onImportCertificateClicked(); Ui::CertificateManagerDialog* ui; CertificateManager m_certificateManager; QPushButton* m_newCertificateButton; QPushButton* m_openCertificateDirectoryButton; + QPushButton* m_deleteCertificateButton; + QPushButton* m_importCertificateButton; + QFileSystemModel* m_certificateFileModel; }; } // namespace pdfplugin diff --git a/Pdf4QtViewerPlugins/SignaturePlugin/certificatemanagerdialog.ui b/Pdf4QtViewerPlugins/SignaturePlugin/certificatemanagerdialog.ui index 5ff7748..8fb0941 100644 --- a/Pdf4QtViewerPlugins/SignaturePlugin/certificatemanagerdialog.ui +++ b/Pdf4QtViewerPlugins/SignaturePlugin/certificatemanagerdialog.ui @@ -19,6 +19,11 @@ Certificates + + + + + @@ -27,7 +32,7 @@ Qt::Horizontal - QDialogButtonBox::Cancel|QDialogButtonBox::Ok + QDialogButtonBox::Close diff --git a/Pdf4QtViewerPlugins/SignaturePlugin/createcertificatedialog.cpp b/Pdf4QtViewerPlugins/SignaturePlugin/createcertificatedialog.cpp index 42e83c7..4015502 100644 --- a/Pdf4QtViewerPlugins/SignaturePlugin/createcertificatedialog.cpp +++ b/Pdf4QtViewerPlugins/SignaturePlugin/createcertificatedialog.cpp @@ -34,6 +34,9 @@ CreateCertificateDialog::CreateCertificateDialog(QWidget *parent) : { ui->setupUi(this); + ui->fileNameEdit->setReadOnly(true); + ui->fileNameEdit->setText(CertificateManager::generateCertificateFileName()); + ui->keyLengthCombo->addItem(tr("1024 bits"), 1024); ui->keyLengthCombo->addItem(tr("2048 bits"), 2048); ui->keyLengthCombo->addItem(tr("4096 bits"), 4096); @@ -115,7 +118,7 @@ void CreateCertificateDialog::accept() int days = currentDate.daysTo(date); // Fill certificate info - m_newCertificateInfo.fileName = CertificateManager::generateCertificateFileName(); + m_newCertificateInfo.fileName = ui->fileNameEdit->text(); m_newCertificateInfo.privateKeyPasword = password1; m_newCertificateInfo.certCountryCode = ui->countryCombo->currentData().toString(); m_newCertificateInfo.certOrganization = ui->organizationEdit->text(); From e492b53a7b156003475c95272fdc01090760da04 Mon Sep 17 00:00:00 2001 From: Jakub Melka Date: Sun, 8 May 2022 16:30:03 +0200 Subject: [PATCH 34/39] Signature plugin: fix unable to get CRL error --- Pdf4QtLib/sources/pdfsignaturehandler.cpp | 18 ++++++++++++++++++ Pdf4QtLib/sources/pdfsignaturehandler.h | 4 +++- 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/Pdf4QtLib/sources/pdfsignaturehandler.cpp b/Pdf4QtLib/sources/pdfsignaturehandler.cpp index 154eb13..2464c06 100644 --- a/Pdf4QtLib/sources/pdfsignaturehandler.cpp +++ b/Pdf4QtLib/sources/pdfsignaturehandler.cpp @@ -348,6 +348,15 @@ void PDFSignatureVerificationResult::addCertificateQualifiedStatementNotVerified } } +void PDFSignatureVerificationResult::addCertificateUnableToGetCRLWarning() +{ + if (!m_flags.testFlag(Warning_Certificate_UnableToGetCRL)) + { + m_flags.setFlag(Warning_Certificate_UnableToGetCRL); + m_warnings << PDFTranslationContext::tr("Unable to get CRL."); + } +} + void PDFSignatureVerificationResult::setSignatureFieldQualifiedName(const QString& signatureFieldQualifiedName) { m_signatureFieldQualifiedName = signatureFieldQualifiedName; @@ -977,6 +986,15 @@ int PDFSignatureHandler_ETSI_base::verifyCallback(int ok, X509_STORE_CTX* contex return 1; } + case X509_V_ERR_UNABLE_TO_GET_CRL: + { + // We will treat this as only warning. It means that + // CRL cannot be downloaded or other error occured. + s_ETSI_currentResult->addCertificateUnableToGetCRLWarning(); + X509_STORE_CTX_set_error(context, X509_V_OK); + return 1; + } + case X509_V_ERR_UNHANDLED_CRITICAL_EXTENSION: { // We must handle all critical extensions manually diff --git a/Pdf4QtLib/sources/pdfsignaturehandler.h b/Pdf4QtLib/sources/pdfsignaturehandler.h index f6db0e5..d93044e 100644 --- a/Pdf4QtLib/sources/pdfsignaturehandler.h +++ b/Pdf4QtLib/sources/pdfsignaturehandler.h @@ -319,6 +319,7 @@ public: Warning_Signature_NotCoveredBytes = 0x00200000, ///< Some bytes in source data are not covered by signature Warning_Certificate_CRLValidityTimeExpired = 0x00400000, ///< Certificate revocation list was not checked, because it's validity expired Warning_Certificate_QualifiedStatement = 0x00800000, ///< Qualified certificate statement not verified + Warning_Certificate_UnableToGetCRL = 0x01000000, ///< Unable to get CRL Error_Certificates_Mask = Error_Certificate_Invalid | Error_Certificate_NoSignatures | Error_Certificate_Missing | Error_Certificate_Generic | Error_Certificate_Expired | Error_Certificate_SelfSigned | Error_Certificate_SelfSignedChain | Error_Certificate_TrustedNotFound | @@ -327,7 +328,7 @@ public: Error_Signatures_Mask = Error_Signature_Invalid | Error_Signature_SourceCertificateMissing | Error_Signature_NoSignaturesFound | Error_Signature_DigestFailure | Error_Signature_DataOther | Error_Signature_DataCoveredBySignatureMissing, - Warning_Certificates_Mask = Warning_Certificate_CRLValidityTimeExpired | Warning_Certificate_QualifiedStatement, + Warning_Certificates_Mask = Warning_Certificate_CRLValidityTimeExpired | Warning_Certificate_QualifiedStatement | Warning_Certificate_UnableToGetCRL, Warning_Signatures_Mask = Warning_Signature_NotCoveredBytes, Warnings_Mask = Warning_Certificates_Mask | Warning_Signatures_Mask @@ -361,6 +362,7 @@ public: void addSignatureNotCoveredBytesWarning(PDFInteger count); void addCertificateCRLValidityTimeExpiredWarning(); void addCertificateQualifiedStatementNotVerifiedWarning(); + void addCertificateUnableToGetCRLWarning(); bool isValid() const { return hasFlag(OK); } bool isCertificateValid() const { return hasFlag(Certificate_OK); } From 4d8d916c3fa6b280a0cef6f1c4288a96cd7e8df6 Mon Sep 17 00:00:00 2001 From: Jakub Melka Date: Sat, 14 May 2022 17:38:28 +0200 Subject: [PATCH 35/39] Signature plugin: sign dialog + verification --- .../SignaturePlugin/SignaturePlugin.pro | 9 +- .../SignaturePlugin/certificatemanager.cpp | 34 ++++ .../SignaturePlugin/certificatemanager.h | 3 + .../SignaturePlugin/signatureplugin.cpp | 26 ++- .../SignaturePlugin/signatureplugin.h | 1 + .../SignaturePlugin/signdialog.cpp | 90 ++++++++++ .../SignaturePlugin/signdialog.h | 56 ++++++ .../SignaturePlugin/signdialog.ui | 160 ++++++++++++++++++ 8 files changed, 375 insertions(+), 4 deletions(-) create mode 100644 Pdf4QtViewerPlugins/SignaturePlugin/signdialog.cpp create mode 100644 Pdf4QtViewerPlugins/SignaturePlugin/signdialog.h create mode 100644 Pdf4QtViewerPlugins/SignaturePlugin/signdialog.ui diff --git a/Pdf4QtViewerPlugins/SignaturePlugin/SignaturePlugin.pro b/Pdf4QtViewerPlugins/SignaturePlugin/SignaturePlugin.pro index 9f4e2c9..c08e5d6 100644 --- a/Pdf4QtViewerPlugins/SignaturePlugin/SignaturePlugin.pro +++ b/Pdf4QtViewerPlugins/SignaturePlugin/SignaturePlugin.pro @@ -43,13 +43,15 @@ SOURCES += \ certificatemanager.cpp \ certificatemanagerdialog.cpp \ createcertificatedialog.cpp \ - signatureplugin.cpp + signatureplugin.cpp \ + signdialog.cpp HEADERS += \ certificatemanager.h \ certificatemanagerdialog.h \ createcertificatedialog.h \ - signatureplugin.h + signatureplugin.h \ + signdialog.h CONFIG += force_debug_info @@ -61,5 +63,6 @@ RESOURCES += \ FORMS += \ certificatemanagerdialog.ui \ - createcertificatedialog.ui + createcertificatedialog.ui \ + signdialog.ui diff --git a/Pdf4QtViewerPlugins/SignaturePlugin/certificatemanager.cpp b/Pdf4QtViewerPlugins/SignaturePlugin/certificatemanager.cpp index f18f9ed..6854c3a 100644 --- a/Pdf4QtViewerPlugins/SignaturePlugin/certificatemanager.cpp +++ b/Pdf4QtViewerPlugins/SignaturePlugin/certificatemanager.cpp @@ -132,6 +132,12 @@ void CertificateManager::createCertificate(const NewCertificateInfo& info) } } +QFileInfoList CertificateManager::getCertificates() +{ + QDir directory(getCertificateDirectory()); + return directory.entryInfoList(QStringList() << "*.pfx", QDir::Files | QDir::NoDotAndDotDot | QDir::Readable, QDir::Name); +} + QString CertificateManager::getCertificateDirectory() { QDir directory(QStandardPaths::standardLocations(QStandardPaths::AppDataLocation).front() + "/certificates/"); @@ -156,4 +162,32 @@ QString CertificateManager::generateCertificateFileName() return QString(); } +bool CertificateManager::isCertificateValid(QString fileName, QString password) +{ + QFile file(fileName); + if (file.open(QFile::ReadOnly)) + { + QByteArray data = file.readAll(); + file.close(); + + openssl_ptr pksBuffer(BIO_new(BIO_s_mem()), &BIO_free_all); + BIO_write(pksBuffer.get(), data.constData(), data.length()); + + openssl_ptr pkcs12(d2i_PKCS12_bio(pksBuffer.get(), nullptr), &PKCS12_free); + if (pkcs12) + { + const char* passwordPointer = nullptr; + QByteArray passwordByteArray = password.isEmpty() ? QByteArray() : password.toUtf8(); + if (!passwordByteArray.isEmpty()) + { + passwordPointer = passwordByteArray.constData(); + } + + return PKCS12_parse(pkcs12.get(), passwordPointer, nullptr, nullptr, nullptr) == 1; + } + } + + return false; +} + } // namespace pdfplugin diff --git a/Pdf4QtViewerPlugins/SignaturePlugin/certificatemanager.h b/Pdf4QtViewerPlugins/SignaturePlugin/certificatemanager.h index 47b8a92..7a52f4e 100644 --- a/Pdf4QtViewerPlugins/SignaturePlugin/certificatemanager.h +++ b/Pdf4QtViewerPlugins/SignaturePlugin/certificatemanager.h @@ -19,6 +19,7 @@ #define CERTIFICATEMANAGER_H #include +#include namespace pdfplugin { @@ -46,8 +47,10 @@ public: void createCertificate(const NewCertificateInfo& info); + static QFileInfoList getCertificates(); static QString getCertificateDirectory(); static QString generateCertificateFileName(); + static bool isCertificateValid(QString fileName, QString password); }; } // namespace pdfplugin diff --git a/Pdf4QtViewerPlugins/SignaturePlugin/signatureplugin.cpp b/Pdf4QtViewerPlugins/SignaturePlugin/signatureplugin.cpp index d23beb2..6b9ae9c 100644 --- a/Pdf4QtViewerPlugins/SignaturePlugin/signatureplugin.cpp +++ b/Pdf4QtViewerPlugins/SignaturePlugin/signatureplugin.cpp @@ -22,6 +22,7 @@ #include "pdfpagecontenteditorstylesettings.h" #include "pdfdocumentbuilder.h" #include "certificatemanagerdialog.h" +#include "signdialog.h" #include #include @@ -156,6 +157,7 @@ void SignaturePlugin::setWidget(pdf::PDFWidget* widget) connect(clearAction, &QAction::triggered, &m_scene, &pdf::PDFPageContentScene::clear); connect(activateAction, &QAction::triggered, this, &SignaturePlugin::setActive); connect(signElectronicallyAction, &QAction::triggered, this, &SignaturePlugin::onSignElectronically); + connect(signDigitallyAction, &QAction::triggered, this, &SignaturePlugin::onSignDigitally); connect(certificatesAction, &QAction::triggered, this, &SignaturePlugin::onOpenCertificatesManager); updateActions(); @@ -305,6 +307,28 @@ void SignaturePlugin::onSignElectronically() } } +void SignaturePlugin::onSignDigitally() +{ + // Jakub Melka: do we have certificates? If not, + // open certificate dialog, so the user can create + // a new one. + if (CertificateManager::getCertificates().isEmpty()) + { + onOpenCertificatesManager(); + + if (CertificateManager::getCertificates().isEmpty()) + { + return; + } + } + + SignDialog dialog(m_dataExchangeInterface->getMainWindow(), m_scene.isEmpty()); + if (dialog.exec() == SignDialog::Accepted) + { + + } +} + void SignaturePlugin::onOpenCertificatesManager() { CertificateManagerDialog dialog(m_dataExchangeInterface->getMainWindow()); @@ -422,7 +446,7 @@ void SignaturePlugin::updateActions() QAction* signElectronicallyAction = m_actions[SignElectronically]; signElectronicallyAction->setEnabled(isSceneNonempty); QAction* signDigitallyAction = m_actions[SignDigitally]; - signDigitallyAction->setEnabled(isSceneNonempty); + signDigitallyAction->setEnabled(m_document); } void SignaturePlugin::updateGraphics() diff --git a/Pdf4QtViewerPlugins/SignaturePlugin/signatureplugin.h b/Pdf4QtViewerPlugins/SignaturePlugin/signatureplugin.h index 8e69b2a..db34c8a 100644 --- a/Pdf4QtViewerPlugins/SignaturePlugin/signatureplugin.h +++ b/Pdf4QtViewerPlugins/SignaturePlugin/signatureplugin.h @@ -54,6 +54,7 @@ private: void onToolActivityChanged(); void onSceneEditElement(const std::set& elements); void onSignElectronically(); + void onSignDigitally(); void onOpenCertificatesManager(); void onPenChanged(const QPen& pen); diff --git a/Pdf4QtViewerPlugins/SignaturePlugin/signdialog.cpp b/Pdf4QtViewerPlugins/SignaturePlugin/signdialog.cpp new file mode 100644 index 0000000..eebadd4 --- /dev/null +++ b/Pdf4QtViewerPlugins/SignaturePlugin/signdialog.cpp @@ -0,0 +1,90 @@ +// Copyright (C) 2022 Jakub Melka +// +// This file is part of PDF4QT. +// +// PDF4QT 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 +// with the written consent of the copyright owner, any later version. +// +// PDF4QT 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 PDF4QT. If not, see . + +#include "signdialog.h" +#include "ui_signdialog.h" + +#include "certificatemanager.h" + +#include + +#include + +namespace pdfplugin +{ + +SignDialog::SignDialog(QWidget* parent, bool isSceneEmpty) : + QDialog(parent), + ui(new Ui::SignDialog) +{ + ui->setupUi(this); + + if (!isSceneEmpty) + { + ui->methodCombo->addItem(tr("Sign digitally"), SignDigitally); + } + + ui->methodCombo->addItem(tr("Sign digitally (invisible signature)"), SignDigitallyInvisible); + ui->methodCombo->setCurrentIndex(0); + + QFileInfoList certificates = CertificateManager::getCertificates(); + for (const QFileInfo& certificateFileInfo : certificates) + { + ui->certificateCombo->addItem(certificateFileInfo.fileName(), certificateFileInfo.absoluteFilePath()); + } +} + +SignDialog::~SignDialog() +{ + delete ui; +} + +SignDialog::SignMethod SignDialog::getSignMethod() const +{ + return static_cast(ui->methodCombo->currentData().toInt()); +} + +QString SignDialog::getCertificatePath() const +{ + return ui->certificateCombo->currentData().toString(); +} + +void SignDialog::accept() +{ + // Check certificate + if (!QFile::exists(getCertificatePath())) + { + QMessageBox::critical(this, tr("Error"), tr("Certificate does not exist.")); + ui->certificateCombo->setFocus(); + return; + } + + // Check we can access the certificate + if (!CertificateManager::isCertificateValid(getCertificatePath(), ui->certificatePasswordEdit->text())) + { + QMessageBox::critical(this, tr("Error"), tr("Password to open certificate is invalid.")); + ui->certificatePasswordEdit->setFocus(); + return; + } + + QDialog::accept(); +} + +} // namespace pdfplugin + + + diff --git a/Pdf4QtViewerPlugins/SignaturePlugin/signdialog.h b/Pdf4QtViewerPlugins/SignaturePlugin/signdialog.h new file mode 100644 index 0000000..aa727da --- /dev/null +++ b/Pdf4QtViewerPlugins/SignaturePlugin/signdialog.h @@ -0,0 +1,56 @@ +// Copyright (C) 2022 Jakub Melka +// +// This file is part of PDF4QT. +// +// PDF4QT 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 +// with the written consent of the copyright owner, any later version. +// +// PDF4QT 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 PDF4QT. If not, see . + +#ifndef SIGNDIALOG_H +#define SIGNDIALOG_H + +#include + +namespace Ui +{ +class SignDialog; +} + +namespace pdfplugin +{ + +class SignDialog : public QDialog +{ + Q_OBJECT + +public: + explicit SignDialog(QWidget* parent, bool isSceneEmpty); + virtual ~SignDialog() override; + + virtual void accept() override; + + enum SignMethod + { + SignDigitally, + SignDigitallyInvisible + }; + + SignMethod getSignMethod() const; + QString getCertificatePath() const; + +private: + Ui::SignDialog* ui; +}; + +} // namespace pdfplugin + +#endif // SIGNDIALOG_H diff --git a/Pdf4QtViewerPlugins/SignaturePlugin/signdialog.ui b/Pdf4QtViewerPlugins/SignaturePlugin/signdialog.ui new file mode 100644 index 0000000..d8c4466 --- /dev/null +++ b/Pdf4QtViewerPlugins/SignaturePlugin/signdialog.ui @@ -0,0 +1,160 @@ + + + SignDialog + + + + 0 + 0 + 501 + 332 + + + + Dialog + + + + + + Sign Method + + + + + + Method + + + + + + + + + + Certificate + + + + + + + + + + Password + + + + + + + QLineEdit::Password + + + true + + + + + + + + + + Parameters + + + + + + Reason + + + + + + + true + + + + + + + Contact Info + + + + + + + true + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + + buttonBox + accepted() + SignDialog + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + SignDialog + reject() + + + 316 + 260 + + + 286 + 274 + + + + + From aebed28c6d6d91daeef3f6be10510670c7c9663a Mon Sep 17 00:00:00 2001 From: Jakub Melka Date: Fri, 20 May 2022 19:57:35 +0200 Subject: [PATCH 36/39] Signature plugin: Some builder functions --- Pdf4QtLib/sources/pdfdocumentbuilder.cpp | 431 ++--- Pdf4QtLib/sources/pdfdocumentbuilder.h | 138 +- .../SignaturePlugin/certificatemanager.cpp | 57 + .../SignaturePlugin/certificatemanager.h | 6 + .../SignaturePlugin/signatureplugin.cpp | 9 + .../SignaturePlugin/signdialog.cpp | 5 + .../SignaturePlugin/signdialog.h | 1 + generated_code_definition.xml | 1437 ++++++++++------- 8 files changed, 1222 insertions(+), 862 deletions(-) diff --git a/Pdf4QtLib/sources/pdfdocumentbuilder.cpp b/Pdf4QtLib/sources/pdfdocumentbuilder.cpp index 190a51b..e7b04a5 100644 --- a/Pdf4QtLib/sources/pdfdocumentbuilder.cpp +++ b/Pdf4QtLib/sources/pdfdocumentbuilder.cpp @@ -1928,6 +1928,30 @@ PDFObjectReference PDFDocumentBuilder::appendPage(QRectF mediaBox) } +PDFObjectReference PDFDocumentBuilder::createAcroForm(PDFObjectReferenceVector fields) +{ + PDFObjectFactory objectBuilder; + + objectBuilder.beginDictionary(); + objectBuilder.beginDictionaryItem("Fields"); + objectBuilder << fields; + objectBuilder.endDictionaryItem(); + objectBuilder.beginDictionaryItem("NeedAppearances"); + objectBuilder << false; + objectBuilder.endDictionaryItem(); + objectBuilder.beginDictionaryItem("SigFlags"); + objectBuilder << 0; + objectBuilder.endDictionaryItem(); + objectBuilder.beginDictionaryItem("XFA"); + objectBuilder << PDFObject(); + objectBuilder.endDictionaryItem(); + objectBuilder.endDictionary(); + PDFObjectReference acroForm = addObject(objectBuilder.takeObject()); + setCatalogAcroForm(acroForm); + return acroForm; +} + + PDFObjectReference PDFDocumentBuilder::createActionGoTo(PDFDestination destination) { PDFObjectFactory objectBuilder; @@ -2692,88 +2716,6 @@ PDFObjectReference PDFDocumentBuilder::createAnnotationFileAttachment(PDFObjectR } -PDFObjectReference PDFDocumentBuilder::createAnnotationFreeText(PDFObjectReference page, - QRectF boundingRectangle, - QRectF textRectangle, - QString title, - QString subject, - QString contents, - TextAlignment textAlignment, - QPointF startPoint, - QPointF endPoint, - AnnotationLineEnding startLineType, - AnnotationLineEnding endLineType) -{ - PDFObjectFactory objectBuilder; - - objectBuilder.beginDictionary(); - objectBuilder.beginDictionaryItem("Type"); - objectBuilder << WrapName("Annot"); - objectBuilder.endDictionaryItem(); - objectBuilder.beginDictionaryItem("Subtype"); - objectBuilder << WrapName("FreeText"); - objectBuilder.endDictionaryItem(); - objectBuilder.beginDictionaryItem("Rect"); - objectBuilder << boundingRectangle; - objectBuilder.endDictionaryItem(); - objectBuilder.beginDictionaryItem("F"); - objectBuilder << 4; - objectBuilder.endDictionaryItem(); - objectBuilder.beginDictionaryItem("P"); - objectBuilder << page; - objectBuilder.endDictionaryItem(); - objectBuilder.beginDictionaryItem("M"); - objectBuilder << WrapCurrentDateTime(); - objectBuilder.endDictionaryItem(); - objectBuilder.beginDictionaryItem("CreationDate"); - objectBuilder << WrapCurrentDateTime(); - objectBuilder.endDictionaryItem(); - objectBuilder.beginDictionaryItem("T"); - objectBuilder << title; - objectBuilder.endDictionaryItem(); - objectBuilder.beginDictionaryItem("Contents"); - objectBuilder << contents; - objectBuilder.endDictionaryItem(); - objectBuilder.beginDictionaryItem("Subj"); - objectBuilder << subject; - objectBuilder.endDictionaryItem(); - objectBuilder.beginDictionaryItem("Q"); - objectBuilder << WrapFreeTextAlignment(textAlignment); - objectBuilder.endDictionaryItem(); - objectBuilder.beginDictionaryItem("DA"); - objectBuilder << WrapString("/Arial 10 Tf"); - objectBuilder.endDictionaryItem(); - objectBuilder.beginDictionaryItem("RD"); - objectBuilder << getAnnotationReductionRectangle(boundingRectangle, textRectangle); - objectBuilder.endDictionaryItem(); - objectBuilder.beginDictionaryItem("CL"); - objectBuilder.beginArray(); - objectBuilder << startPoint; - objectBuilder << endPoint; - objectBuilder.endArray(); - objectBuilder.endDictionaryItem(); - objectBuilder.beginDictionaryItem("LE"); - objectBuilder.beginArray(); - objectBuilder << startLineType; - objectBuilder << endLineType; - objectBuilder.endArray(); - objectBuilder.endDictionaryItem(); - objectBuilder.endDictionary(); - PDFObjectReference annotationObject = addObject(objectBuilder.takeObject()); - objectBuilder.beginDictionary(); - objectBuilder.beginDictionaryItem("Annots"); - objectBuilder.beginArray(); - objectBuilder << annotationObject; - objectBuilder.endArray(); - objectBuilder.endDictionaryItem(); - objectBuilder.endDictionary(); - PDFObject pageAnnots = objectBuilder.takeObject(); - appendTo(page, pageAnnots); - updateAnnotationAppearanceStreams(annotationObject); - return annotationObject; -} - - PDFObjectReference PDFDocumentBuilder::createAnnotationFreeText(PDFObjectReference page, QRectF boundingRectangle, QRectF textRectangle, @@ -2920,6 +2862,88 @@ PDFObjectReference PDFDocumentBuilder::createAnnotationFreeText(PDFObjectReferen } +PDFObjectReference PDFDocumentBuilder::createAnnotationFreeText(PDFObjectReference page, + QRectF boundingRectangle, + QRectF textRectangle, + QString title, + QString subject, + QString contents, + TextAlignment textAlignment, + QPointF startPoint, + QPointF endPoint, + AnnotationLineEnding startLineType, + AnnotationLineEnding endLineType) +{ + PDFObjectFactory objectBuilder; + + objectBuilder.beginDictionary(); + objectBuilder.beginDictionaryItem("Type"); + objectBuilder << WrapName("Annot"); + objectBuilder.endDictionaryItem(); + objectBuilder.beginDictionaryItem("Subtype"); + objectBuilder << WrapName("FreeText"); + objectBuilder.endDictionaryItem(); + objectBuilder.beginDictionaryItem("Rect"); + objectBuilder << boundingRectangle; + objectBuilder.endDictionaryItem(); + objectBuilder.beginDictionaryItem("F"); + objectBuilder << 4; + objectBuilder.endDictionaryItem(); + objectBuilder.beginDictionaryItem("P"); + objectBuilder << page; + objectBuilder.endDictionaryItem(); + objectBuilder.beginDictionaryItem("M"); + objectBuilder << WrapCurrentDateTime(); + objectBuilder.endDictionaryItem(); + objectBuilder.beginDictionaryItem("CreationDate"); + objectBuilder << WrapCurrentDateTime(); + objectBuilder.endDictionaryItem(); + objectBuilder.beginDictionaryItem("T"); + objectBuilder << title; + objectBuilder.endDictionaryItem(); + objectBuilder.beginDictionaryItem("Contents"); + objectBuilder << contents; + objectBuilder.endDictionaryItem(); + objectBuilder.beginDictionaryItem("Subj"); + objectBuilder << subject; + objectBuilder.endDictionaryItem(); + objectBuilder.beginDictionaryItem("Q"); + objectBuilder << WrapFreeTextAlignment(textAlignment); + objectBuilder.endDictionaryItem(); + objectBuilder.beginDictionaryItem("DA"); + objectBuilder << WrapString("/Arial 10 Tf"); + objectBuilder.endDictionaryItem(); + objectBuilder.beginDictionaryItem("RD"); + objectBuilder << getAnnotationReductionRectangle(boundingRectangle, textRectangle); + objectBuilder.endDictionaryItem(); + objectBuilder.beginDictionaryItem("CL"); + objectBuilder.beginArray(); + objectBuilder << startPoint; + objectBuilder << endPoint; + objectBuilder.endArray(); + objectBuilder.endDictionaryItem(); + objectBuilder.beginDictionaryItem("LE"); + objectBuilder.beginArray(); + objectBuilder << startLineType; + objectBuilder << endLineType; + objectBuilder.endArray(); + objectBuilder.endDictionaryItem(); + objectBuilder.endDictionary(); + PDFObjectReference annotationObject = addObject(objectBuilder.takeObject()); + objectBuilder.beginDictionary(); + objectBuilder.beginDictionaryItem("Annots"); + objectBuilder.beginArray(); + objectBuilder << annotationObject; + objectBuilder.endArray(); + objectBuilder.endDictionaryItem(); + objectBuilder.endDictionary(); + PDFObject pageAnnots = objectBuilder.takeObject(); + appendTo(page, pageAnnots); + updateAnnotationAppearanceStreams(annotationObject); + return annotationObject; +} + + PDFObjectReference PDFDocumentBuilder::createAnnotationHighlight(PDFObjectReference page, QRectF rectangle, QColor color, @@ -3845,6 +3869,47 @@ PDFObjectReference PDFDocumentBuilder::createAnnotationSquiggly(PDFObjectReferen } +PDFObjectReference PDFDocumentBuilder::createAnnotationSquiggly(PDFObjectReference page, + QPolygonF quadrilaterals, + QColor color) +{ + PDFObjectFactory objectBuilder; + + objectBuilder.beginDictionary(); + objectBuilder.beginDictionaryItem("Type"); + objectBuilder << WrapName("Annot"); + objectBuilder.endDictionaryItem(); + objectBuilder.beginDictionaryItem("Subtype"); + objectBuilder << WrapName("Squiggly"); + objectBuilder.endDictionaryItem(); + objectBuilder.beginDictionaryItem("P"); + objectBuilder << page; + objectBuilder.endDictionaryItem(); + objectBuilder.beginDictionaryItem("CreationDate"); + objectBuilder << WrapCurrentDateTime(); + objectBuilder.endDictionaryItem(); + objectBuilder.beginDictionaryItem("C"); + objectBuilder << color; + objectBuilder.endDictionaryItem(); + objectBuilder.beginDictionaryItem("QuadPoints"); + objectBuilder << quadrilaterals; + objectBuilder.endDictionaryItem(); + objectBuilder.endDictionary(); + PDFObjectReference annotationObject = addObject(objectBuilder.takeObject()); + objectBuilder.beginDictionary(); + objectBuilder.beginDictionaryItem("Annots"); + objectBuilder.beginArray(); + objectBuilder << annotationObject; + objectBuilder.endArray(); + objectBuilder.endDictionaryItem(); + objectBuilder.endDictionary(); + PDFObject pageAnnots = objectBuilder.takeObject(); + appendTo(page, pageAnnots); + updateAnnotationAppearanceStreams(annotationObject); + return annotationObject; +} + + PDFObjectReference PDFDocumentBuilder::createAnnotationSquiggly(PDFObjectReference page, QRectF rectangle, QColor color, @@ -3909,47 +3974,6 @@ PDFObjectReference PDFDocumentBuilder::createAnnotationSquiggly(PDFObjectReferen } -PDFObjectReference PDFDocumentBuilder::createAnnotationSquiggly(PDFObjectReference page, - QPolygonF quadrilaterals, - QColor color) -{ - PDFObjectFactory objectBuilder; - - objectBuilder.beginDictionary(); - objectBuilder.beginDictionaryItem("Type"); - objectBuilder << WrapName("Annot"); - objectBuilder.endDictionaryItem(); - objectBuilder.beginDictionaryItem("Subtype"); - objectBuilder << WrapName("Squiggly"); - objectBuilder.endDictionaryItem(); - objectBuilder.beginDictionaryItem("P"); - objectBuilder << page; - objectBuilder.endDictionaryItem(); - objectBuilder.beginDictionaryItem("CreationDate"); - objectBuilder << WrapCurrentDateTime(); - objectBuilder.endDictionaryItem(); - objectBuilder.beginDictionaryItem("C"); - objectBuilder << color; - objectBuilder.endDictionaryItem(); - objectBuilder.beginDictionaryItem("QuadPoints"); - objectBuilder << quadrilaterals; - objectBuilder.endDictionaryItem(); - objectBuilder.endDictionary(); - PDFObjectReference annotationObject = addObject(objectBuilder.takeObject()); - objectBuilder.beginDictionary(); - objectBuilder.beginDictionaryItem("Annots"); - objectBuilder.beginArray(); - objectBuilder << annotationObject; - objectBuilder.endArray(); - objectBuilder.endDictionaryItem(); - objectBuilder.endDictionary(); - PDFObject pageAnnots = objectBuilder.takeObject(); - appendTo(page, pageAnnots); - updateAnnotationAppearanceStreams(annotationObject); - return annotationObject; -} - - PDFObjectReference PDFDocumentBuilder::createAnnotationStamp(PDFObjectReference page, QRectF rectangle, Stamp stampType, @@ -4233,70 +4257,6 @@ PDFObjectReference PDFDocumentBuilder::createAnnotationText(PDFObjectReference p } -PDFObjectReference PDFDocumentBuilder::createAnnotationUnderline(PDFObjectReference page, - QRectF rectangle, - QColor color, - QString title, - QString subject, - QString contents) -{ - PDFObjectFactory objectBuilder; - - objectBuilder.beginDictionary(); - objectBuilder.beginDictionaryItem("Type"); - objectBuilder << WrapName("Annot"); - objectBuilder.endDictionaryItem(); - objectBuilder.beginDictionaryItem("Subtype"); - objectBuilder << WrapName("Underline"); - objectBuilder.endDictionaryItem(); - objectBuilder.beginDictionaryItem("Rect"); - objectBuilder << rectangle; - objectBuilder.endDictionaryItem(); - objectBuilder.beginDictionaryItem("P"); - objectBuilder << page; - objectBuilder.endDictionaryItem(); - objectBuilder.beginDictionaryItem("M"); - objectBuilder << WrapCurrentDateTime(); - objectBuilder.endDictionaryItem(); - objectBuilder.beginDictionaryItem("CreationDate"); - objectBuilder << WrapCurrentDateTime(); - objectBuilder.endDictionaryItem(); - objectBuilder.beginDictionaryItem("C"); - objectBuilder << color; - objectBuilder.endDictionaryItem(); - objectBuilder.beginDictionaryItem("T"); - objectBuilder << title; - objectBuilder.endDictionaryItem(); - objectBuilder.beginDictionaryItem("Contents"); - objectBuilder << contents; - objectBuilder.endDictionaryItem(); - objectBuilder.beginDictionaryItem("Subj"); - objectBuilder << subject; - objectBuilder.endDictionaryItem(); - objectBuilder.beginDictionaryItem("QuadPoints"); - objectBuilder.beginArray(); - objectBuilder << rectangle.bottomLeft(); - objectBuilder << rectangle.bottomRight(); - objectBuilder << rectangle.topLeft(); - objectBuilder << rectangle.topRight(); - objectBuilder.endArray(); - objectBuilder.endDictionaryItem(); - objectBuilder.endDictionary(); - PDFObjectReference annotationObject = addObject(objectBuilder.takeObject()); - objectBuilder.beginDictionary(); - objectBuilder.beginDictionaryItem("Annots"); - objectBuilder.beginArray(); - objectBuilder << annotationObject; - objectBuilder.endArray(); - objectBuilder.endDictionaryItem(); - objectBuilder.endDictionary(); - PDFObject pageAnnots = objectBuilder.takeObject(); - appendTo(page, pageAnnots); - updateAnnotationAppearanceStreams(annotationObject); - return annotationObject; -} - - PDFObjectReference PDFDocumentBuilder::createAnnotationUnderline(PDFObjectReference page, QRectF rectangle, QColor color) @@ -4387,6 +4347,70 @@ PDFObjectReference PDFDocumentBuilder::createAnnotationUnderline(PDFObjectRefere } +PDFObjectReference PDFDocumentBuilder::createAnnotationUnderline(PDFObjectReference page, + QRectF rectangle, + QColor color, + QString title, + QString subject, + QString contents) +{ + PDFObjectFactory objectBuilder; + + objectBuilder.beginDictionary(); + objectBuilder.beginDictionaryItem("Type"); + objectBuilder << WrapName("Annot"); + objectBuilder.endDictionaryItem(); + objectBuilder.beginDictionaryItem("Subtype"); + objectBuilder << WrapName("Underline"); + objectBuilder.endDictionaryItem(); + objectBuilder.beginDictionaryItem("Rect"); + objectBuilder << rectangle; + objectBuilder.endDictionaryItem(); + objectBuilder.beginDictionaryItem("P"); + objectBuilder << page; + objectBuilder.endDictionaryItem(); + objectBuilder.beginDictionaryItem("M"); + objectBuilder << WrapCurrentDateTime(); + objectBuilder.endDictionaryItem(); + objectBuilder.beginDictionaryItem("CreationDate"); + objectBuilder << WrapCurrentDateTime(); + objectBuilder.endDictionaryItem(); + objectBuilder.beginDictionaryItem("C"); + objectBuilder << color; + objectBuilder.endDictionaryItem(); + objectBuilder.beginDictionaryItem("T"); + objectBuilder << title; + objectBuilder.endDictionaryItem(); + objectBuilder.beginDictionaryItem("Contents"); + objectBuilder << contents; + objectBuilder.endDictionaryItem(); + objectBuilder.beginDictionaryItem("Subj"); + objectBuilder << subject; + objectBuilder.endDictionaryItem(); + objectBuilder.beginDictionaryItem("QuadPoints"); + objectBuilder.beginArray(); + objectBuilder << rectangle.bottomLeft(); + objectBuilder << rectangle.bottomRight(); + objectBuilder << rectangle.topLeft(); + objectBuilder << rectangle.topRight(); + objectBuilder.endArray(); + objectBuilder.endDictionaryItem(); + objectBuilder.endDictionary(); + PDFObjectReference annotationObject = addObject(objectBuilder.takeObject()); + objectBuilder.beginDictionary(); + objectBuilder.beginDictionaryItem("Annots"); + objectBuilder.beginArray(); + objectBuilder << annotationObject; + objectBuilder.endArray(); + objectBuilder.endDictionaryItem(); + objectBuilder.endDictionary(); + PDFObject pageAnnots = objectBuilder.takeObject(); + appendTo(page, pageAnnots); + updateAnnotationAppearanceStreams(annotationObject); + return annotationObject; +} + + PDFObjectReference PDFDocumentBuilder::createCatalog() { PDFObjectFactory objectBuilder; @@ -4518,6 +4542,39 @@ PDFObjectReference PDFDocumentBuilder::createFileSpecification(QString fileName) } +PDFObjectReference PDFDocumentBuilder::createSignatureDictionary(QByteArray filter, + QByteArray subfilter, + QByteArray contents, + QDateTime signingTime, + PDFInteger byteRangeItem) +{ + PDFObjectFactory objectBuilder; + + objectBuilder.beginDictionary(); + objectBuilder.beginDictionaryItem("Type"); + objectBuilder << WrapName("Sig"); + objectBuilder.endDictionaryItem(); + objectBuilder.beginDictionaryItem("Filter"); + objectBuilder << WrapName(filter); + objectBuilder.endDictionaryItem(); + objectBuilder.beginDictionaryItem("SubFilter"); + objectBuilder << WrapName(subfilter); + objectBuilder.endDictionaryItem(); + objectBuilder.beginDictionaryItem("ByteRange"); + objectBuilder << std::array{ byteRangeItem, byteRangeItem, byteRangeItem, byteRangeItem }; + objectBuilder.endDictionaryItem(); + objectBuilder.beginDictionaryItem("Contents"); + objectBuilder << contents; + objectBuilder.endDictionaryItem(); + objectBuilder.beginDictionaryItem("M"); + objectBuilder << signingTime; + objectBuilder.endDictionaryItem(); + objectBuilder.endDictionary(); + PDFObjectReference signatureDictionary = addObject(objectBuilder.takeObject()); + return signatureDictionary; +} + + PDFObject PDFDocumentBuilder::createTrailerDictionary(PDFObjectReference catalog) { PDFObjectFactory objectBuilder; diff --git a/Pdf4QtLib/sources/pdfdocumentbuilder.h b/Pdf4QtLib/sources/pdfdocumentbuilder.h index 0f1f7ea..efef069 100644 --- a/Pdf4QtLib/sources/pdfdocumentbuilder.h +++ b/Pdf4QtLib/sources/pdfdocumentbuilder.h @@ -39,6 +39,12 @@ struct WrapName } + WrapName(QByteArray name) : + name(std::move(name)) + { + + } + QByteArray name; }; @@ -468,6 +474,11 @@ public: PDFObjectReference appendPage(QRectF mediaBox); + /// Creates AcroForm dictionary. Erases XFA form if present. + /// \param fields Fields + PDFObjectReference createAcroForm(PDFObjectReferenceVector fields); + + /// Creates GoTo action. This action changes view to a specific destination in the same document. /// \param destination Destination PDFObjectReference createActionGoTo(PDFDestination destination); @@ -684,36 +695,6 @@ public: QString description); - /// Free text annotation displays text directly on a page. Text appears directly on the page, in the - /// same way, as standard text in PDF document. Free text annotations are usually used to comment - /// the document. Free text annotation can also have callout line, with, or without a knee. Specify - /// start/end point parameters of this function to get callout line. - /// \param page Page to which is annotation added - /// \param boundingRectangle Bounding rectangle of free text annotation. It must contain both - /// callout line and text rectangle. - /// \param textRectangle Rectangle with text, in absolute coordinates. They are then recomputed to - /// match bounding rectangle. - /// \param title Title - /// \param subject Subject - /// \param contents Contents (text displayed) - /// \param textAlignment Text alignment. Only horizontal alignment flags are valid. - /// \param startPoint Start point of callout line - /// \param endPoint End point of callout line - /// \param startLineType Line ending at the start point - /// \param endLineType Line ending at the end point - PDFObjectReference createAnnotationFreeText(PDFObjectReference page, - QRectF boundingRectangle, - QRectF textRectangle, - QString title, - QString subject, - QString contents, - TextAlignment textAlignment, - QPointF startPoint, - QPointF endPoint, - AnnotationLineEnding startLineType, - AnnotationLineEnding endLineType); - - /// Free text annotation displays text directly on a page. Text appears directly on the page, in the /// same way, as standard text in PDF document. Free text annotations are usually used to comment /// the document. Free text annotation can also have callout line, with, or without a knee. Specify @@ -763,6 +744,36 @@ public: TextAlignment textAlignment); + /// Free text annotation displays text directly on a page. Text appears directly on the page, in the + /// same way, as standard text in PDF document. Free text annotations are usually used to comment + /// the document. Free text annotation can also have callout line, with, or without a knee. Specify + /// start/end point parameters of this function to get callout line. + /// \param page Page to which is annotation added + /// \param boundingRectangle Bounding rectangle of free text annotation. It must contain both + /// callout line and text rectangle. + /// \param textRectangle Rectangle with text, in absolute coordinates. They are then recomputed to + /// match bounding rectangle. + /// \param title Title + /// \param subject Subject + /// \param contents Contents (text displayed) + /// \param textAlignment Text alignment. Only horizontal alignment flags are valid. + /// \param startPoint Start point of callout line + /// \param endPoint End point of callout line + /// \param startLineType Line ending at the start point + /// \param endLineType Line ending at the end point + PDFObjectReference createAnnotationFreeText(PDFObjectReference page, + QRectF boundingRectangle, + QRectF textRectangle, + QString title, + QString subject, + QString contents, + TextAlignment textAlignment, + QPointF startPoint, + QPointF endPoint, + AnnotationLineEnding startLineType, + AnnotationLineEnding endLineType); + + /// Text markup annotation is used to highlight text. It is a markup annotation, so it can contain /// window to be opened (and commented). This annotation is usually used to highlight text, but can /// also highlight other things, such as images, or other graphics. @@ -1046,6 +1057,16 @@ public: QColor color); + /// Text markup annotation is used to squiggly underline text. It is a markup annotation, so it can + /// contain window to be opened (and commented). + /// \param page Page to which is annotation added + /// \param quadrilaterals Area in which is markup displayed + /// \param color Color + PDFObjectReference createAnnotationSquiggly(PDFObjectReference page, + QPolygonF quadrilaterals, + QColor color); + + /// Text markup annotation is used to squiggly underline text. It is a markup annotation, so it can /// contain window to be opened (and commented). /// \param page Page to which is annotation added @@ -1062,16 +1083,6 @@ public: QString contents); - /// Text markup annotation is used to squiggly underline text. It is a markup annotation, so it can - /// contain window to be opened (and commented). - /// \param page Page to which is annotation added - /// \param quadrilaterals Area in which is markup displayed - /// \param color Color - PDFObjectReference createAnnotationSquiggly(PDFObjectReference page, - QPolygonF quadrilaterals, - QColor color); - - /// Stamp annotation /// \param page Page to which is annotation added /// \param rectangle Stamp area @@ -1144,22 +1155,6 @@ public: bool open); - /// Text markup annotation is used to underline text. It is a markup annotation, so it can contain - /// window to be opened (and commented). - /// \param page Page to which is annotation added - /// \param rectangle Area in which is markup displayed - /// \param color Color - /// \param title Title - /// \param subject Subject - /// \param contents Contents - PDFObjectReference createAnnotationUnderline(PDFObjectReference page, - QRectF rectangle, - QColor color, - QString title, - QString subject, - QString contents); - - /// Text markup annotation is used to underline text. It is a markup annotation, so it can contain /// window to be opened (and commented). /// \param page Page to which is annotation added @@ -1180,6 +1175,22 @@ public: QColor color); + /// Text markup annotation is used to underline text. It is a markup annotation, so it can contain + /// window to be opened (and commented). + /// \param page Page to which is annotation added + /// \param rectangle Area in which is markup displayed + /// \param color Color + /// \param title Title + /// \param subject Subject + /// \param contents Contents + PDFObjectReference createAnnotationUnderline(PDFObjectReference page, + QRectF rectangle, + QColor color, + QString title, + QString subject, + QString contents); + + /// Creates empty catalog. This function is used, when a new document is being created. Do not call /// this function manually. PDFObjectReference createCatalog(); @@ -1215,6 +1226,21 @@ public: PDFObjectReference createFileSpecification(QString fileName); + /// Creates signature dictionary used for preparation in signing process. Can define parameters of the + /// signature. + /// \param filter Filter (for example, Adobe.PPKLite, Entrust.PPKEF, CiCi.SignIt, ...) + /// \param subfilter Subfilter (for example, adbe.pkcs7.detached, adbe.pkcs7.sha1, + /// ETSI.CAdES.detached, ...) + /// \param contents Contents (reserved data for signature). + /// \param signingTime Signing date/time + /// \param byteRangeItem Item which will fill byte range array. + PDFObjectReference createSignatureDictionary(QByteArray filter, + QByteArray subfilter, + QByteArray contents, + QDateTime signingTime, + PDFInteger byteRangeItem); + + /// This function is used to create a new trailer dictionary, when blank document is created. Do not /// call this function manually. /// \param catalog Reference to document catalog diff --git a/Pdf4QtViewerPlugins/SignaturePlugin/certificatemanager.cpp b/Pdf4QtViewerPlugins/SignaturePlugin/certificatemanager.cpp index 6854c3a..2fd5c08 100644 --- a/Pdf4QtViewerPlugins/SignaturePlugin/certificatemanager.cpp +++ b/Pdf4QtViewerPlugins/SignaturePlugin/certificatemanager.cpp @@ -190,4 +190,61 @@ bool CertificateManager::isCertificateValid(QString fileName, QString password) return false; } +bool SignatureFactory::sign(QString certificateName, QString password, QByteArray data, QByteArray& result) +{ + QFile file(certificateName); + if (file.open(QFile::ReadOnly)) + { + QByteArray certificateData = file.readAll(); + file.close(); + + openssl_ptr certificateBuffer(BIO_new(BIO_s_mem()), &BIO_free_all); + BIO_write(certificateBuffer.get(), certificateData.constData(), certificateData.length()); + + openssl_ptr pkcs12(d2i_PKCS12_bio(certificateBuffer.get(), nullptr), &PKCS12_free); + if (pkcs12) + { + const char* passwordPointer = nullptr; + QByteArray passwordByteArray = password.isEmpty() ? QByteArray() : password.toUtf8(); + if (!passwordByteArray.isEmpty()) + { + passwordPointer = passwordByteArray.constData(); + } + + EVP_PKEY* key = nullptr; + X509* certificate = nullptr; + STACK_OF(X509)* certificates = nullptr; + if (PKCS12_parse(pkcs12.get(), passwordPointer, &key, &certificate, &certificates) == 1) + { + openssl_ptr signedDataBuffer(BIO_new(BIO_s_mem()), &BIO_free_all); + BIO_write(signedDataBuffer.get(), data.constData(), data.length()); + + PKCS7* signature = PKCS7_sign(certificate, key, certificates, signedDataBuffer.get(), PKCS7_DETACHED | PKCS7_BINARY); + if (signature) + { + openssl_ptr outputBuffer(BIO_new(BIO_s_mem()), &BIO_free_all); + i2d_PKCS7_bio(outputBuffer.get(), signature); + + BUF_MEM* pksMemoryBuffer = nullptr; + BIO_get_mem_ptr(outputBuffer.get(), &pksMemoryBuffer); + + result = QByteArray(pksMemoryBuffer->data, int(pksMemoryBuffer->length)); + + EVP_PKEY_free(key); + X509_free(certificate); + sk_X509_free(certificates); + return true; + } + + EVP_PKEY_free(key); + X509_free(certificate); + sk_X509_free(certificates); + return false; + } + } + } + + return false; +} + } // namespace pdfplugin diff --git a/Pdf4QtViewerPlugins/SignaturePlugin/certificatemanager.h b/Pdf4QtViewerPlugins/SignaturePlugin/certificatemanager.h index 7a52f4e..94de089 100644 --- a/Pdf4QtViewerPlugins/SignaturePlugin/certificatemanager.h +++ b/Pdf4QtViewerPlugins/SignaturePlugin/certificatemanager.h @@ -53,6 +53,12 @@ public: static bool isCertificateValid(QString fileName, QString password); }; +class SignatureFactory +{ +public: + static bool sign(QString certificateName, QString password, QByteArray data, QByteArray& result); +}; + } // namespace pdfplugin #endif // CERTIFICATEMANAGER_H diff --git a/Pdf4QtViewerPlugins/SignaturePlugin/signatureplugin.cpp b/Pdf4QtViewerPlugins/SignaturePlugin/signatureplugin.cpp index 6b9ae9c..d5a28e8 100644 --- a/Pdf4QtViewerPlugins/SignaturePlugin/signatureplugin.cpp +++ b/Pdf4QtViewerPlugins/SignaturePlugin/signatureplugin.cpp @@ -325,7 +325,16 @@ void SignaturePlugin::onSignDigitally() SignDialog dialog(m_dataExchangeInterface->getMainWindow(), m_scene.isEmpty()); if (dialog.exec() == SignDialog::Accepted) { + QByteArray data = "xxgaghre"; + QByteArray result; + SignatureFactory::sign(dialog.getCertificatePath(), dialog.getPassword(), data, result); + for (int i = 1; i < 15; ++i) + { + data.append(data); + } + + SignatureFactory::sign(dialog.getCertificatePath(), dialog.getPassword(), data, result); } } diff --git a/Pdf4QtViewerPlugins/SignaturePlugin/signdialog.cpp b/Pdf4QtViewerPlugins/SignaturePlugin/signdialog.cpp index eebadd4..32ab351 100644 --- a/Pdf4QtViewerPlugins/SignaturePlugin/signdialog.cpp +++ b/Pdf4QtViewerPlugins/SignaturePlugin/signdialog.cpp @@ -63,6 +63,11 @@ QString SignDialog::getCertificatePath() const return ui->certificateCombo->currentData().toString(); } +QString SignDialog::getPassword() const +{ + return ui->certificatePasswordEdit->text(); +} + void SignDialog::accept() { // Check certificate diff --git a/Pdf4QtViewerPlugins/SignaturePlugin/signdialog.h b/Pdf4QtViewerPlugins/SignaturePlugin/signdialog.h index aa727da..9e5f96e 100644 --- a/Pdf4QtViewerPlugins/SignaturePlugin/signdialog.h +++ b/Pdf4QtViewerPlugins/SignaturePlugin/signdialog.h @@ -46,6 +46,7 @@ public: SignMethod getSignMethod() const; QString getCertificatePath() const; + QString getPassword() const; private: Ui::SignDialog* ui; diff --git a/generated_code_definition.xml b/generated_code_definition.xml index 0429049..8af42b7 100644 --- a/generated_code_definition.xml +++ b/generated_code_definition.xml @@ -112,6 +112,85 @@ return pageReference; Appends a new page after last page. _PDFObjectReference
+ + + + + + + + + + fields + _PDFObjectReferenceVector + Fields + + + Parameters + + _void + + + + + + + + + + + + Fields + DictionaryItemSimple + fields + + + + + NeedAppearances + DictionaryItemSimple + false + + + + + SigFlags + DictionaryItemSimple + 0 + + + + + XFA + DictionaryItemSimple + PDFObject() + + + + Dictionary + + + + CreateObject + acroForm + _PDFObjectReference + + + + + + Code + + _void + setCatalogAcroForm(acroForm); +return acroForm; + + + Structure + createAcroForm + Creates AcroForm dictionary. Erases XFA form if present. + _PDFObjectReference + @@ -2716,281 +2795,6 @@ return annotationReference; Creates a new file attachment annotation. This annotation needs file specification as parameter. _PDFObjectReference - - - - - - - - - - page - _PDFObjectReference - Page to which is annotation added - - - - - boundingRectangle - _QRectF - Bounding rectangle of free text annotation. It must contain both callout line and text rectangle. - - - - - textRectangle - _QRectF - Rectangle with text, in absolute coordinates. They are then recomputed to match bounding rectangle. - - - - - title - _QString - Title - - - - - subject - _QString - Subject - - - - - contents - _QString - Contents (text displayed) - - - - - textAlignment - _TextAlignment - Text alignment. Only horizontal alignment flags are valid. - - - - - startPoint - _QPointF - Start point of callout line - - - - - endPoint - _QPointF - End point of callout line - - - - - startLineType - _AnnotationLineEnding - Line ending at the start point - - - - - endLineType - _AnnotationLineEnding - Line ending at the end point - - - Parameters - - _void - - - - - - - - - - - - Type - DictionaryItemSimple - WrapName("Annot") - - - - - Subtype - DictionaryItemSimple - WrapName("FreeText") - - - - - Rect - DictionaryItemSimple - boundingRectangle - - - - - F - DictionaryItemSimple - 4 - - - - - P - DictionaryItemSimple - page - - - - - M - DictionaryItemSimple - WrapCurrentDateTime() - - - - - CreationDate - DictionaryItemSimple - WrapCurrentDateTime() - - - - - T - DictionaryItemSimple - title - - - - - Contents - DictionaryItemSimple - contents - - - - - Subj - DictionaryItemSimple - subject - - - - - Q - DictionaryItemSimple - WrapFreeTextAlignment(textAlignment) - - - - - DA - DictionaryItemSimple - WrapString("/Arial 10 Tf") - - - - - RD - DictionaryItemSimple - getAnnotationReductionRectangle(boundingRectangle, textRectangle) - - - - - - - - - ArraySimple - startPoint;endPoint - - - CL - DictionaryItemComplex - - - - - - - - - - ArraySimple - startLineType;endLineType - - - LE - DictionaryItemComplex - - - - - Dictionary - - - - CreateObject - annotationObject - _PDFObjectReference - - - - - - - - - - - - - - - - ArraySimple - annotationObject - - - Annots - DictionaryItemComplex - - - - - Dictionary - - - - CreateObject - pageAnnots - _PDFObject - - - - - - Code - - _void - appendTo(page, pageAnnots); -updateAnnotationAppearanceStreams(annotationObject); -return annotationObject; - - - Annotations - createAnnotationFreeText - Free text annotation displays text directly on a page. Text appears directly on the page, in the same way, as standard text in PDF document. Free text annotations are usually used to comment the document. Free text annotation can also have callout line, with, or without a knee. Specify start/end point parameters of this function to get callout line. - _PDFObjectReference - @@ -3476,6 +3280,281 @@ return annotationObject; Free text annotation displays text directly on a page. Text appears directly on the page, in the same way, as standard text in PDF document. Free text annotations are usually used to comment the document. Free text annotation can also have callout line, with, or without a knee. _PDFObjectReference + + + + + + + + + + page + _PDFObjectReference + Page to which is annotation added + + + + + boundingRectangle + _QRectF + Bounding rectangle of free text annotation. It must contain both callout line and text rectangle. + + + + + textRectangle + _QRectF + Rectangle with text, in absolute coordinates. They are then recomputed to match bounding rectangle. + + + + + title + _QString + Title + + + + + subject + _QString + Subject + + + + + contents + _QString + Contents (text displayed) + + + + + textAlignment + _TextAlignment + Text alignment. Only horizontal alignment flags are valid. + + + + + startPoint + _QPointF + Start point of callout line + + + + + endPoint + _QPointF + End point of callout line + + + + + startLineType + _AnnotationLineEnding + Line ending at the start point + + + + + endLineType + _AnnotationLineEnding + Line ending at the end point + + + Parameters + + _void + + + + + + + + + + + + Type + DictionaryItemSimple + WrapName("Annot") + + + + + Subtype + DictionaryItemSimple + WrapName("FreeText") + + + + + Rect + DictionaryItemSimple + boundingRectangle + + + + + F + DictionaryItemSimple + 4 + + + + + P + DictionaryItemSimple + page + + + + + M + DictionaryItemSimple + WrapCurrentDateTime() + + + + + CreationDate + DictionaryItemSimple + WrapCurrentDateTime() + + + + + T + DictionaryItemSimple + title + + + + + Contents + DictionaryItemSimple + contents + + + + + Subj + DictionaryItemSimple + subject + + + + + Q + DictionaryItemSimple + WrapFreeTextAlignment(textAlignment) + + + + + DA + DictionaryItemSimple + WrapString("/Arial 10 Tf") + + + + + RD + DictionaryItemSimple + getAnnotationReductionRectangle(boundingRectangle, textRectangle) + + + + + + + + + ArraySimple + startPoint;endPoint + + + CL + DictionaryItemComplex + + + + + + + + + + ArraySimple + startLineType;endLineType + + + LE + DictionaryItemComplex + + + + + Dictionary + + + + CreateObject + annotationObject + _PDFObjectReference + + + + + + + + + + + + + + + + ArraySimple + annotationObject + + + Annots + DictionaryItemComplex + + + + + Dictionary + + + + CreateObject + pageAnnots + _PDFObject + + + + + + Code + + _void + appendTo(page, pageAnnots); +updateAnnotationAppearanceStreams(annotationObject); +return annotationObject; + + + Annotations + createAnnotationFreeText + Free text annotation displays text directly on a page. Text appears directly on the page, in the same way, as standard text in PDF document. Free text annotations are usually used to comment the document. Free text annotation can also have callout line, with, or without a knee. Specify start/end point parameters of this function to get callout line. + _PDFObjectReference + @@ -6548,6 +6627,146 @@ return annotationObject; _void appendTo(page, pageAnnots); updateAnnotationAppearanceStreams(annotationObject); +return annotationObject; + + + Annotations + createAnnotationSquiggly + Text markup annotation is used to squiggly underline text. It is a markup annotation, so it can contain window to be opened (and commented). + _PDFObjectReference + + + + + + + + + + + page + _PDFObjectReference + Page to which is annotation added + + + + + quadrilaterals + _QPolygonF + Area in which is markup displayed + + + + + color + _QColor + Color + + + Parameters + + _void + + + + + + + + + + + + Type + DictionaryItemSimple + WrapName("Annot") + + + + + Subtype + DictionaryItemSimple + WrapName("Squiggly") + + + + + P + DictionaryItemSimple + page + + + + + CreationDate + DictionaryItemSimple + WrapCurrentDateTime() + + + + + C + DictionaryItemSimple + color + + + + + QuadPoints + DictionaryItemSimple + quadrilaterals + + + + Dictionary + + + + CreateObject + annotationObject + _PDFObjectReference + + + + + + + + + + + + + + + + ArraySimple + annotationObject + + + Annots + DictionaryItemComplex + + + + + Dictionary + + + + CreateObject + pageAnnots + _PDFObject + + + + + + Code + + _void + appendTo(page, pageAnnots); +updateAnnotationAppearanceStreams(annotationObject); return annotationObject; @@ -6752,146 +6971,6 @@ return annotationObject; _void appendTo(page, pageAnnots); updateAnnotationAppearanceStreams(annotationObject); -return annotationObject; - - - Annotations - createAnnotationSquiggly - Text markup annotation is used to squiggly underline text. It is a markup annotation, so it can contain window to be opened (and commented). - _PDFObjectReference - - - - - - - - - - - page - _PDFObjectReference - Page to which is annotation added - - - - - quadrilaterals - _QPolygonF - Area in which is markup displayed - - - - - color - _QColor - Color - - - Parameters - - _void - - - - - - - - - - - - Type - DictionaryItemSimple - WrapName("Annot") - - - - - Subtype - DictionaryItemSimple - WrapName("Squiggly") - - - - - P - DictionaryItemSimple - page - - - - - CreationDate - DictionaryItemSimple - WrapCurrentDateTime() - - - - - C - DictionaryItemSimple - color - - - - - QuadPoints - DictionaryItemSimple - quadrilaterals - - - - Dictionary - - - - CreateObject - annotationObject - _PDFObjectReference - - - - - - - - - - - - - - - - ArraySimple - annotationObject - - - Annots - DictionaryItemComplex - - - - - Dictionary - - - - CreateObject - pageAnnots - _PDFObject - - - - - - Code - - _void - appendTo(page, pageAnnots); -updateAnnotationAppearanceStreams(annotationObject); return annotationObject; @@ -7831,210 +7910,6 @@ return annotationObject; Creates text annotation. Text annotation is "sticky note" attached to a point in the PDF document. When closed, it is displayed as icon, if opened, widget appears with attached text. Text annotations do not scale or rotate, they appear independent of zoom/rotate. So, they behave as if flags NoZoom or NoRotate to the annotations are being set. Popup annotation is automatically created for this annotation. _PDFObjectReference - - - - - - - - - - page - _PDFObjectReference - Page to which is annotation added - - - - - rectangle - _QRectF - Area in which is markup displayed - - - - - color - _QColor - Color - - - - - title - _QString - Title - - - - - subject - _QString - Subject - - - - - contents - _QString - Contents - - - Parameters - - _void - - - - - - - - - - - - Type - DictionaryItemSimple - WrapName("Annot") - - - - - Subtype - DictionaryItemSimple - WrapName("Underline") - - - - - Rect - DictionaryItemSimple - rectangle - - - - - P - DictionaryItemSimple - page - - - - - M - DictionaryItemSimple - WrapCurrentDateTime() - - - - - CreationDate - DictionaryItemSimple - WrapCurrentDateTime() - - - - - C - DictionaryItemSimple - color - - - - - T - DictionaryItemSimple - title - - - - - Contents - DictionaryItemSimple - contents - - - - - Subj - DictionaryItemSimple - subject - - - - - - - - - ArraySimple - rectangle.bottomLeft();rectangle.bottomRight();rectangle.topLeft();rectangle.topRight() - - - QuadPoints - DictionaryItemComplex - - - - - Dictionary - - - - CreateObject - annotationObject - _PDFObjectReference - - - - - - - - - - - - - - - - ArraySimple - annotationObject - - - Annots - DictionaryItemComplex - - - - - Dictionary - - - - CreateObject - pageAnnots - _PDFObject - - - - - - Code - - _void - appendTo(page, pageAnnots); -updateAnnotationAppearanceStreams(annotationObject); -return annotationObject; - - - Annotations - createAnnotationUnderline - Text markup annotation is used to underline text. It is a markup annotation, so it can contain window to be opened (and commented). - _PDFObjectReference - @@ -8322,6 +8197,210 @@ return annotationObject; _void appendTo(page, pageAnnots); updateAnnotationAppearanceStreams(annotationObject); +return annotationObject; + + + Annotations + createAnnotationUnderline + Text markup annotation is used to underline text. It is a markup annotation, so it can contain window to be opened (and commented). + _PDFObjectReference + + + + + + + + + + + page + _PDFObjectReference + Page to which is annotation added + + + + + rectangle + _QRectF + Area in which is markup displayed + + + + + color + _QColor + Color + + + + + title + _QString + Title + + + + + subject + _QString + Subject + + + + + contents + _QString + Contents + + + Parameters + + _void + + + + + + + + + + + + Type + DictionaryItemSimple + WrapName("Annot") + + + + + Subtype + DictionaryItemSimple + WrapName("Underline") + + + + + Rect + DictionaryItemSimple + rectangle + + + + + P + DictionaryItemSimple + page + + + + + M + DictionaryItemSimple + WrapCurrentDateTime() + + + + + CreationDate + DictionaryItemSimple + WrapCurrentDateTime() + + + + + C + DictionaryItemSimple + color + + + + + T + DictionaryItemSimple + title + + + + + Contents + DictionaryItemSimple + contents + + + + + Subj + DictionaryItemSimple + subject + + + + + + + + + ArraySimple + rectangle.bottomLeft();rectangle.bottomRight();rectangle.topLeft();rectangle.topRight() + + + QuadPoints + DictionaryItemComplex + + + + + Dictionary + + + + CreateObject + annotationObject + _PDFObjectReference + + + + + + + + + + + + + + + + ArraySimple + annotationObject + + + Annots + DictionaryItemComplex + + + + + Dictionary + + + + CreateObject + pageAnnots + _PDFObject + + + + + + Code + + _void + appendTo(page, pageAnnots); +updateAnnotationAppearanceStreams(annotationObject); return annotationObject; @@ -8775,6 +8854,126 @@ return rootNodeReference; Creates file specification for external file. _PDFObjectReference + + + + + + + + + + filter + _QByteArray + Filter (for example, Adobe.PPKLite, Entrust.PPKEF, CiCi.SignIt, ...) + + + + + subfilter + _QByteArray + Subfilter (for example, adbe.pkcs7.detached, adbe.pkcs7.sha1, ETSI.CAdES.detached, ...) + + + + + contents + _QByteArray + Contents (reserved data for signature). + + + + + signingTime + _QDateTime + Signing date/time + + + + + byteRangeItem + _PDFInteger + Item which will fill byte range array. + + + Parameters + + _void + + + + + + + + + + + + Type + DictionaryItemSimple + WrapName("Sig") + + + + + Filter + DictionaryItemSimple + WrapName(filter) + + + + + SubFilter + DictionaryItemSimple + WrapName(subfilter) + + + + + ByteRange + DictionaryItemSimple + std::array{ byteRangeItem, byteRangeItem, byteRangeItem, byteRangeItem } + + + + + Contents + DictionaryItemSimple + contents + + + + + M + DictionaryItemSimple + signingTime + + + + Dictionary + + + + CreateObject + signatureDictionary + _PDFObjectReference + + + + + + Code + + _void + return signatureDictionary; + + + Structure + createSignatureDictionary + Creates signature dictionary used for preparation in signing process. Can define parameters of the signature. + _PDFObjectReference + From 3d6954dc415b184c8c448a0250d886ec4beaac5d Mon Sep 17 00:00:00 2001 From: Jakub Melka Date: Sun, 22 May 2022 17:17:06 +0200 Subject: [PATCH 37/39] Signature plugin: digital signing --- Pdf4QtLib/sources/pdfdocumentbuilder.cpp | 41 ++++- Pdf4QtLib/sources/pdfdocumentbuilder.h | 19 ++- .../SignaturePlugin/signatureplugin.cpp | 76 ++++++++- generated_code_definition.xml | 158 ++++++++++++++---- 4 files changed, 241 insertions(+), 53 deletions(-) diff --git a/Pdf4QtLib/sources/pdfdocumentbuilder.cpp b/Pdf4QtLib/sources/pdfdocumentbuilder.cpp index e7b04a5..900278d 100644 --- a/Pdf4QtLib/sources/pdfdocumentbuilder.cpp +++ b/Pdf4QtLib/sources/pdfdocumentbuilder.cpp @@ -4542,6 +4542,31 @@ PDFObjectReference PDFDocumentBuilder::createFileSpecification(QString fileName) } +PDFObjectReference PDFDocumentBuilder::createFormFieldSignature(QString fieldName, + PDFObjectReferenceVector kids, + PDFObjectReference signatureValue) +{ + PDFObjectFactory objectBuilder; + + objectBuilder.beginDictionary(); + objectBuilder.beginDictionaryItem("FT"); + objectBuilder << WrapName("Sig"); + objectBuilder.endDictionaryItem(); + objectBuilder.beginDictionaryItem("Kids"); + objectBuilder << kids; + objectBuilder.endDictionaryItem(); + objectBuilder.beginDictionaryItem("T"); + objectBuilder << fieldName; + objectBuilder.endDictionaryItem(); + objectBuilder.beginDictionaryItem("V"); + objectBuilder << signatureValue; + objectBuilder.endDictionaryItem(); + objectBuilder.endDictionary(); + PDFObjectReference formFieldSignature = addObject(objectBuilder.takeObject()); + return formFieldSignature; +} + + PDFObjectReference PDFDocumentBuilder::createSignatureDictionary(QByteArray filter, QByteArray subfilter, QByteArray contents, @@ -5075,6 +5100,14 @@ void PDFDocumentBuilder::setFormFieldValue(PDFObjectReference formField, } +void PDFDocumentBuilder::setLanguage(QLocale locale) +{ + PDFObjectFactory objectBuilder; + + setLanguage(locale.name()); +} + + void PDFDocumentBuilder::setLanguage(QString language) { PDFObjectFactory objectBuilder; @@ -5089,14 +5122,6 @@ void PDFDocumentBuilder::setLanguage(QString language) } -void PDFDocumentBuilder::setLanguage(QLocale locale) -{ - PDFObjectFactory objectBuilder; - - setLanguage(locale.name()); -} - - void PDFDocumentBuilder::setOutline(PDFObjectReference outline) { PDFObjectFactory objectBuilder; diff --git a/Pdf4QtLib/sources/pdfdocumentbuilder.h b/Pdf4QtLib/sources/pdfdocumentbuilder.h index efef069..3f27651 100644 --- a/Pdf4QtLib/sources/pdfdocumentbuilder.h +++ b/Pdf4QtLib/sources/pdfdocumentbuilder.h @@ -1226,6 +1226,15 @@ public: PDFObjectReference createFileSpecification(QString fileName); + /// Creates form field of type signature. + /// \param fieldName Field name + /// \param kids Kids of the signature field. + /// \param signatureValue Signature value + PDFObjectReference createFormFieldSignature(QString fieldName, + PDFObjectReferenceVector kids, + PDFObjectReference signatureValue); + + /// Creates signature dictionary used for preparation in signing process. Can define parameters of the /// signature. /// \param filter Filter (for example, Adobe.PPKLite, Entrust.PPKEF, CiCi.SignIt, ...) @@ -1441,6 +1450,11 @@ public: PDFObject value); + /// Set document language. + /// \param locale Locale, from which is language determined + void setLanguage(QLocale locale); + + /// Set document language. /// \param language Document language. It should be a language identifier, as defined in ISO 639 /// and ISO 3166. For example, "en-US", where first two letter means language code (en = @@ -1448,11 +1462,6 @@ public: void setLanguage(QString language); - /// Set document language. - /// \param locale Locale, from which is language determined - void setLanguage(QLocale locale); - - /// Set document outline. /// \param outline Document outline root void setOutline(PDFObjectReference outline); diff --git a/Pdf4QtViewerPlugins/SignaturePlugin/signatureplugin.cpp b/Pdf4QtViewerPlugins/SignaturePlugin/signatureplugin.cpp index d5a28e8..3dea0f0 100644 --- a/Pdf4QtViewerPlugins/SignaturePlugin/signatureplugin.cpp +++ b/Pdf4QtViewerPlugins/SignaturePlugin/signatureplugin.cpp @@ -23,6 +23,7 @@ #include "pdfdocumentbuilder.h" #include "certificatemanagerdialog.h" #include "signdialog.h" +#include "pdfdocumentwriter.h" #include #include @@ -325,16 +326,77 @@ void SignaturePlugin::onSignDigitally() SignDialog dialog(m_dataExchangeInterface->getMainWindow(), m_scene.isEmpty()); if (dialog.exec() == SignDialog::Accepted) { - QByteArray data = "xxgaghre"; - QByteArray result; - SignatureFactory::sign(dialog.getCertificatePath(), dialog.getPassword(), data, result); - - for (int i = 1; i < 15; ++i) + QByteArray data = "SampleDataToBeSigned" + QByteArray::number(QDateTime::currentMSecsSinceEpoch()); + QByteArray signature; + if (!SignatureFactory::sign(dialog.getCertificatePath(), dialog.getPassword(), data, signature)) { - data.append(data); + QMessageBox::critical(m_widget, tr("Error"), tr("Failed to create digital signature.")); + return; } - SignatureFactory::sign(dialog.getCertificatePath(), dialog.getPassword(), data, result); + pdf::PDFInteger offsetMark = 123456789123; + constexpr const char* offsetMarkString = "123456789123"; + const auto offsetMarkStringLength = std::strlen(offsetMarkString); + + pdf::PDFDocumentBuilder builder(m_document); + pdf::PDFObjectReference signatureDictionary = builder.createSignatureDictionary("Adobe.PPKLite", "adbe.pkcs7.detached", signature, QDateTime::currentDateTime(), offsetMark); + pdf::PDFObjectReference formField = builder.createFormFieldSignature("signature", { }, signatureDictionary); + builder.createAcroForm({ formField }); + + pdf::PDFDocument signedDocument = builder.build(); + + // 1) Save the document with incorrect signature + QBuffer buffer; + pdf::PDFDocumentWriter writer(m_widget->getDrawWidgetProxy()->getProgress()); + buffer.open(QBuffer::ReadWrite); + writer.write(&buffer, &signedDocument); + + const int indexOfSignature = buffer.data().indexOf(signature.toHex()); + if (indexOfSignature == -1) + { + QMessageBox::critical(m_widget, tr("Error"), tr("Failed to create digital signature.")); + buffer.close(); + return; + } + + // 2) Write ranges to be checked + const pdf::PDFInteger i1 = 0; + const pdf::PDFInteger i2 = indexOfSignature; + const pdf::PDFInteger i3 = i2 + signature.size() * 2; + const pdf::PDFInteger i4 = buffer.data().size() - i3; + + auto writeInt = [&](pdf::PDFInteger offset) + { + QString offsetString = QString::number(offset); + offsetString = offsetString.leftJustified(static_cast(offsetMarkStringLength), ' ', true); + const auto index = buffer.data().lastIndexOf(QString(offsetMarkString), indexOfSignature); + buffer.seek(index); + buffer.write(offsetString.toLocal8Bit()); + }; + + writeInt(i4); + writeInt(i3); + writeInt(i2); + writeInt(i1); + + // 3) Sign the data + QByteArray dataToBeSigned; + buffer.seek(i1); + dataToBeSigned.append(buffer.read(i2)); + buffer.seek(i3); + dataToBeSigned.append(buffer.read(i4)); + + if (!SignatureFactory::sign(dialog.getCertificatePath(), dialog.getPassword(), dataToBeSigned, signature)) + { + QMessageBox::critical(m_widget, tr("Error"), tr("Failed to create digital signature.")); + buffer.close(); + return; + } + + buffer.seek(i2); + buffer.write(signature.toHex()); + + buffer.close(); } } diff --git a/generated_code_definition.xml b/generated_code_definition.xml index 8af42b7..e7ce281 100644 --- a/generated_code_definition.xml +++ b/generated_code_definition.xml @@ -8854,6 +8854,98 @@ return rootNodeReference; Creates file specification for external file. _PDFObjectReference + + + + + + + + + + fieldName + _QString + Field name + + + + + kids + _PDFObjectReferenceVector + Kids of the signature field. + + + + + signatureValue + _PDFObjectReference + Signature value + + + Parameters + + _void + + + + + + + + + + + + FT + DictionaryItemSimple + WrapName("Sig") + + + + + Kids + DictionaryItemSimple + kids + + + + + T + DictionaryItemSimple + fieldName + + + + + V + DictionaryItemSimple + signatureValue + + + + Dictionary + + + + CreateObject + formFieldSignature + _PDFObjectReference + + + + + + Code + + _void + return formFieldSignature; + + + Structure + createFormFieldSignature + Creates form field of type signature. + _PDFObjectReference + @@ -10938,6 +11030,39 @@ return rootNodeReference; Sets form field value. Value must be correct for this form field, no checking is performed. Also, if you use this function, annotation widgets, which are attached to this form field, should also be updated (for example, appearance state and sometimes appearance streams). _void + + + + + + + + + + locale + _QLocale + Locale, from which is language determined + + + Parameters + + _void + + + + + + Code + + _void + setLanguage(locale.name()); + + + Structure + setLanguage + Set document language. + _void + @@ -10995,39 +11120,6 @@ return rootNodeReference; Set document language. _void - - - - - - - - - - locale - _QLocale - Locale, from which is language determined - - - Parameters - - _void - - - - - - Code - - _void - setLanguage(locale.name()); - - - Structure - setLanguage - Set document language. - _void - From c7e7b76e5be692bfe62af6e719ecf084bee5dae3 Mon Sep 17 00:00:00 2001 From: Jakub Melka Date: Sat, 28 May 2022 19:42:52 +0200 Subject: [PATCH 38/39] Signature plugin: invisible digital signature --- Pdf4QtLib/sources/pdfdocumentbuilder.cpp | 79 +++- Pdf4QtLib/sources/pdfdocumentbuilder.h | 31 +- .../SignaturePlugin/signatureplugin.cpp | 51 +- .../SignaturePlugin/signatureplugin.h | 2 + .../SignaturePlugin/signdialog.cpp | 10 + .../SignaturePlugin/signdialog.h | 2 + generated_code_definition.xml | 442 ++++++++++++++---- 7 files changed, 497 insertions(+), 120 deletions(-) diff --git a/Pdf4QtLib/sources/pdfdocumentbuilder.cpp b/Pdf4QtLib/sources/pdfdocumentbuilder.cpp index 900278d..94208c8 100644 --- a/Pdf4QtLib/sources/pdfdocumentbuilder.cpp +++ b/Pdf4QtLib/sources/pdfdocumentbuilder.cpp @@ -4567,6 +4567,37 @@ PDFObjectReference PDFDocumentBuilder::createFormFieldSignature(QString fieldNam } +void PDFDocumentBuilder::createInvisibleFormFieldWidget(PDFObjectReference formField, + PDFObjectReference page) +{ + PDFObjectFactory objectBuilder; + + objectBuilder.beginDictionary(); + objectBuilder.beginDictionaryItem("P"); + objectBuilder << page; + objectBuilder.endDictionaryItem(); + objectBuilder.beginDictionaryItem("Rect"); + objectBuilder << std::array{ 0.0, 0.0, 0.0, 0.0 }; + objectBuilder.endDictionaryItem(); + objectBuilder.beginDictionaryItem("Subtype"); + objectBuilder << WrapName("Widget"); + objectBuilder.endDictionaryItem(); + objectBuilder.beginDictionaryItem("Type"); + objectBuilder << WrapName("Annot"); + objectBuilder.endDictionaryItem(); + objectBuilder.endDictionary(); + PDFObject widgetObject = objectBuilder.takeObject(); + objectBuilder.beginDictionary(); + objectBuilder.beginDictionaryItem("Annots"); + objectBuilder << std::array{ formField }; + objectBuilder.endDictionaryItem(); + objectBuilder.endDictionary(); + PDFObject pageObject = objectBuilder.takeObject(); + mergeTo(formField, widgetObject); + appendTo(page, pageObject); +} + + PDFObjectReference PDFDocumentBuilder::createSignatureDictionary(QByteArray filter, QByteArray subfilter, QByteArray contents, @@ -4589,7 +4620,7 @@ PDFObjectReference PDFDocumentBuilder::createSignatureDictionary(QByteArray filt objectBuilder << std::array{ byteRangeItem, byteRangeItem, byteRangeItem, byteRangeItem }; objectBuilder.endDictionaryItem(); objectBuilder.beginDictionaryItem("Contents"); - objectBuilder << contents; + objectBuilder << WrapString(contents); objectBuilder.endDictionaryItem(); objectBuilder.beginDictionaryItem("M"); objectBuilder << signingTime; @@ -5100,14 +5131,6 @@ void PDFDocumentBuilder::setFormFieldValue(PDFObjectReference formField, } -void PDFDocumentBuilder::setLanguage(QLocale locale) -{ - PDFObjectFactory objectBuilder; - - setLanguage(locale.name()); -} - - void PDFDocumentBuilder::setLanguage(QString language) { PDFObjectFactory objectBuilder; @@ -5122,6 +5145,14 @@ void PDFDocumentBuilder::setLanguage(QString language) } +void PDFDocumentBuilder::setLanguage(QLocale locale) +{ + PDFObjectFactory objectBuilder; + + setLanguage(locale.name()); +} + + void PDFDocumentBuilder::setOutline(PDFObjectReference outline) { PDFObjectFactory objectBuilder; @@ -5256,6 +5287,36 @@ void PDFDocumentBuilder::setPageUserUnit(PDFObjectReference page, } +void PDFDocumentBuilder::setSignatureContactInfo(PDFObjectReference signatureDictionary, + QString contactInfoText) +{ + PDFObjectFactory objectBuilder; + + objectBuilder.beginDictionary(); + objectBuilder.beginDictionaryItem("ContactInfo"); + objectBuilder << contactInfoText; + objectBuilder.endDictionaryItem(); + objectBuilder.endDictionary(); + PDFObject updatedSignatureDictionary = objectBuilder.takeObject(); + mergeTo(signatureDictionary, updatedSignatureDictionary); +} + + +void PDFDocumentBuilder::setSignatureReason(PDFObjectReference signatureDictionary, + QString reasonText) +{ + PDFObjectFactory objectBuilder; + + objectBuilder.beginDictionary(); + objectBuilder.beginDictionaryItem("Reason"); + objectBuilder << reasonText; + objectBuilder.endDictionaryItem(); + objectBuilder.endDictionary(); + PDFObject updatedSignatureDictionary = objectBuilder.takeObject(); + mergeTo(signatureDictionary, updatedSignatureDictionary); +} + + void PDFDocumentBuilder::updateTrailerDictionary(PDFInteger objectCount) { PDFObjectFactory objectBuilder; diff --git a/Pdf4QtLib/sources/pdfdocumentbuilder.h b/Pdf4QtLib/sources/pdfdocumentbuilder.h index 3f27651..32c1a77 100644 --- a/Pdf4QtLib/sources/pdfdocumentbuilder.h +++ b/Pdf4QtLib/sources/pdfdocumentbuilder.h @@ -1235,6 +1235,13 @@ public: PDFObjectReference signatureValue); + /// + /// \param formField Form field reference + /// \param page Page reference + void createInvisibleFormFieldWidget(PDFObjectReference formField, + PDFObjectReference page); + + /// Creates signature dictionary used for preparation in signing process. Can define parameters of the /// signature. /// \param filter Filter (for example, Adobe.PPKLite, Entrust.PPKEF, CiCi.SignIt, ...) @@ -1450,11 +1457,6 @@ public: PDFObject value); - /// Set document language. - /// \param locale Locale, from which is language determined - void setLanguage(QLocale locale); - - /// Set document language. /// \param language Document language. It should be a language identifier, as defined in ISO 639 /// and ISO 3166. For example, "en-US", where first two letter means language code (en = @@ -1462,6 +1464,11 @@ public: void setLanguage(QString language); + /// Set document language. + /// \param locale Locale, from which is language determined + void setLanguage(QLocale locale); + + /// Set document outline. /// \param outline Document outline root void setOutline(PDFObjectReference outline); @@ -1527,6 +1534,20 @@ public: PDFReal unit); + /// Sets signature contact info field. + /// \param signatureDictionary Signature dictionary reference + /// \param contactInfoText Contact info text + void setSignatureContactInfo(PDFObjectReference signatureDictionary, + QString contactInfoText); + + + /// Sets signature reason field. + /// \param signatureDictionary Signature dictionary reference + /// \param reasonText Reason text + void setSignatureReason(PDFObjectReference signatureDictionary, + QString reasonText); + + /// This function is used to update trailer dictionary. Must be called each time the final document is /// being built. /// \param objectCount Number of objects (including empty ones) diff --git a/Pdf4QtViewerPlugins/SignaturePlugin/signatureplugin.cpp b/Pdf4QtViewerPlugins/SignaturePlugin/signatureplugin.cpp index 3dea0f0..f6e429a 100644 --- a/Pdf4QtViewerPlugins/SignaturePlugin/signatureplugin.cpp +++ b/Pdf4QtViewerPlugins/SignaturePlugin/signatureplugin.cpp @@ -29,6 +29,7 @@ #include #include #include +#include namespace pdfplugin { @@ -334,15 +335,39 @@ void SignaturePlugin::onSignDigitally() return; } + QString signatureName = QString("pdf4qt_signature_%1").arg(QString::number(QDateTime::currentMSecsSinceEpoch())); + pdf::PDFInteger offsetMark = 123456789123; constexpr const char* offsetMarkString = "123456789123"; const auto offsetMarkStringLength = std::strlen(offsetMarkString); pdf::PDFDocumentBuilder builder(m_document); pdf::PDFObjectReference signatureDictionary = builder.createSignatureDictionary("Adobe.PPKLite", "adbe.pkcs7.detached", signature, QDateTime::currentDateTime(), offsetMark); - pdf::PDFObjectReference formField = builder.createFormFieldSignature("signature", { }, signatureDictionary); + pdf::PDFObjectReference formField = builder.createFormFieldSignature(signatureName, { }, signatureDictionary); builder.createAcroForm({ formField }); + if (dialog.getSignMethod() == SignDialog::SignDigitallyInvisible) + { + const pdf::PDFCatalog* catalog = m_document->getCatalog(); + if (catalog->getPageCount() > 0) + { + const pdf::PDFObjectReference pageReference = catalog->getPage(0)->getPageReference(); + builder.createInvisibleFormFieldWidget(formField, pageReference); + } + } + + QString reasonText = dialog.getReasonText(); + if (!reasonText.isEmpty()) + { + builder.setSignatureReason(signatureDictionary, reasonText); + } + + QString contactInfoText = dialog.getContactInfoText(); + if (!contactInfoText.isEmpty()) + { + builder.setSignatureContactInfo(signatureDictionary, contactInfoText); + } + pdf::PDFDocument signedDocument = builder.build(); // 1) Save the document with incorrect signature @@ -361,8 +386,8 @@ void SignaturePlugin::onSignDigitally() // 2) Write ranges to be checked const pdf::PDFInteger i1 = 0; - const pdf::PDFInteger i2 = indexOfSignature; - const pdf::PDFInteger i3 = i2 + signature.size() * 2; + const pdf::PDFInteger i2 = indexOfSignature - 1; + const pdf::PDFInteger i3 = i2 + signature.size() * 2 + 2; const pdf::PDFInteger i4 = buffer.data().size() - i3; auto writeInt = [&](pdf::PDFInteger offset) @@ -393,13 +418,31 @@ void SignaturePlugin::onSignDigitally() return; } - buffer.seek(i2); + buffer.seek(i2 + 1); buffer.write(signature.toHex()); buffer.close(); + + QString fileName = QFileDialog::getSaveFileName(m_dataExchangeInterface->getMainWindow(), tr("Save Signed Document"), getSignedFileName(), tr("Portable Document (*.pdf);;All files (*.*)")); + if (!fileName.isEmpty()) + { + QFile signedFile(fileName); + if (signedFile.open(QFile::WriteOnly | QFile::Truncate)) + { + signedFile.write(buffer.data()); + signedFile.close(); + } + } } } +QString SignaturePlugin::getSignedFileName() const +{ + QFileInfo fileInfo(m_dataExchangeInterface->getOriginalFileName()); + + return fileInfo.path() + "/" + fileInfo.baseName() + "_SIGNED.pdf"; +} + void SignaturePlugin::onOpenCertificatesManager() { CertificateManagerDialog dialog(m_dataExchangeInterface->getMainWindow()); diff --git a/Pdf4QtViewerPlugins/SignaturePlugin/signatureplugin.h b/Pdf4QtViewerPlugins/SignaturePlugin/signatureplugin.h index db34c8a..ea1cd86 100644 --- a/Pdf4QtViewerPlugins/SignaturePlugin/signatureplugin.h +++ b/Pdf4QtViewerPlugins/SignaturePlugin/signatureplugin.h @@ -112,6 +112,8 @@ private: void updateGraphics(); void updateDockWidget(); + QString getSignedFileName() const; + std::array m_actions; std::array m_tools; pdf::PDFPageContentEditorWidget* m_editorWidget; diff --git a/Pdf4QtViewerPlugins/SignaturePlugin/signdialog.cpp b/Pdf4QtViewerPlugins/SignaturePlugin/signdialog.cpp index 32ab351..5f36158 100644 --- a/Pdf4QtViewerPlugins/SignaturePlugin/signdialog.cpp +++ b/Pdf4QtViewerPlugins/SignaturePlugin/signdialog.cpp @@ -68,6 +68,16 @@ QString SignDialog::getPassword() const return ui->certificatePasswordEdit->text(); } +QString SignDialog::getReasonText() const +{ + return ui->reasonEdit->text(); +} + +QString SignDialog::getContactInfoText() const +{ + return ui->contactInfoEdit->text(); +} + void SignDialog::accept() { // Check certificate diff --git a/Pdf4QtViewerPlugins/SignaturePlugin/signdialog.h b/Pdf4QtViewerPlugins/SignaturePlugin/signdialog.h index 9e5f96e..6546b58 100644 --- a/Pdf4QtViewerPlugins/SignaturePlugin/signdialog.h +++ b/Pdf4QtViewerPlugins/SignaturePlugin/signdialog.h @@ -47,6 +47,8 @@ public: SignMethod getSignMethod() const; QString getCertificatePath() const; QString getPassword() const; + QString getReasonText() const; + QString getContactInfoText() const; private: Ui::SignDialog* ui; diff --git a/generated_code_definition.xml b/generated_code_definition.xml index e7ce281..b5b85d4 100644 --- a/generated_code_definition.xml +++ b/generated_code_definition.xml @@ -103,7 +103,7 @@ Code _void - appendTo(getPageTreeRoot(), updatedTreeRoot); + appendTo(getPageTreeRoot(), updatedTreeRoot); return pageReference; @@ -182,7 +182,7 @@ return pageReference; Code _void - setCatalogAcroForm(acroForm); + setCatalogAcroForm(acroForm); return acroForm; @@ -2386,8 +2386,8 @@ return acroForm; Code _void - appendTo(page, pageAnnots); -updateAnnotationAppearanceStreams(annotationObject); + appendTo(page, pageAnnots); +updateAnnotationAppearanceStreams(annotationObject); return annotationObject; @@ -2610,8 +2610,8 @@ return annotationObject; Code _void - appendTo(page, pageAnnots); -updateAnnotationAppearanceStreams(annotationObject); + appendTo(page, pageAnnots); +updateAnnotationAppearanceStreams(annotationObject); return annotationObject; @@ -2785,8 +2785,8 @@ return annotationObject; Code _void - appendTo(page, pageAnnots); -updateAnnotationAppearanceStreams(annotationReference); + appendTo(page, pageAnnots); +updateAnnotationAppearanceStreams(annotationReference); return annotationReference; @@ -3067,8 +3067,8 @@ return annotationReference; Code _void - appendTo(page, pageAnnots); -updateAnnotationAppearanceStreams(annotationObject); + appendTo(page, pageAnnots); +updateAnnotationAppearanceStreams(annotationObject); return annotationObject; @@ -3270,8 +3270,8 @@ return annotationObject; Code _void - appendTo(page, pageAnnots); -updateAnnotationAppearanceStreams(annotationObject); + appendTo(page, pageAnnots); +updateAnnotationAppearanceStreams(annotationObject); return annotationObject; @@ -3545,8 +3545,8 @@ return annotationObject; Code _void - appendTo(page, pageAnnots); -updateAnnotationAppearanceStreams(annotationObject); + appendTo(page, pageAnnots); +updateAnnotationAppearanceStreams(annotationObject); return annotationObject; @@ -3749,8 +3749,8 @@ return annotationObject; Code _void - appendTo(page, pageAnnots); -updateAnnotationAppearanceStreams(annotationObject); + appendTo(page, pageAnnots); +updateAnnotationAppearanceStreams(annotationObject); return annotationObject; @@ -3904,8 +3904,8 @@ return annotationObject; Code _void - appendTo(page, pageAnnots); -updateAnnotationAppearanceStreams(annotationObject); + appendTo(page, pageAnnots); +updateAnnotationAppearanceStreams(annotationObject); return annotationObject; @@ -4044,8 +4044,8 @@ return annotationObject; Code _void - appendTo(page, pageAnnots); -updateAnnotationAppearanceStreams(annotationObject); + appendTo(page, pageAnnots); +updateAnnotationAppearanceStreams(annotationObject); return annotationObject; @@ -4269,8 +4269,8 @@ return annotationObject; Code _void - appendTo(page, pageAnnots); -updateAnnotationAppearanceStreams(annotationObject); + appendTo(page, pageAnnots); +updateAnnotationAppearanceStreams(annotationObject); return annotationObject; @@ -4486,8 +4486,8 @@ return annotationObject; Code _void - appendTo(page, pageAnnots); -updateAnnotationAppearanceStreams(annotationObject); + appendTo(page, pageAnnots); +updateAnnotationAppearanceStreams(annotationObject); return annotationObject; @@ -4768,8 +4768,8 @@ return annotationObject; Code _void - appendTo(page, pageAnnots); -updateAnnotationAppearanceStreams(annotationObject); + appendTo(page, pageAnnots); +updateAnnotationAppearanceStreams(annotationObject); return annotationObject; @@ -5120,8 +5120,8 @@ return annotationObject; Code _void - appendTo(page, pageAnnots); -updateAnnotationAppearanceStreams(annotationObject); + appendTo(page, pageAnnots); +updateAnnotationAppearanceStreams(annotationObject); return annotationObject; @@ -5321,8 +5321,8 @@ return annotationObject; Code _void - appendTo(page, pageAnnots); -updateAnnotationAppearanceStreams(annotationReference); + appendTo(page, pageAnnots); +updateAnnotationAppearanceStreams(annotationReference); return annotationReference; @@ -5552,8 +5552,8 @@ return annotationReference; Code _void - appendTo(page, pageAnnots); -updateAnnotationAppearanceStreams(annotationObject); + appendTo(page, pageAnnots); +updateAnnotationAppearanceStreams(annotationObject); return annotationObject; @@ -5812,8 +5812,8 @@ return annotationObject; Code _void - appendTo(page, pageAnnots); -updateAnnotationAppearanceStreams(annotationObject); + appendTo(page, pageAnnots); +updateAnnotationAppearanceStreams(annotationObject); return annotationObject; @@ -5951,8 +5951,8 @@ return annotationObject; Code _void - mergeTo(parentAnnotation, upgradedParentAnnotation); -updateAnnotationAppearanceStreams(popupAnnotation); + mergeTo(parentAnnotation, upgradedParentAnnotation); +updateAnnotationAppearanceStreams(popupAnnotation); return popupAnnotation; @@ -6106,8 +6106,8 @@ return popupAnnotation; Code _void - appendTo(page, pageAnnots); -updateAnnotationAppearanceStreams(annotationObject); + appendTo(page, pageAnnots); +updateAnnotationAppearanceStreams(annotationObject); return annotationObject; @@ -6246,8 +6246,8 @@ return annotationObject; Code _void - appendTo(page, pageAnnots); -updateAnnotationAppearanceStreams(annotationObject); + appendTo(page, pageAnnots); +updateAnnotationAppearanceStreams(annotationObject); return annotationObject; @@ -6470,8 +6470,8 @@ return annotationObject; Code _void - appendTo(page, pageAnnots); -updateAnnotationAppearanceStreams(annotationObject); + appendTo(page, pageAnnots); +updateAnnotationAppearanceStreams(annotationObject); return annotationObject; @@ -6625,8 +6625,8 @@ return annotationObject; Code _void - appendTo(page, pageAnnots); -updateAnnotationAppearanceStreams(annotationObject); + appendTo(page, pageAnnots); +updateAnnotationAppearanceStreams(annotationObject); return annotationObject; @@ -6765,8 +6765,8 @@ return annotationObject; Code _void - appendTo(page, pageAnnots); -updateAnnotationAppearanceStreams(annotationObject); + appendTo(page, pageAnnots); +updateAnnotationAppearanceStreams(annotationObject); return annotationObject; @@ -6969,8 +6969,8 @@ return annotationObject; Code _void - appendTo(page, pageAnnots); -updateAnnotationAppearanceStreams(annotationObject); + appendTo(page, pageAnnots); +updateAnnotationAppearanceStreams(annotationObject); return annotationObject; @@ -7158,8 +7158,8 @@ return annotationObject; Code _void - appendTo(page, pageAnnots); -updateAnnotationAppearanceStreams(annotationObject); + appendTo(page, pageAnnots); +updateAnnotationAppearanceStreams(annotationObject); return annotationObject; @@ -7362,8 +7362,8 @@ return annotationObject; Code _void - appendTo(page, pageAnnots); -updateAnnotationAppearanceStreams(annotationObject); + appendTo(page, pageAnnots); +updateAnnotationAppearanceStreams(annotationObject); return annotationObject; @@ -7517,8 +7517,8 @@ return annotationObject; Code _void - appendTo(page, pageAnnots); -updateAnnotationAppearanceStreams(annotationObject); + appendTo(page, pageAnnots); +updateAnnotationAppearanceStreams(annotationObject); return annotationObject; @@ -7657,8 +7657,8 @@ return annotationObject; Code _void - appendTo(page, pageAnnots); -updateAnnotationAppearanceStreams(annotationObject); + appendTo(page, pageAnnots); +updateAnnotationAppearanceStreams(annotationObject); return annotationObject; @@ -7899,9 +7899,9 @@ return annotationObject; Code _void - mergeTo(annotationObject, updateAnnotationPopup); -appendTo(page, pageAnnots); -updateAnnotationAppearanceStreams(annotationObject); + mergeTo(annotationObject, updateAnnotationPopup); +appendTo(page, pageAnnots); +updateAnnotationAppearanceStreams(annotationObject); return annotationObject; @@ -8055,8 +8055,8 @@ return annotationObject; Code _void - appendTo(page, pageAnnots); -updateAnnotationAppearanceStreams(annotationObject); + appendTo(page, pageAnnots); +updateAnnotationAppearanceStreams(annotationObject); return annotationObject; @@ -8195,8 +8195,8 @@ return annotationObject; Code _void - appendTo(page, pageAnnots); -updateAnnotationAppearanceStreams(annotationObject); + appendTo(page, pageAnnots); +updateAnnotationAppearanceStreams(annotationObject); return annotationObject; @@ -8399,8 +8399,8 @@ return annotationObject; Code _void - appendTo(page, pageAnnots); -updateAnnotationAppearanceStreams(annotationObject); + appendTo(page, pageAnnots); +updateAnnotationAppearanceStreams(annotationObject); return annotationObject; @@ -8716,8 +8716,8 @@ return annotationObject; Code _void - mergeTo(rootNodeReference, updatedRootNode); -mergeTo(getCatalogReference(), updatedCatalog); + mergeTo(rootNodeReference, updatedRootNode); +mergeTo(getCatalogReference(), updatedCatalog); return rootNodeReference; @@ -8946,6 +8946,116 @@ return rootNodeReference; Creates form field of type signature. _PDFObjectReference + + + + + + + + + + formField + _PDFObjectReference + Form field reference + + + + + page + _PDFObjectReference + Page reference + + + Parameters + + _void + + + + + + + + + + + + P + DictionaryItemSimple + page + + + + + Rect + DictionaryItemSimple + std::array{ 0.0, 0.0, 0.0, 0.0 } + + + + + Subtype + DictionaryItemSimple + WrapName("Widget") + + + + + Type + DictionaryItemSimple + WrapName("Annot") + + + + Dictionary + + + + CreateObject + widgetObject + _PDFObject + + + + + + + + + + + + Annots + DictionaryItemSimple + std::array{ formField } + + + + Dictionary + + + + CreateObject + pageObject + _PDFObject + + + + + + Code + + _void + mergeTo(formField, widgetObject); +appendTo(page, pageObject); + + + Structure + createInvisibleFormFieldWidget + + _void + @@ -9032,7 +9142,7 @@ return rootNodeReference; Contents DictionaryItemSimple - contents + WrapString(contents) @@ -11030,39 +11140,6 @@ return rootNodeReference; Sets form field value. Value must be correct for this form field, no checking is performed. Also, if you use this function, annotation widgets, which are attached to this form field, should also be updated (for example, appearance state and sometimes appearance streams). _void - - - - - - - - - - locale - _QLocale - Locale, from which is language determined - - - Parameters - - _void - - - - - - Code - - _void - setLanguage(locale.name()); - - - Structure - setLanguage - Set document language. - _void - @@ -11120,6 +11197,39 @@ return rootNodeReference; Set document language. _void + + + + + + + + + + locale + _QLocale + Locale, from which is language determined + + + Parameters + + _void + + + + + + Code + + _void + setLanguage(locale.name()); + + + Structure + setLanguage + Set document language. + _void + @@ -11689,6 +11799,134 @@ return rootNodeReference; Sets page's user unit. It specifies user space unit, in multiples of 1 / 72 inch. _void + + + + + + + + + + signatureDictionary + _PDFObjectReference + Signature dictionary reference + + + + + contactInfoText + _QString + Contact info text + + + Parameters + + _void + + + + + + + + + + + + ContactInfo + DictionaryItemSimple + contactInfoText + + + + Dictionary + + + + CreateObject + updatedSignatureDictionary + _PDFObject + + + + + + Code + + _void + mergeTo(signatureDictionary, updatedSignatureDictionary); + + + Structure + setSignatureContactInfo + Sets signature contact info field. + _void + + + + + + + + + + + signatureDictionary + _PDFObjectReference + Signature dictionary reference + + + + + reasonText + _QString + Reason text + + + Parameters + + _void + + + + + + + + + + + + Reason + DictionaryItemSimple + reasonText + + + + Dictionary + + + + CreateObject + updatedSignatureDictionary + _PDFObject + + + + + + Code + + _void + mergeTo(signatureDictionary, updatedSignatureDictionary); + + + Structure + setSignatureReason + Sets signature reason field. + _void + @@ -11769,7 +12007,7 @@ return rootNodeReference; Code _void - m_storage.updateTrailerDictionary(qMove(trailerDictionary)); + m_storage.updateTrailerDictionary(qMove(trailerDictionary)); updateDocumentInfo(qMove(updatedInfoDictionary)); From b46ff4f63cc4782d6768549ffac8fae7e27ff81c Mon Sep 17 00:00:00 2001 From: Jakub Melka Date: Sun, 29 May 2022 17:27:58 +0200 Subject: [PATCH 39/39] Signature plugin: visible digital signatures --- Pdf4QtLib/sources/pdfdocumentbuilder.cpp | 56 ++- Pdf4QtLib/sources/pdfdocumentbuilder.h | 23 +- Pdf4QtLib/sources/pdfpagecontentelements.cpp | 15 + Pdf4QtLib/sources/pdfpagecontentelements.h | 3 + .../SignaturePlugin/signatureplugin.cpp | 49 ++- generated_code_definition.xml | 351 +++++++++++++----- 6 files changed, 380 insertions(+), 117 deletions(-) diff --git a/Pdf4QtLib/sources/pdfdocumentbuilder.cpp b/Pdf4QtLib/sources/pdfdocumentbuilder.cpp index 94208c8..13266f1 100644 --- a/Pdf4QtLib/sources/pdfdocumentbuilder.cpp +++ b/Pdf4QtLib/sources/pdfdocumentbuilder.cpp @@ -4567,6 +4567,46 @@ PDFObjectReference PDFDocumentBuilder::createFormFieldSignature(QString fieldNam } +void PDFDocumentBuilder::createFormFieldWidget(PDFObjectReference formField, + PDFObjectReference page, + PDFObjectReference appearanceStream, + QRectF rect) +{ + PDFObjectFactory objectBuilder; + + objectBuilder.beginDictionary(); + objectBuilder.beginDictionaryItem("Type"); + objectBuilder << WrapName("Annot"); + objectBuilder.endDictionaryItem(); + objectBuilder.beginDictionaryItem("Subtype"); + objectBuilder << WrapName("Widget"); + objectBuilder.endDictionaryItem(); + objectBuilder.beginDictionaryItem("P"); + objectBuilder << page; + objectBuilder.endDictionaryItem(); + objectBuilder.beginDictionaryItem("Rect"); + objectBuilder << rect; + objectBuilder.endDictionaryItem(); + objectBuilder.beginDictionaryItem("AP"); + objectBuilder.beginDictionary(); + objectBuilder.beginDictionaryItem("N"); + objectBuilder << appearanceStream; + objectBuilder.endDictionaryItem(); + objectBuilder.endDictionary(); + objectBuilder.endDictionaryItem(); + objectBuilder.endDictionary(); + PDFObject widgetObject = objectBuilder.takeObject(); + objectBuilder.beginDictionary(); + objectBuilder.beginDictionaryItem("Annots"); + objectBuilder << std::array{ formField }; + objectBuilder.endDictionaryItem(); + objectBuilder.endDictionary(); + PDFObject pageObject = objectBuilder.takeObject(); + mergeTo(formField, widgetObject); + appendTo(page, pageObject); +} + + void PDFDocumentBuilder::createInvisibleFormFieldWidget(PDFObjectReference formField, PDFObjectReference page) { @@ -5131,6 +5171,14 @@ void PDFDocumentBuilder::setFormFieldValue(PDFObjectReference formField, } +void PDFDocumentBuilder::setLanguage(QLocale locale) +{ + PDFObjectFactory objectBuilder; + + setLanguage(locale.name()); +} + + void PDFDocumentBuilder::setLanguage(QString language) { PDFObjectFactory objectBuilder; @@ -5145,14 +5193,6 @@ void PDFDocumentBuilder::setLanguage(QString language) } -void PDFDocumentBuilder::setLanguage(QLocale locale) -{ - PDFObjectFactory objectBuilder; - - setLanguage(locale.name()); -} - - void PDFDocumentBuilder::setOutline(PDFObjectReference outline) { PDFObjectFactory objectBuilder; diff --git a/Pdf4QtLib/sources/pdfdocumentbuilder.h b/Pdf4QtLib/sources/pdfdocumentbuilder.h index 32c1a77..a7fe950 100644 --- a/Pdf4QtLib/sources/pdfdocumentbuilder.h +++ b/Pdf4QtLib/sources/pdfdocumentbuilder.h @@ -92,7 +92,7 @@ struct WrapEmptyArray { }; /// Factory for creating various PDF objects, such as simple objects, /// dictionaries, arrays etc. -class PDFObjectFactory +class PDF4QTLIBSHARED_EXPORT PDFObjectFactory { public: inline explicit PDFObjectFactory() = default; @@ -1235,6 +1235,17 @@ public: PDFObjectReference signatureValue); + /// Creates visible form field widget without contents. + /// \param formField Form field reference + /// \param page Page reference + /// \param appearanceStream Appearance stream + /// \param rect Widget rectangle + void createFormFieldWidget(PDFObjectReference formField, + PDFObjectReference page, + PDFObjectReference appearanceStream, + QRectF rect); + + /// /// \param formField Form field reference /// \param page Page reference @@ -1457,6 +1468,11 @@ public: PDFObject value); + /// Set document language. + /// \param locale Locale, from which is language determined + void setLanguage(QLocale locale); + + /// Set document language. /// \param language Document language. It should be a language identifier, as defined in ISO 639 /// and ISO 3166. For example, "en-US", where first two letter means language code (en = @@ -1464,11 +1480,6 @@ public: void setLanguage(QString language); - /// Set document language. - /// \param locale Locale, from which is language determined - void setLanguage(QLocale locale); - - /// Set document outline. /// \param outline Document outline root void setOutline(PDFObjectReference outline); diff --git a/Pdf4QtLib/sources/pdfpagecontentelements.cpp b/Pdf4QtLib/sources/pdfpagecontentelements.cpp index 41023d1..48d8607 100644 --- a/Pdf4QtLib/sources/pdfpagecontentelements.cpp +++ b/Pdf4QtLib/sources/pdfpagecontentelements.cpp @@ -889,6 +889,21 @@ std::set PDFPageContentScene::getPageIndices() const return result; } +QRectF PDFPageContentScene::getBoundingBox(PDFInteger pageIndex) const +{ + QRectF rect; + + for (const auto& element : m_elements) + { + if (element->getPageIndex() == pageIndex) + { + rect = rect.united(element->getBoundingBox()); + } + } + + return rect; +} + void PDFPageContentScene::setSelectedElementIds(const std::set& selectedElementIds) { m_manipulator.selectNew(selectedElementIds); diff --git a/Pdf4QtLib/sources/pdfpagecontentelements.h b/Pdf4QtLib/sources/pdfpagecontentelements.h index 7785417..4e5143f 100644 --- a/Pdf4QtLib/sources/pdfpagecontentelements.h +++ b/Pdf4QtLib/sources/pdfpagecontentelements.h @@ -507,6 +507,9 @@ public: /// Returns set of involved pages std::set getPageIndices() const; + /// Returns bounding box of elements on page + QRectF getBoundingBox(PDFInteger pageIndex) const; + /// Set selected items void setSelectedElementIds(const std::set& selectedElementIds); diff --git a/Pdf4QtViewerPlugins/SignaturePlugin/signatureplugin.cpp b/Pdf4QtViewerPlugins/SignaturePlugin/signatureplugin.cpp index f6e429a..16f1234 100644 --- a/Pdf4QtViewerPlugins/SignaturePlugin/signatureplugin.cpp +++ b/Pdf4QtViewerPlugins/SignaturePlugin/signatureplugin.cpp @@ -346,15 +346,62 @@ void SignaturePlugin::onSignDigitally() pdf::PDFObjectReference formField = builder.createFormFieldSignature(signatureName, { }, signatureDictionary); builder.createAcroForm({ formField }); + const pdf::PDFCatalog* catalog = m_document->getCatalog(); if (dialog.getSignMethod() == SignDialog::SignDigitallyInvisible) { - const pdf::PDFCatalog* catalog = m_document->getCatalog(); if (catalog->getPageCount() > 0) { const pdf::PDFObjectReference pageReference = catalog->getPage(0)->getPageReference(); builder.createInvisibleFormFieldWidget(formField, pageReference); } } + else if (dialog.getSignMethod() == SignDialog::SignDigitally) + { + Q_ASSERT(!m_scene.isEmpty()); + const pdf::PDFInteger pageIndex = *m_scene.getPageIndices().begin(); + const pdf::PDFPage* page = catalog->getPage(pageIndex); + + pdf::PDFContentStreamBuilder contentBuilder(page->getMediaBox().size(), pdf::PDFContentStreamBuilder::CoordinateSystem::PDF); + QPainter* painter = contentBuilder.begin(); + QList errors; + pdf::PDFTextLayoutGetter nullGetter(nullptr, pageIndex); + m_scene.drawPage(painter, pageIndex, nullptr, nullGetter, QMatrix(), errors); + pdf::PDFContentStreamBuilder::ContentStream contentStream = contentBuilder.end(painter); + + QRectF boundingRect = m_scene.getBoundingBox(pageIndex); + std::vector copiedObjects = builder.copyFrom({ contentStream.resources, contentStream.contents }, contentStream.document.getStorage(), true); + Q_ASSERT(copiedObjects.size() == 2); + + pdf::PDFObjectReference resourcesReference = copiedObjects[0].getReference(); + pdf::PDFObjectReference formReference = copiedObjects[1].getReference(); + + // Create form object + pdf::PDFObjectFactory formFactory; + + formFactory.beginDictionary(); + + formFactory.beginDictionaryItem("Type"); + formFactory << pdf::WrapName("XObject"); + formFactory.endDictionaryItem(); + + formFactory.beginDictionaryItem("Subtype"); + formFactory << pdf::WrapName("Form"); + formFactory.endDictionaryItem(); + + formFactory.beginDictionaryItem("BBox"); + formFactory << boundingRect; + formFactory.endDictionaryItem(); + + formFactory.beginDictionaryItem("Resources"); + formFactory << resourcesReference; + formFactory.endDictionaryItem(); + + formFactory.endDictionary(); + + builder.mergeTo(formReference, formFactory.takeObject()); + + builder.createFormFieldWidget(formField, page->getPageReference(), formReference, boundingRect); + } QString reasonText = dialog.getReasonText(); if (!reasonText.isEmpty()) diff --git a/generated_code_definition.xml b/generated_code_definition.xml index b5b85d4..fa6ec83 100644 --- a/generated_code_definition.xml +++ b/generated_code_definition.xml @@ -103,7 +103,7 @@ Code _void - appendTo(getPageTreeRoot(), updatedTreeRoot); + appendTo(getPageTreeRoot(), updatedTreeRoot); return pageReference; @@ -182,7 +182,7 @@ return pageReference; Code _void - setCatalogAcroForm(acroForm); + setCatalogAcroForm(acroForm); return acroForm; @@ -2386,8 +2386,8 @@ return acroForm; Code _void - appendTo(page, pageAnnots); -updateAnnotationAppearanceStreams(annotationObject); + appendTo(page, pageAnnots); +updateAnnotationAppearanceStreams(annotationObject); return annotationObject; @@ -2610,8 +2610,8 @@ return annotationObject; Code _void - appendTo(page, pageAnnots); -updateAnnotationAppearanceStreams(annotationObject); + appendTo(page, pageAnnots); +updateAnnotationAppearanceStreams(annotationObject); return annotationObject; @@ -2785,8 +2785,8 @@ return annotationObject; Code _void - appendTo(page, pageAnnots); -updateAnnotationAppearanceStreams(annotationReference); + appendTo(page, pageAnnots); +updateAnnotationAppearanceStreams(annotationReference); return annotationReference; @@ -3067,8 +3067,8 @@ return annotationReference; Code _void - appendTo(page, pageAnnots); -updateAnnotationAppearanceStreams(annotationObject); + appendTo(page, pageAnnots); +updateAnnotationAppearanceStreams(annotationObject); return annotationObject; @@ -3270,8 +3270,8 @@ return annotationObject; Code _void - appendTo(page, pageAnnots); -updateAnnotationAppearanceStreams(annotationObject); + appendTo(page, pageAnnots); +updateAnnotationAppearanceStreams(annotationObject); return annotationObject; @@ -3545,8 +3545,8 @@ return annotationObject; Code _void - appendTo(page, pageAnnots); -updateAnnotationAppearanceStreams(annotationObject); + appendTo(page, pageAnnots); +updateAnnotationAppearanceStreams(annotationObject); return annotationObject; @@ -3749,8 +3749,8 @@ return annotationObject; Code _void - appendTo(page, pageAnnots); -updateAnnotationAppearanceStreams(annotationObject); + appendTo(page, pageAnnots); +updateAnnotationAppearanceStreams(annotationObject); return annotationObject; @@ -3904,8 +3904,8 @@ return annotationObject; Code _void - appendTo(page, pageAnnots); -updateAnnotationAppearanceStreams(annotationObject); + appendTo(page, pageAnnots); +updateAnnotationAppearanceStreams(annotationObject); return annotationObject; @@ -4044,8 +4044,8 @@ return annotationObject; Code _void - appendTo(page, pageAnnots); -updateAnnotationAppearanceStreams(annotationObject); + appendTo(page, pageAnnots); +updateAnnotationAppearanceStreams(annotationObject); return annotationObject; @@ -4269,8 +4269,8 @@ return annotationObject; Code _void - appendTo(page, pageAnnots); -updateAnnotationAppearanceStreams(annotationObject); + appendTo(page, pageAnnots); +updateAnnotationAppearanceStreams(annotationObject); return annotationObject; @@ -4486,8 +4486,8 @@ return annotationObject; Code _void - appendTo(page, pageAnnots); -updateAnnotationAppearanceStreams(annotationObject); + appendTo(page, pageAnnots); +updateAnnotationAppearanceStreams(annotationObject); return annotationObject; @@ -4768,8 +4768,8 @@ return annotationObject; Code _void - appendTo(page, pageAnnots); -updateAnnotationAppearanceStreams(annotationObject); + appendTo(page, pageAnnots); +updateAnnotationAppearanceStreams(annotationObject); return annotationObject; @@ -5120,8 +5120,8 @@ return annotationObject; Code _void - appendTo(page, pageAnnots); -updateAnnotationAppearanceStreams(annotationObject); + appendTo(page, pageAnnots); +updateAnnotationAppearanceStreams(annotationObject); return annotationObject; @@ -5321,8 +5321,8 @@ return annotationObject; Code _void - appendTo(page, pageAnnots); -updateAnnotationAppearanceStreams(annotationReference); + appendTo(page, pageAnnots); +updateAnnotationAppearanceStreams(annotationReference); return annotationReference; @@ -5552,8 +5552,8 @@ return annotationReference; Code _void - appendTo(page, pageAnnots); -updateAnnotationAppearanceStreams(annotationObject); + appendTo(page, pageAnnots); +updateAnnotationAppearanceStreams(annotationObject); return annotationObject; @@ -5812,8 +5812,8 @@ return annotationObject; Code _void - appendTo(page, pageAnnots); -updateAnnotationAppearanceStreams(annotationObject); + appendTo(page, pageAnnots); +updateAnnotationAppearanceStreams(annotationObject); return annotationObject; @@ -5951,8 +5951,8 @@ return annotationObject; Code _void - mergeTo(parentAnnotation, upgradedParentAnnotation); -updateAnnotationAppearanceStreams(popupAnnotation); + mergeTo(parentAnnotation, upgradedParentAnnotation); +updateAnnotationAppearanceStreams(popupAnnotation); return popupAnnotation; @@ -6106,8 +6106,8 @@ return popupAnnotation; Code _void - appendTo(page, pageAnnots); -updateAnnotationAppearanceStreams(annotationObject); + appendTo(page, pageAnnots); +updateAnnotationAppearanceStreams(annotationObject); return annotationObject; @@ -6246,8 +6246,8 @@ return annotationObject; Code _void - appendTo(page, pageAnnots); -updateAnnotationAppearanceStreams(annotationObject); + appendTo(page, pageAnnots); +updateAnnotationAppearanceStreams(annotationObject); return annotationObject; @@ -6470,8 +6470,8 @@ return annotationObject; Code _void - appendTo(page, pageAnnots); -updateAnnotationAppearanceStreams(annotationObject); + appendTo(page, pageAnnots); +updateAnnotationAppearanceStreams(annotationObject); return annotationObject; @@ -6625,8 +6625,8 @@ return annotationObject; Code _void - appendTo(page, pageAnnots); -updateAnnotationAppearanceStreams(annotationObject); + appendTo(page, pageAnnots); +updateAnnotationAppearanceStreams(annotationObject); return annotationObject; @@ -6765,8 +6765,8 @@ return annotationObject; Code _void - appendTo(page, pageAnnots); -updateAnnotationAppearanceStreams(annotationObject); + appendTo(page, pageAnnots); +updateAnnotationAppearanceStreams(annotationObject); return annotationObject; @@ -6969,8 +6969,8 @@ return annotationObject; Code _void - appendTo(page, pageAnnots); -updateAnnotationAppearanceStreams(annotationObject); + appendTo(page, pageAnnots); +updateAnnotationAppearanceStreams(annotationObject); return annotationObject; @@ -7158,8 +7158,8 @@ return annotationObject; Code _void - appendTo(page, pageAnnots); -updateAnnotationAppearanceStreams(annotationObject); + appendTo(page, pageAnnots); +updateAnnotationAppearanceStreams(annotationObject); return annotationObject; @@ -7362,8 +7362,8 @@ return annotationObject; Code _void - appendTo(page, pageAnnots); -updateAnnotationAppearanceStreams(annotationObject); + appendTo(page, pageAnnots); +updateAnnotationAppearanceStreams(annotationObject); return annotationObject; @@ -7517,8 +7517,8 @@ return annotationObject; Code _void - appendTo(page, pageAnnots); -updateAnnotationAppearanceStreams(annotationObject); + appendTo(page, pageAnnots); +updateAnnotationAppearanceStreams(annotationObject); return annotationObject; @@ -7657,8 +7657,8 @@ return annotationObject; Code _void - appendTo(page, pageAnnots); -updateAnnotationAppearanceStreams(annotationObject); + appendTo(page, pageAnnots); +updateAnnotationAppearanceStreams(annotationObject); return annotationObject; @@ -7899,9 +7899,9 @@ return annotationObject; Code _void - mergeTo(annotationObject, updateAnnotationPopup); -appendTo(page, pageAnnots); -updateAnnotationAppearanceStreams(annotationObject); + mergeTo(annotationObject, updateAnnotationPopup); +appendTo(page, pageAnnots); +updateAnnotationAppearanceStreams(annotationObject); return annotationObject; @@ -8055,8 +8055,8 @@ return annotationObject; Code _void - appendTo(page, pageAnnots); -updateAnnotationAppearanceStreams(annotationObject); + appendTo(page, pageAnnots); +updateAnnotationAppearanceStreams(annotationObject); return annotationObject; @@ -8195,8 +8195,8 @@ return annotationObject; Code _void - appendTo(page, pageAnnots); -updateAnnotationAppearanceStreams(annotationObject); + appendTo(page, pageAnnots); +updateAnnotationAppearanceStreams(annotationObject); return annotationObject; @@ -8399,8 +8399,8 @@ return annotationObject; Code _void - appendTo(page, pageAnnots); -updateAnnotationAppearanceStreams(annotationObject); + appendTo(page, pageAnnots); +updateAnnotationAppearanceStreams(annotationObject); return annotationObject; @@ -8716,8 +8716,8 @@ return annotationObject; Code _void - mergeTo(rootNodeReference, updatedRootNode); -mergeTo(getCatalogReference(), updatedCatalog); + mergeTo(rootNodeReference, updatedRootNode); +mergeTo(getCatalogReference(), updatedCatalog); return rootNodeReference; @@ -8946,6 +8946,153 @@ return rootNodeReference; Creates form field of type signature. _PDFObjectReference + + + + + + + + + + formField + _PDFObjectReference + Form field reference + + + + + page + _PDFObjectReference + Page reference + + + + + appearanceStream + _PDFObjectReference + Appearance stream + + + + + rect + _QRectF + Widget rectangle + + + Parameters + + _void + + + + + + + + + + + + Type + DictionaryItemSimple + WrapName("Annot") + + + + + Subtype + DictionaryItemSimple + WrapName("Widget") + + + + + P + DictionaryItemSimple + page + + + + + Rect + DictionaryItemSimple + rect + + + + + + + + + + + N + DictionaryItemSimple + appearanceStream + + + + Dictionary + + + + AP + DictionaryItemComplex + + + + + Dictionary + + + + CreateObject + widgetObject + _PDFObject + + + + + + + + + + + + Annots + DictionaryItemSimple + std::array{ formField } + + + + Dictionary + + + + CreateObject + pageObject + _PDFObject + + + + + + Code + + _void + mergeTo(formField, widgetObject); +appendTo(page, pageObject); + + + Structure + createFormFieldWidget + Creates visible form field widget without contents. + _void + @@ -9047,7 +9194,7 @@ return rootNodeReference; Code _void - mergeTo(formField, widgetObject); + mergeTo(formField, widgetObject); appendTo(page, pageObject); @@ -11140,6 +11287,39 @@ appendTo(page, pageObject); Sets form field value. Value must be correct for this form field, no checking is performed. Also, if you use this function, annotation widgets, which are attached to this form field, should also be updated (for example, appearance state and sometimes appearance streams). _void + + + + + + + + + + locale + _QLocale + Locale, from which is language determined + + + Parameters + + _void + + + + + + Code + + _void + setLanguage(locale.name()); + + + Structure + setLanguage + Set document language. + _void + @@ -11197,39 +11377,6 @@ appendTo(page, pageObject); Set document language. _void - - - - - - - - - - locale - _QLocale - Locale, from which is language determined - - - Parameters - - _void - - - - - - Code - - _void - setLanguage(locale.name()); - - - Structure - setLanguage - Set document language. - _void - @@ -12007,7 +12154,7 @@ appendTo(page, pageObject); Code _void - m_storage.updateTrailerDictionary(qMove(trailerDictionary)); + m_storage.updateTrailerDictionary(qMove(trailerDictionary)); updateDocumentInfo(qMove(updatedInfoDictionary));