diff --git a/PdfForQtLib/sources/pdfannotation.cpp b/PdfForQtLib/sources/pdfannotation.cpp index 2323f40..9820bfb 100644 --- a/PdfForQtLib/sources/pdfannotation.cpp +++ b/PdfForQtLib/sources/pdfannotation.cpp @@ -25,6 +25,7 @@ #include "pdfpagecontentprocessor.h" #include "pdfparser.h" #include "pdfdrawwidget.h" +#include "pdfform.h" #include #include @@ -897,6 +898,7 @@ PDFAnnotationManager::PDFAnnotationManager(PDFFontCache* fontCache, m_fontCache(fontCache), m_cmsManager(cmsManager), m_optionalActivity(optionalActivity), + m_formManager(nullptr), m_meshQualitySettings(meshQualitySettings), m_features(features), m_target(target) @@ -1081,6 +1083,22 @@ void PDFAnnotationManager::drawPage(QPainter* painter, if (!oc.isValid() || !pdfPainter.isContentSuppressedByOC(oc)) { pdfPainter.processForm(AA, formBoundingBox, resources, transparencyGroup, content); + + // Is it a form field? + if (m_formManager && annotation.annotation->getType() == AnnotationType::Widget) + { + const PDFFormManager::FormAppearanceFlags flags = m_formManager->getAppearanceFlags(); + if (flags.testFlag(PDFFormManager::HighlightFields) || flags.testFlag(PDFFormManager::HighlightRequiredFields)) + { + const PDFFormField* formField = m_formManager->getFormFieldForWidget(annotation.annotation->getSelfReference()); + if (!formField) + { + continue; + } + + s + } + } } } catch (PDFException exception) @@ -1168,6 +1186,16 @@ bool PDFAnnotationManager::hasAnyPageAnnotation(const std::vector& p return std::any_of(pageIndices.cbegin(), pageIndices.cend(), std::bind(&PDFAnnotationManager::hasAnnotation, this, std::placeholders::_1)); } +PDFFormManager* PDFAnnotationManager::getFormManager() const +{ + return m_formManager; +} + +void PDFAnnotationManager::setFormManager(PDFFormManager* formManager) +{ + m_formManager = formManager; +} + PDFRenderer::Features PDFAnnotationManager::getFeatures() const { return m_features; @@ -1336,7 +1364,8 @@ void PDFWidgetAnnotationManager::updateFromMouseEvent(QMouseEvent* event) } const PDFAction* linkAction = nullptr; - if (pageAnnotation.annotation->getType() == AnnotationType::Link) + const AnnotationType annotationType = pageAnnotation.annotation->getType(); + if (annotationType == AnnotationType::Link) { const PDFLinkAnnotation* linkAnnotation = dynamic_cast(pageAnnotation.annotation.data()); Q_ASSERT(linkAnnotation); @@ -1350,6 +1379,10 @@ void PDFWidgetAnnotationManager::updateFromMouseEvent(QMouseEvent* event) linkAction = linkAnnotation->getAction(); } } + if (annotationType == AnnotationType::Widget) + { + m_cursor = QCursor(Qt::ArrowCursor); + } // Generate popup window if (event->type() == QEvent::MouseButtonPress && event->button() == Qt::LeftButton) diff --git a/PdfForQtLib/sources/pdfannotation.h b/PdfForQtLib/sources/pdfannotation.h index dd73f31..3c39dfb 100644 --- a/PdfForQtLib/sources/pdfannotation.h +++ b/PdfForQtLib/sources/pdfannotation.h @@ -43,6 +43,7 @@ class PDFWidget; class PDFObjectStorage; class PDFDrawWidgetProxy; class PDFFontCache; +class PDFFormManager; class PDFOptionalContentActivity; using TextAlignment = Qt::Alignment; @@ -1301,6 +1302,9 @@ public: PDFRenderer::Features getFeatures() const; void setFeatures(PDFRenderer::Features features); + PDFFormManager* getFormManager() const; + void setFormManager(PDFFormManager* formManager); + protected: struct PageAnnotation { @@ -1368,6 +1372,7 @@ protected: PDFFontCache* m_fontCache; const PDFCMSManager* m_cmsManager; const PDFOptionalContentActivity* m_optionalActivity; + PDFFormManager* m_formManager; PDFMeshQualitySettings m_meshQualitySettings; PDFRenderer::Features m_features; diff --git a/PdfForQtLib/sources/pdfcatalog.cpp b/PdfForQtLib/sources/pdfcatalog.cpp index d9517cc..d4bf9de 100644 --- a/PdfForQtLib/sources/pdfcatalog.cpp +++ b/PdfForQtLib/sources/pdfcatalog.cpp @@ -168,6 +168,8 @@ PDFCatalog PDFCatalog::parse(const PDFObject& catalog, const PDFDocument* docume catalogObject.m_baseURI = loader.readStringFromDictionary(URIDictionary, "Base"); } + catalogObject.m_formObject = catalogDictionary->get("AcroForm"); + return catalogObject; } diff --git a/PdfForQtLib/sources/pdfcatalog.h b/PdfForQtLib/sources/pdfcatalog.h index 2e66c4e..c228cd1 100644 --- a/PdfForQtLib/sources/pdfcatalog.h +++ b/PdfForQtLib/sources/pdfcatalog.h @@ -235,6 +235,7 @@ public: PageMode getPageMode() const { return m_pageMode; } const QByteArray& getBaseURI() const { return m_baseURI; } const std::map& getEmbeddedFiles() const { return m_embeddedFiles; } + const PDFObject& getFormObject() const { return m_formObject; } /// Returns destination using the key. If destination with the key is not found, /// then nullptr is returned. @@ -257,6 +258,7 @@ private: PageLayout m_pageLayout = PageLayout::SinglePage; PageMode m_pageMode = PageMode::UseNone; QByteArray m_baseURI; + PDFObject m_formObject; // Maps from Names dictionary std::map m_destinations; diff --git a/PdfForQtLib/sources/pdfform.cpp b/PdfForQtLib/sources/pdfform.cpp index 25d63e4..66cffbe 100644 --- a/PdfForQtLib/sources/pdfform.cpp +++ b/PdfForQtLib/sources/pdfform.cpp @@ -17,6 +17,7 @@ #include "pdfform.h" #include "pdfdocument.h" +#include "pdfdrawspacecontroller.h" namespace pdf { @@ -55,6 +56,19 @@ PDFForm PDFForm::parse(const PDFObjectStorage* storage, PDFObject object) return form; } +void PDFFormField::fillWidgetToFormFieldMapping(PDFWidgetToFormFieldMapping& mapping) +{ + for (const auto& childField : m_childFields) + { + childField->fillWidgetToFormFieldMapping(mapping); + } + + for (const PDFFormWidget& formWidget : m_widgets) + { + mapping[formWidget.getWidget()] = formWidget.getParent(); + } +} + PDFFormFieldPointer PDFFormField::parse(const PDFObjectStorage* storage, PDFObjectReference reference, PDFFormField* parentField) { PDFFormFieldPointer result; @@ -253,4 +267,77 @@ PDFFormFieldButton::ButtonType PDFFormFieldButton::getButtonType() const return ButtonType::CheckBox; } +PDFFormManager::PDFFormManager(PDFDrawWidgetProxy* proxy, QObject* parent) : + BaseClass(parent), + m_proxy(proxy), + m_annotationManager(nullptr), + m_document(nullptr), + m_flags(getDefaultApperanceFlags()) +{ + Q_ASSERT(proxy); +} + +PDFFormManager::~PDFFormManager() +{ + +} + +PDFAnnotationManager* PDFFormManager::getAnnotationManager() const +{ + return m_annotationManager; +} + +void PDFFormManager::setAnnotationManager(PDFAnnotationManager* annotationManager) +{ + m_annotationManager = annotationManager; +} + +const PDFDocument* PDFFormManager::getDocument() const +{ + return m_document; +} + +void PDFFormManager::setDocument(const PDFDocument* document) +{ + if (m_document != document) + { + m_document = document; + + if (m_document) + { + m_form = PDFForm::parse(&m_document->getStorage(), m_document->getCatalog()->getFormObject()); + } + else + { + // Clean the form + m_form = PDFForm(); + } + + updateWidgetToFormFieldMapping(); + } +} + +PDFFormManager::FormAppearanceFlags PDFFormManager::getAppearanceFlags() const +{ + return m_flags; +} + +void PDFFormManager::setAppearanceFlags(FormAppearanceFlags flags) +{ + m_flags = flags; +} + +void PDFFormManager::updateWidgetToFormFieldMapping() +{ + m_widgetToFormField.clear(); + + if (hasAcroForm()) + { + for (const PDFFormFieldPointer& formFieldPtr : m_form.getFormFields()) + { + formFieldPtr->fillWidgetToFormFieldMapping(m_widgetToFormField); + } + } +} + } // namespace pdf diff --git a/PdfForQtLib/sources/pdfform.h b/PdfForQtLib/sources/pdfform.h index be05f07..18a3900 100644 --- a/PdfForQtLib/sources/pdfform.h +++ b/PdfForQtLib/sources/pdfform.h @@ -31,6 +31,7 @@ class PDFFormField; using PDFFormFieldPointer = QSharedPointer; using PDFFormFields = std::vector; +using PDFWidgetToFormFieldMapping = std::map; /// A simple proxy to the widget annotation class PDFFormWidget @@ -182,6 +183,10 @@ public: const PDFObject& getValue() const { return m_value; } const PDFObject& getDefaultValue() const { return m_defaultValue; } + /// Fills widget to form field mapping + /// \param mapping Form field mapping + void fillWidgetToFormFieldMapping(PDFWidgetToFormFieldMapping& mapping); + /// Parses form field from the object reference. If some error occurs /// then null pointer is returned, no exception is thrown. /// \param storage Storage @@ -311,6 +316,7 @@ public: }; Q_DECLARE_FLAGS(SignatureFlags, SignatureFlag) + FormType getFormType() const { return m_formType; } const PDFFormFields& getFormFields() const { return m_formFields; } bool isAppearanceUpdateNeeded() const { return m_needAppearances; } SignatureFlags getSignatureFlags() const { return m_signatureFlags; } @@ -338,6 +344,58 @@ private: PDFObject m_xfa; }; +/// Form manager. Manages all form widgets functionality - triggers actions, +/// edits fields, updates annotation appearances, etc. Valid pointer to annotation +/// manager is requirement. +class PDFFORQTLIBSHARED_EXPORT PDFFormManager : public QObject +{ + Q_OBJECT + +private: + using BaseClass = QObject; + +public: + explicit PDFFormManager(PDFDrawWidgetProxy* proxy, QObject* parent); + virtual ~PDFFormManager() override; + + enum FormAppearanceFlag + { + None = 0x0000, + HighlightFields = 0x0001, + HighlightRequiredFields = 0x0002, + }; + Q_DECLARE_FLAGS(FormAppearanceFlags, FormAppearanceFlag) + + bool hasAcroForm() const { return m_form.getFormType() == PDFForm::FormType::AcroForm; } + + /// Returns form field for widget. If widget doesn't have attached form field, + /// then nullptr is returned. + /// \param widget Widget annotation + const PDFFormField* getFormFieldForWidget(PDFObjectReference widget) const; + + PDFAnnotationManager* getAnnotationManager() const; + void setAnnotationManager(PDFAnnotationManager* annotationManager); + + const PDFDocument* getDocument() const; + void setDocument(const PDFDocument* document); + + /// Returns default form apperance flags + static constexpr FormAppearanceFlags getDefaultApperanceFlags() { return HighlightFields | HighlightRequiredFields; } + + FormAppearanceFlags getAppearanceFlags() const; + void setAppearanceFlags(FormAppearanceFlags flags); + +private: + void updateWidgetToFormFieldMapping(); + + PDFDrawWidgetProxy* m_proxy; + PDFAnnotationManager* m_annotationManager; + const PDFDocument* m_document; + FormAppearanceFlags m_flags; + PDFForm m_form; + PDFWidgetToFormFieldMapping m_widgetToFormField; +}; + } // namespace pdf #endif // PDFFORM_H diff --git a/PdfForQtViewer/pdfforqtviewer.qrc b/PdfForQtViewer/pdfforqtviewer.qrc index 9fd3a6f..3154736 100644 --- a/PdfForQtViewer/pdfforqtviewer.qrc +++ b/PdfForQtViewer/pdfforqtviewer.qrc @@ -43,5 +43,6 @@ resources/magnifier.svg resources/screenshot-tool.svg resources/extract-image.svg + resources/form-settings.svg diff --git a/PdfForQtViewer/pdfviewermainwindow.cpp b/PdfForQtViewer/pdfviewermainwindow.cpp index 8e10ad2..5afd529 100644 --- a/PdfForQtViewer/pdfviewermainwindow.cpp +++ b/PdfForQtViewer/pdfviewermainwindow.cpp @@ -90,6 +90,7 @@ PDFViewerMainWindow::PDFViewerMainWindow(QWidget* parent) : m_isChangingProgressStep(false), m_toolManager(nullptr), m_annotationManager(nullptr), + m_formManager(nullptr), m_textToSpeech(new PDFTextToSpeech(this)) { ui->setupUi(this); @@ -268,6 +269,11 @@ PDFViewerMainWindow::PDFViewerMainWindow(QWidget* parent) : connect(m_annotationManager, &pdf::PDFWidgetAnnotationManager::actionTriggered, this, &PDFViewerMainWindow::onActionTriggered); m_pdfWidget->setAnnotationManager(m_annotationManager); + m_formManager = new pdf::PDFFormManager(m_pdfWidget->getDrawWidgetProxy(), this); + m_formManager->setAnnotationManager(m_annotationManager); + m_formManager->setAppearanceFlags(m_settings->getSettings().m_formAppearanceFlags); + m_annotationManager->setFormManager(m_formManager); + connect(m_pdfWidget->getDrawWidgetProxy(), &pdf::PDFDrawWidgetProxy::drawSpaceChanged, this, &PDFViewerMainWindow::onDrawSpaceChanged); connect(m_pdfWidget->getDrawWidgetProxy(), &pdf::PDFDrawWidgetProxy::pageLayoutChanged, this, &PDFViewerMainWindow::onPageLayoutChanged); connect(m_pdfWidget, &pdf::PDFWidget::pageRenderingErrorsChanged, this, &PDFViewerMainWindow::onPageRenderingErrorsChanged, Qt::QueuedConnection); @@ -288,6 +294,9 @@ PDFViewerMainWindow::PDFViewerMainWindow(QWidget* parent) : PDFViewerMainWindow::~PDFViewerMainWindow() { + delete m_formManager; + m_formManager = nullptr; + delete m_annotationManager; m_annotationManager = nullptr; @@ -669,6 +678,11 @@ void PDFViewerMainWindow::readSettings() m_settings->readSettings(settings, m_CMSManager->getDefaultSettings()); m_CMSManager->setSettings(m_settings->getColorManagementSystemSettings()); m_textToSpeech->setSettings(m_settings); + + if (m_formManager) + { + m_formManager->setAppearanceFlags(m_settings->getSettings().m_formAppearanceFlags); + } } void PDFViewerMainWindow::readActionSettings() @@ -979,6 +993,7 @@ void PDFViewerMainWindow::setDocument(const pdf::PDFDocument* document) } m_annotationManager->setDocument(document, m_optionalContentActivity); + m_formManager->setDocument(document); m_toolManager->setDocument(document); m_textToSpeech->setDocument(document); m_pdfWidget->setDocument(document, m_optionalContentActivity); @@ -1164,6 +1179,7 @@ void PDFViewerMainWindow::on_actionOptions_triggered() m_CMSManager->setSettings(m_settings->getColorManagementSystemSettings()); m_recentFileManager->setRecentFilesLimit(dialog.getOtherSettings().maximumRecentFileCount); m_textToSpeech->setSettings(m_settings); + m_formManager->setAppearanceFlags(m_settings->getSettings().m_formAppearanceFlags); updateMagnifierToolSettings(); } } diff --git a/PdfForQtViewer/pdfviewermainwindow.h b/PdfForQtViewer/pdfviewermainwindow.h index 483ac9f..eb8d8ce 100644 --- a/PdfForQtViewer/pdfviewermainwindow.h +++ b/PdfForQtViewer/pdfviewermainwindow.h @@ -29,6 +29,7 @@ #include "pdfrecentfilemanager.h" #include "pdftexttospeech.h" #include "pdfannotation.h" +#include "pdfform.h" #include #include @@ -181,6 +182,7 @@ private: pdf::PDFToolManager* m_toolManager; pdf::PDFWidgetAnnotationManager* m_annotationManager; + pdf::PDFFormManager* m_formManager; PDFTextToSpeech* m_textToSpeech; }; diff --git a/PdfForQtViewer/pdfviewersettings.cpp b/PdfForQtViewer/pdfviewersettings.cpp index a52259e..57e43e8 100644 --- a/PdfForQtViewer/pdfviewersettings.cpp +++ b/PdfForQtViewer/pdfviewersettings.cpp @@ -80,6 +80,10 @@ void PDFViewerSettings::readSettings(QSettings& settings, const pdf::PDFCMSSetti m_settings.m_speechVolume = settings.value("speechVolume", defaultSettings.m_speechVolume).toDouble(); settings.endGroup(); + settings.beginGroup("Forms"); + m_settings.m_formAppearanceFlags = static_cast(settings.value("formAppearance", int(pdf::PDFFormManager::getDefaultApperanceFlags())).toInt()); + settings.endGroup(); + emit settingsChanged(); } @@ -127,6 +131,10 @@ void PDFViewerSettings::writeSettings(QSettings& settings) settings.setValue("speechPitch", m_settings.m_speechPitch); settings.setValue("speechVolume", m_settings.m_speechVolume); settings.endGroup(); + + settings.beginGroup("Forms"); + settings.setValue("formAppearance", int(m_settings.m_formAppearanceFlags)); + settings.endGroup(); } QString PDFViewerSettings::getDirectory() const @@ -232,7 +240,8 @@ PDFViewerSettings::Settings::Settings() : m_speechPitch(0.0), m_speechVolume(1.0), m_magnifierSize(100), - m_magnifierZoom(2.0) + m_magnifierZoom(2.0), + m_formAppearanceFlags(pdf::PDFFormManager::getDefaultApperanceFlags()) { } diff --git a/PdfForQtViewer/pdfviewersettings.h b/PdfForQtViewer/pdfviewersettings.h index 6f07132..859c571 100644 --- a/PdfForQtViewer/pdfviewersettings.h +++ b/PdfForQtViewer/pdfviewersettings.h @@ -21,6 +21,7 @@ #include "pdfrenderer.h" #include "pdfcms.h" #include "pdfexecutionpolicy.h" +#include "pdfform.h" #include @@ -75,6 +76,9 @@ public: // Magnifier tool settings int m_magnifierSize; double m_magnifierZoom; + + // Form settings + pdf::PDFFormManager::FormAppearanceFlags m_formAppearanceFlags; }; const Settings& getSettings() const { return m_settings; } diff --git a/PdfForQtViewer/pdfviewersettingsdialog.cpp b/PdfForQtViewer/pdfviewersettingsdialog.cpp index 5128b19..9261193 100644 --- a/PdfForQtViewer/pdfviewersettingsdialog.cpp +++ b/PdfForQtViewer/pdfviewersettingsdialog.cpp @@ -58,6 +58,7 @@ PDFViewerSettingsDialog::PDFViewerSettingsDialog(const PDFViewerSettings::Settin new QListWidgetItem(QIcon(":/resources/security.svg"), tr("Security"), ui->optionsPagesWidget, SecuritySettings); new QListWidgetItem(QIcon(":/resources/ui.svg"), tr("UI"), ui->optionsPagesWidget, UISettings); new QListWidgetItem(QIcon(":/resources/speech.svg"), tr("Speech"), ui->optionsPagesWidget, SpeechSettings); + new QListWidgetItem(QIcon(":/resources/form-settings.svg"), tr("Forms"), ui->optionsPagesWidget, FormSettings); ui->renderingEngineComboBox->addItem(tr("Software"), static_cast(pdf::RendererEngine::Software)); ui->renderingEngineComboBox->addItem(tr("Hardware accelerated (OpenGL)"), static_cast(pdf::RendererEngine::OpenGL)); @@ -193,6 +194,10 @@ void PDFViewerSettingsDialog::on_optionsPagesWidget_currentItemChanged(QListWidg ui->stackedWidget->setCurrentWidget(ui->speechPage); break; + case FormSettings: + ui->stackedWidget->setCurrentWidget(ui->formPage); + break; + default: Q_ASSERT(false); break; @@ -316,6 +321,10 @@ void PDFViewerSettingsDialog::loadData() ui->speechRateEdit->setValue(m_settings.m_speechRate); ui->speechPitchEdit->setValue(m_settings.m_speechPitch); ui->speechVolumeEdit->setValue(m_settings.m_speechVolume); + + // Form Settings + ui->formHighlightFieldsCheckBox->setChecked(m_settings.m_formAppearanceFlags.testFlag(pdf::PDFFormManager::HighlightFields)); + ui->formHighlightRequiredFieldsCheckBox->setChecked(m_settings.m_formAppearanceFlags.testFlag(pdf::PDFFormManager::HighlightRequiredFields)); } void PDFViewerSettingsDialog::saveData() @@ -487,6 +496,14 @@ void PDFViewerSettingsDialog::saveData() { m_settings.m_magnifierZoom = ui->magnifierZoomEdit->value(); } + else if (sender == ui->formHighlightFieldsCheckBox) + { + m_settings.m_formAppearanceFlags.setFlag(pdf::PDFFormManager::HighlightFields, ui->formHighlightFieldsCheckBox->isChecked()); + } + else if (sender == ui->formHighlightRequiredFieldsCheckBox) + { + m_settings.m_formAppearanceFlags.setFlag(pdf::PDFFormManager::HighlightRequiredFields, ui->formHighlightRequiredFieldsCheckBox->isChecked()); + } const bool loadData = !qobject_cast(sender) && !qobject_cast(sender); if (loadData) diff --git a/PdfForQtViewer/pdfviewersettingsdialog.h b/PdfForQtViewer/pdfviewersettingsdialog.h index a750458..e496195 100644 --- a/PdfForQtViewer/pdfviewersettingsdialog.h +++ b/PdfForQtViewer/pdfviewersettingsdialog.h @@ -70,7 +70,8 @@ public: ColorManagementSystemSettings, SecuritySettings, UISettings, - SpeechSettings + SpeechSettings, + FormSettings }; const PDFViewerSettings::Settings& getSettings() const { return m_settings; } diff --git a/PdfForQtViewer/pdfviewersettingsdialog.ui b/PdfForQtViewer/pdfviewersettingsdialog.ui index 0a1e478..d58cf3c 100644 --- a/PdfForQtViewer/pdfviewersettingsdialog.ui +++ b/PdfForQtViewer/pdfviewersettingsdialog.ui @@ -1068,6 +1068,86 @@ + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Form Settings + + + + + + + + Highlight form fields + + + + + + + Highlight required form fields + + + + + + + Enable + + + + + + + Enable + + + + + + + + + <html><head/><body><p>Form field appearance settings can be used to set highlighting of editable form fields. Required form fields can be highlighted separately by red color, other fields can be highlighted by blue color.</p></body></html> + + + true + + + + + + + Qt::Vertical + + + + 20 + 393 + + + + + + + + + diff --git a/PdfForQtViewer/resources/form-settings.svg b/PdfForQtViewer/resources/form-settings.svg new file mode 100644 index 0000000..0218693 --- /dev/null +++ b/PdfForQtViewer/resources/form-settings.svg @@ -0,0 +1,179 @@ + + + + + + + + + + + + image/svg+xml + + + + + Jakub Melka + + + + + + + + + + + + + + + + + + + + lorem i + dolor + sil + amet + +