// 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 "pdfutils.h" #include "pdfpagecontenteditorwidget.h" #include "pdfpagecontenteditorstylesettings.h" #include "pdfdocumentbuilder.h" #include #include #include #include namespace pdfplugin { SignaturePlugin::SignaturePlugin() : pdf::PDFPlugin(nullptr), m_actions({ }), m_tools({ }), m_editorWidget(nullptr), m_scene(nullptr), m_sceneSelectionChangeEnabled(true) { } void SignaturePlugin::setWidget(pdf::PDFWidget* widget) { Q_ASSERT(!m_widget); BaseClass::setWidget(widget); 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"); createFreehandCurveAction->setObjectName("signaturetool_createFreehandCurveAction"); 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"); 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); createRoundedRectangleAction->setCheckable(true); createHorizontalLineAction->setCheckable(true); createVerticalLineAction->setCheckable(true); createLineAction->setCheckable(true); 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; 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[SignElectronically] = signElectronicallyAction; m_actions[SignDigitally] = signDigitallyAction; m_actions[Certificates] = 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[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::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) { toolManager->addTool(tool); connect(tool, &pdf::PDFWidgetTool::toolActivityChanged, this, &SignaturePlugin::onToolActivityChanged); } 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(&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); connect(signElectronicallyAction, &QAction::triggered, this, &SignaturePlugin::onSignElectronically); updateActions(); } void SignaturePlugin::setDocument(const pdf::PDFModifiedDocument& document) { BaseClass::setDocument(document); if (document.hasReset()) { setActive(false); updateActions(); } } std::vector SignaturePlugin::getActions() const { std::vector result; result.push_back(m_actions[Activate]); result.push_back(m_actions[SignElectronically]); result.push_back(m_actions[SignDigitally]); result.push_back(m_actions[Certificates]); return result; } void SignaturePlugin::onSceneChanged(bool graphicsOnly) { if (!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()); } 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::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::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.drawElements(painter, pageIndex, nullGetter, QMatrix(), nullptr, errors); pageContentStreamBuilder.end(painter); modifier.markPageContentsChanged(); } m_scene.clear(); 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())) { 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) { // 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(); } else { updateDockWidget(); } 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(isSceneNonempty); // Sign actions QAction* signElectronicallyAction = m_actions[SignElectronically]; signElectronicallyAction->setEnabled(isSceneNonempty); QAction* signDigitallyAction = m_actions[SignDigitally]; signDigitallyAction->setEnabled(isSceneNonempty); } void SignaturePlugin::updateGraphics() { if (m_widget) { m_widget->getDrawWidget()->getWidget()->update(); } } 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); 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")); 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); } 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); } }