diff --git a/PdfForQtLib/PdfForQtLib.pro b/PdfForQtLib/PdfForQtLib.pro index 6cd126c..c168ba0 100644 --- a/PdfForQtLib/PdfForQtLib.pro +++ b/PdfForQtLib/PdfForQtLib.pro @@ -46,6 +46,7 @@ SOURCES += \ sources/pdfdocumentwriter.cpp \ sources/pdfexecutionpolicy.cpp \ sources/pdffile.cpp \ + sources/pdfform.cpp \ sources/pdfitemmodels.cpp \ sources/pdfjbig2decoder.cpp \ sources/pdfmultimedia.cpp \ @@ -94,6 +95,7 @@ HEADERS += \ sources/pdfdocumentwriter.h \ sources/pdfexecutionpolicy.h \ sources/pdffile.h \ + sources/pdfform.h \ sources/pdfitemmodels.h \ sources/pdfjbig2decoder.h \ sources/pdfmeshqualitysettings.h \ diff --git a/PdfForQtLib/sources/pdfannotation.cpp b/PdfForQtLib/sources/pdfannotation.cpp index fbfec11..2323f40 100644 --- a/PdfForQtLib/sources/pdfannotation.cpp +++ b/PdfForQtLib/sources/pdfannotation.cpp @@ -976,7 +976,8 @@ void PDFAnnotationManager::drawPage(QPainter* painter, PDFInteger pageIndex, const PDFPrecompiledPage* compiledPage, PDFTextLayoutGetter& layoutGetter, - const QMatrix& pagePointToDevicePointMatrix) const + const QMatrix& pagePointToDevicePointMatrix, + QList& errors) const { Q_UNUSED(compiledPage); Q_UNUSED(layoutGetter); @@ -1003,82 +1004,95 @@ void PDFAnnotationManager::drawPage(QPainter* painter, for (const PageAnnotation& annotation : annotations.annotations) { - const PDFAnnotation::Flags annotationFlags = annotation.annotation->getEffectiveFlags(); - if (annotationFlags.testFlag(PDFAnnotation::Hidden) || // Annotation is completely hidden - (m_target == Target::Print && !annotationFlags.testFlag(PDFAnnotation::Print)) || // Target is print and annotation is marked as not printed - (m_target == Target::View && annotationFlags.testFlag(PDFAnnotation::NoView)) || // Target is view, and annotation is disabled for screen - annotation.annotation->isReplyTo()) // Annotation is reply to another annotation, display just the first annotation + try { + const PDFAnnotation::Flags annotationFlags = annotation.annotation->getEffectiveFlags(); + if (annotationFlags.testFlag(PDFAnnotation::Hidden) || // Annotation is completely hidden + (m_target == Target::Print && !annotationFlags.testFlag(PDFAnnotation::Print)) || // Target is print and annotation is marked as not printed + (m_target == Target::View && annotationFlags.testFlag(PDFAnnotation::NoView)) || // Target is view, and annotation is disabled for screen + annotation.annotation->isReplyTo()) // Annotation is reply to another annotation, display just the first annotation + { + continue; + } + + PDFObject appearanceStreamObject = m_document->getObject(getAppearanceStream(annotation)); + if (!appearanceStreamObject.isStream()) + { + // Object is not valid appearance stream. We will try to draw default + // annotation appearance. + painter->save(); + painter->setRenderHint(QPainter::Antialiasing, true); + painter->setWorldMatrix(pagePointToDevicePointMatrix, true); + AnnotationDrawParameters parameters; + parameters.painter = painter; + parameters.key = std::make_pair(annotation.appearance, annotation.annotation->getAppearanceState()); + annotation.annotation->draw(parameters); + painter->restore(); + continue; + } + + PDFDocumentDataLoaderDecorator loader(m_document); + const PDFStream* formStream = appearanceStreamObject.getStream(); + const PDFDictionary* formDictionary = formStream->getDictionary(); + + QRectF annotationRectangle = annotation.annotation->getRectangle(); + QRectF formBoundingBox = loader.readRectangle(formDictionary->get("BBox"), QRectF()); + QMatrix formMatrix = loader.readMatrixFromDictionary(formDictionary, "Matrix", QMatrix()); + QByteArray content = m_document->getDecodedStream(formStream); + PDFObject resources = m_document->getObject(formDictionary->get("Resources")); + PDFObject transparencyGroup = m_document->getObject(formDictionary->get("Group")); + + if (formBoundingBox.isEmpty() || annotationRectangle.isEmpty()) + { + // Form bounding box is empty + continue; + } + + QMatrix userSpaceToDeviceSpace = prepareTransformations(pagePointToDevicePointMatrix, painter->device(), annotationFlags, page, annotationRectangle); + + if (annotationFlags.testFlag(PDFAnnotation::NoZoom)) + { + features.setFlag(PDFRenderer::ClipToCropBox, false); + } + + // Jakub Melka: perform algorithm 8.1, defined in PDF 1.7 reference, + // chapter 8.4.4 Appearance streams. + + // Step 1) - calculate transformed appearance box + QRectF transformedAppearanceBox = formMatrix.mapRect(formBoundingBox); + + // Step 2) - calculate matrix A, which maps from form space to annotation space + // Matrix A transforms from transformed appearance box space to the + // annotation rectangle. + const PDFReal scaleX = annotationRectangle.width() / transformedAppearanceBox.width(); + const PDFReal scaleY = annotationRectangle.height() / transformedAppearanceBox.height(); + const PDFReal translateX = annotationRectangle.left() - transformedAppearanceBox.left() * scaleX; + const PDFReal translateY = annotationRectangle.bottom() - transformedAppearanceBox.bottom() * scaleY; + QMatrix A(scaleX, 0.0, 0.0, scaleY, translateX, translateY); + + // Step 3) - compute final matrix AA + QMatrix AA = formMatrix * A; + + PDFPainter pdfPainter(painter, features, userSpaceToDeviceSpace, page, m_document, m_fontCache, cms.get(), m_optionalActivity, m_meshQualitySettings); + pdfPainter.initializeProcessor(); + + // Jakub Melka: we must check, that we do not display annotation disabled by optional content + PDFObjectReference oc = annotation.annotation->getOptionalContent(); + if (!oc.isValid() || !pdfPainter.isContentSuppressedByOC(oc)) + { + pdfPainter.processForm(AA, formBoundingBox, resources, transparencyGroup, content); + } + } + catch (PDFException exception) + { + errors.push_back(PDFRenderError(RenderErrorType::Error, exception.getMessage())); continue; } - - PDFObject appearanceStreamObject = m_document->getObject(getAppearanceStream(annotation)); - if (!appearanceStreamObject.isStream()) + catch (PDFRendererException exception) { - // Object is not valid appearance stream. We will try to draw default - // annotation appearance. - painter->save(); - painter->setRenderHint(QPainter::Antialiasing, true); - painter->setWorldMatrix(pagePointToDevicePointMatrix, true); - AnnotationDrawParameters parameters; - parameters.painter = painter; - parameters.key = std::make_pair(annotation.appearance, annotation.annotation->getAppearanceState()); - annotation.annotation->draw(parameters); - painter->restore(); + errors.push_back(exception.getError()); continue; } - - PDFDocumentDataLoaderDecorator loader(m_document); - const PDFStream* formStream = appearanceStreamObject.getStream(); - const PDFDictionary* formDictionary = formStream->getDictionary(); - - QRectF annotationRectangle = annotation.annotation->getRectangle(); - QRectF formBoundingBox = loader.readRectangle(formDictionary->get("BBox"), QRectF()); - QMatrix formMatrix = loader.readMatrixFromDictionary(formDictionary, "Matrix", QMatrix()); - QByteArray content = m_document->getDecodedStream(formStream); - PDFObject resources = m_document->getObject(formDictionary->get("Resources")); - PDFObject transparencyGroup = m_document->getObject(formDictionary->get("Group")); - - if (formBoundingBox.isEmpty() || annotationRectangle.isEmpty()) - { - // Form bounding box is empty - continue; - } - - QMatrix userSpaceToDeviceSpace = prepareTransformations(pagePointToDevicePointMatrix, painter->device(), annotationFlags, page, annotationRectangle); - - if (annotationFlags.testFlag(PDFAnnotation::NoZoom)) - { - features.setFlag(PDFRenderer::ClipToCropBox, false); - } - - // Jakub Melka: perform algorithm 8.1, defined in PDF 1.7 reference, - // chapter 8.4.4 Appearance streams. - - // Step 1) - calculate transformed appearance box - QRectF transformedAppearanceBox = formMatrix.mapRect(formBoundingBox); - - // Step 2) - calculate matrix A, which maps from form space to annotation space - // Matrix A transforms from transformed appearance box space to the - // annotation rectangle. - const PDFReal scaleX = annotationRectangle.width() / transformedAppearanceBox.width(); - const PDFReal scaleY = annotationRectangle.height() / transformedAppearanceBox.height(); - const PDFReal translateX = annotationRectangle.left() - transformedAppearanceBox.left() * scaleX; - const PDFReal translateY = annotationRectangle.bottom() - transformedAppearanceBox.bottom() * scaleY; - QMatrix A(scaleX, 0.0, 0.0, scaleY, translateX, translateY); - - // Step 3) - compute final matrix AA - QMatrix AA = formMatrix * A; - - PDFPainter pdfPainter(painter, features, userSpaceToDeviceSpace, page, m_document, m_fontCache, cms.get(), m_optionalActivity, m_meshQualitySettings); - pdfPainter.initializeProcessor(); - - // Jakub Melka: we must check, that we do not display annotation disabled by optional content - PDFObjectReference oc = annotation.annotation->getOptionalContent(); - if (!oc.isValid() || !pdfPainter.isContentSuppressedByOC(oc)) - { - pdfPainter.processForm(AA, formBoundingBox, resources, transparencyGroup, content); - } } m_fontCache->setCacheShrinkEnabled(this, true); diff --git a/PdfForQtLib/sources/pdfannotation.h b/PdfForQtLib/sources/pdfannotation.h index a36025e..79164ac 100644 --- a/PdfForQtLib/sources/pdfannotation.h +++ b/PdfForQtLib/sources/pdfannotation.h @@ -1129,7 +1129,7 @@ private: PDFAnnotationAdditionalActions m_additionalActions; }; -/// Widget annotation represents form fileds for interactive forms. +/// Widget annotation represents form fields for interactive forms. /// Annotation's dictionary is merged with form field dictionary, /// it can be done, because dictionaries doesn't overlap. class PDFWidgetAnnotation : public PDFAnnotation @@ -1281,7 +1281,8 @@ public: PDFInteger pageIndex, const PDFPrecompiledPage* compiledPage, PDFTextLayoutGetter& layoutGetter, - const QMatrix& pagePointToDevicePointMatrix) const override; + const QMatrix& pagePointToDevicePointMatrix, + QList& errors) const override; void setDocument(const PDFDocument* document, const PDFOptionalContentActivity* optionalContentActivity); diff --git a/PdfForQtLib/sources/pdfdocument.cpp b/PdfForQtLib/sources/pdfdocument.cpp index 3a6a551..c01aabf 100644 --- a/PdfForQtLib/sources/pdfdocument.cpp +++ b/PdfForQtLib/sources/pdfdocument.cpp @@ -253,7 +253,7 @@ PDFDocumentDataLoaderDecorator::PDFDocumentDataLoaderDecorator(const PDFDocument } -QByteArray PDFDocumentDataLoaderDecorator::readName(const PDFObject& object) +QByteArray PDFDocumentDataLoaderDecorator::readName(const PDFObject& object) const { const PDFObject& dereferencedObject = m_storage->getObject(object); if (dereferencedObject.isName()) @@ -264,7 +264,7 @@ QByteArray PDFDocumentDataLoaderDecorator::readName(const PDFObject& object) return QByteArray(); } -QByteArray PDFDocumentDataLoaderDecorator::readString(const PDFObject& object) +QByteArray PDFDocumentDataLoaderDecorator::readString(const PDFObject& object) const { const PDFObject& dereferencedObject = m_storage->getObject(object); if (dereferencedObject.isString()) @@ -362,7 +362,7 @@ QRectF PDFDocumentDataLoaderDecorator::readRectangle(const PDFObject& object, co return defaultValue; } -QMatrix PDFDocumentDataLoaderDecorator::readMatrixFromDictionary(const PDFDictionary* dictionary, const char* key, QMatrix defaultValue) +QMatrix PDFDocumentDataLoaderDecorator::readMatrixFromDictionary(const PDFDictionary* dictionary, const char* key, QMatrix defaultValue) const { if (dictionary->hasKey(key)) { @@ -380,7 +380,7 @@ QMatrix PDFDocumentDataLoaderDecorator::readMatrixFromDictionary(const PDFDictio std::vector PDFDocumentDataLoaderDecorator::readNumberArrayFromDictionary(const PDFDictionary* dictionary, const char* key, - std::vector defaultValue) + std::vector defaultValue) const { if (dictionary->hasKey(key)) { @@ -390,7 +390,7 @@ std::vector PDFDocumentDataLoaderDecorator::readNumberArrayFromDictiona return defaultValue; } -std::vector PDFDocumentDataLoaderDecorator::readIntegerArrayFromDictionary(const PDFDictionary* dictionary, const char* key) +std::vector PDFDocumentDataLoaderDecorator::readIntegerArrayFromDictionary(const PDFDictionary* dictionary, const char* key) const { if (dictionary->hasKey(key)) { @@ -440,7 +440,7 @@ QString PDFDocumentDataLoaderDecorator::readTextStringFromDictionary(const PDFDi return defaultValue; } -std::vector PDFDocumentDataLoaderDecorator::readReferenceArrayFromDictionary(const PDFDictionary* dictionary, const char* key) +std::vector PDFDocumentDataLoaderDecorator::readReferenceArrayFromDictionary(const PDFDictionary* dictionary, const char* key) const { if (dictionary->hasKey(key)) { @@ -605,7 +605,7 @@ bool PDFDocumentDataLoaderDecorator::readBooleanFromDictionary(const PDFDictiona return defaultValue; } -QByteArray PDFDocumentDataLoaderDecorator::readNameFromDictionary(const PDFDictionary* dictionary, const char* key) +QByteArray PDFDocumentDataLoaderDecorator::readNameFromDictionary(const PDFDictionary* dictionary, const char* key) const { if (dictionary->hasKey(key)) { @@ -615,7 +615,7 @@ QByteArray PDFDocumentDataLoaderDecorator::readNameFromDictionary(const PDFDicti return QByteArray(); } -QByteArray PDFDocumentDataLoaderDecorator::readStringFromDictionary(const PDFDictionary* dictionary, const char* key) +QByteArray PDFDocumentDataLoaderDecorator::readStringFromDictionary(const PDFDictionary* dictionary, const char* key) const { if (dictionary->hasKey(key)) { @@ -655,6 +655,28 @@ QStringList PDFDocumentDataLoaderDecorator::readTextStringList(const PDFObject& return result; } +std::optional PDFDocumentDataLoaderDecorator::readOptionalStringFromDictionary(const PDFDictionary* dictionary, const char* key) const +{ + if (dictionary->hasKey(key)) + { + return readStringFromDictionary(dictionary, key); + } + return std::nullopt; +} + +std::optional PDFDocumentDataLoaderDecorator::readOptionalIntegerFromDictionary(const PDFDictionary* dictionary, const char* key) const +{ + if (dictionary->hasKey(key)) + { + PDFInteger integer = readIntegerFromDictionary(dictionary, key, std::numeric_limits::max()); + if (integer != std::numeric_limits::max()) + { + return integer; + } + } + return std::nullopt; +} + std::vector PDFDocumentDataLoaderDecorator::readStringArray(const PDFObject& object) const { const PDFObject& dereferencedObject = m_storage->getObject(object); diff --git a/PdfForQtLib/sources/pdfdocument.h b/PdfForQtLib/sources/pdfdocument.h index 19e123a..2c334bb 100644 --- a/PdfForQtLib/sources/pdfdocument.h +++ b/PdfForQtLib/sources/pdfdocument.h @@ -28,6 +28,8 @@ #include #include +#include + namespace pdf { class PDFDocument; @@ -138,12 +140,12 @@ public: /// Reads a name from the object, if it is possible. If object is not a name, /// then empty byte array is returned. /// \param object Object, can be an indirect reference to object (it is dereferenced) - QByteArray readName(const PDFObject& object); + QByteArray readName(const PDFObject& object) const; /// Reads a string from the object, if it is possible. If object is not a string, /// then empty byte array is returned. /// \param object Object, can be an indirect reference to object (it is dereferenced) - QByteArray readString(const PDFObject& object); + QByteArray readString(const PDFObject& object) const; /// Reads an integer from the object, if it is possible. /// \param object Object, can be an indirect reference to object (it is dereferenced) @@ -239,15 +241,15 @@ public: /// Tries to read matrix from the dictionary. If matrix entry is not present, default value is returned. /// If it is present and invalid, exception is thrown. - QMatrix readMatrixFromDictionary(const PDFDictionary* dictionary, const char* key, QMatrix defaultValue); + QMatrix readMatrixFromDictionary(const PDFDictionary* dictionary, const char* key, QMatrix defaultValue) const; /// Tries to read array of real values from dictionary. If entry dictionary doesn't exist, /// or error occurs, default value is returned. - std::vector readNumberArrayFromDictionary(const PDFDictionary* dictionary, const char* key, std::vector defaultValue = std::vector()); + std::vector readNumberArrayFromDictionary(const PDFDictionary* dictionary, const char* key, std::vector defaultValue = std::vector()) const; /// Tries to read array of integer values from dictionary. If entry dictionary doesn't exist, /// or error occurs, empty array is returned. - std::vector readIntegerArrayFromDictionary(const PDFDictionary* dictionary, const char* key); + std::vector readIntegerArrayFromDictionary(const PDFDictionary* dictionary, const char* key) const; /// Reads number from dictionary. If dictionary entry doesn't exist, or error occurs, default value is returned. /// \param dictionary Dictionary containing desired data @@ -275,7 +277,7 @@ public: /// Tries to read array of references from dictionary. If entry dictionary doesn't exist, /// or error occurs, empty array is returned. - std::vector readReferenceArrayFromDictionary(const PDFDictionary* dictionary, const char* key); + std::vector readReferenceArrayFromDictionary(const PDFDictionary* dictionary, const char* key) const; /// Reads number array from dictionary. Reads all values. If some value is not /// real number (or integer number), default value is returned. Default value is also returned, @@ -324,12 +326,12 @@ public: /// Reads a name from dictionary. If dictionary entry doesn't exist, or error occurs, empty byte array is returned. /// \param dictionary Dictionary containing desired data /// \param key Entry key - QByteArray readNameFromDictionary(const PDFDictionary* dictionary, const char* key); + QByteArray readNameFromDictionary(const PDFDictionary* dictionary, const char* key) const; /// Reads a string from dictionary. If dictionary entry doesn't exist, or error occurs, empty byte array is returned. /// \param dictionary Dictionary containing desired data /// \param key Entry key - QByteArray readStringFromDictionary(const PDFDictionary* dictionary, const char* key); + QByteArray readStringFromDictionary(const PDFDictionary* dictionary, const char* key) const; /// Reads string array from dictionary. Reads all values. If error occurs, /// then empty array is returned. @@ -361,6 +363,17 @@ public: return result; } + /// Reads optional string from dictionary. If key is not in dictionary, + /// then empty optional is returned. + /// \param dictionary Dictionary containing desired data + /// \param key Entry key + std::optional readOptionalStringFromDictionary(const PDFDictionary* dictionary, const char* key) const; + + /// Reads optionalinteger from dictionary. If dictionary entry doesn't exist, or error occurs, empty optional is returned. + /// \param dictionary Dictionary containing desired data + /// \param key Entry key + std::optional readOptionalIntegerFromDictionary(const PDFDictionary* dictionary, const char* key) const; + private: const PDFObjectStorage* m_storage; }; diff --git a/PdfForQtLib/sources/pdfdocumentdrawinterface.h b/PdfForQtLib/sources/pdfdocumentdrawinterface.h index 41dd599..72ef89c 100644 --- a/PdfForQtLib/sources/pdfdocumentdrawinterface.h +++ b/PdfForQtLib/sources/pdfdocumentdrawinterface.h @@ -19,6 +19,7 @@ #define PDFDOCUMENTDRAWINTERFACE_H #include "pdfglobal.h" +#include "pdfexception.h" class QPainter; @@ -40,11 +41,13 @@ public: /// \param compiledPage Compiled page /// \param layoutGetter Layout getter /// \param pagePointToDevicePointMatrix Matrix mapping page space to device point space + /// \param[out] errors Output parameter - rendering errors virtual void drawPage(QPainter* painter, pdf::PDFInteger pageIndex, const PDFPrecompiledPage* compiledPage, PDFTextLayoutGetter& layoutGetter, - const QMatrix& pagePointToDevicePointMatrix) const; + const QMatrix& pagePointToDevicePointMatrix, + QList& errors) const; /// Performs drawing of additional graphics after all pages are drawn onto the painter. /// \param painter Painter diff --git a/PdfForQtLib/sources/pdfdrawspacecontroller.cpp b/PdfForQtLib/sources/pdfdrawspacecontroller.cpp index 043ed33..ff88927 100644 --- a/PdfForQtLib/sources/pdfdrawspacecontroller.cpp +++ b/PdfForQtLib/sources/pdfdrawspacecontroller.cpp @@ -752,12 +752,13 @@ void PDFDrawWidgetProxy::drawPages(QPainter* painter, QRect rect, PDFRenderer::F painter->restore(); } + QList drawInterfaceErrors; if (!features.testFlag(PDFRenderer::DenyExtraGraphics)) { for (IDocumentDrawInterface* drawInterface : m_drawInterfaces) { painter->save(); - drawInterface->drawPage(painter, item.pageIndex, compiledPage, layoutGetter, matrix); + drawInterface->drawPage(painter, item.pageIndex, compiledPage, layoutGetter, matrix, drawInterfaceErrors); painter->restore(); } } @@ -794,10 +795,15 @@ void PDFDrawWidgetProxy::drawPages(QPainter* painter, QRect rect, PDFRenderer::F painter->restore(); } - const QList& errors = compiledPage->getErrors(); - if (!errors.empty()) + const QList& pageErrors = compiledPage->getErrors(); + if (!pageErrors.empty() || !drawInterfaceErrors.empty()) { - emit renderingError(item.pageIndex, errors); + QList errors = pageErrors; + if (!drawInterfaceErrors.isEmpty()) + { + errors.append(drawInterfaceErrors); + } + emit renderingError(item.pageIndex, qMove(errors)); } } } @@ -1371,13 +1377,15 @@ void IDocumentDrawInterface::drawPage(QPainter* painter, PDFInteger pageIndex, const PDFPrecompiledPage* compiledPage, PDFTextLayoutGetter& layoutGetter, - const QMatrix& pagePointToDevicePointMatrix) const + const QMatrix& pagePointToDevicePointMatrix, + QList& errors) const { Q_UNUSED(painter); Q_UNUSED(pageIndex); Q_UNUSED(compiledPage); Q_UNUSED(layoutGetter); Q_UNUSED(pagePointToDevicePointMatrix); + Q_UNUSED(errors); } void IDocumentDrawInterface::drawPostRendering(QPainter* painter, QRect rect) const diff --git a/PdfForQtLib/sources/pdfform.cpp b/PdfForQtLib/sources/pdfform.cpp new file mode 100644 index 0000000..376478c --- /dev/null +++ b/PdfForQtLib/sources/pdfform.cpp @@ -0,0 +1,213 @@ +// Copyright (C) 2020 Jakub Melka +// +// This file is part of PdfForQt. +// +// PdfForQt is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// PdfForQt is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with PDFForQt. If not, see . + +#include "pdfform.h" +#include "pdfdocument.h" + +namespace pdf +{ + +PDFForm PDFForm::parse(const PDFObjectStorage* storage, PDFObject object) +{ + PDFForm form; + + if (const PDFDictionary* formDictionary = storage->getDictionaryFromObject(object)) + { + PDFDocumentDataLoaderDecorator loader(storage); + + std::vector fieldRoots = loader.readReferenceArrayFromDictionary(formDictionary, "Fields"); + form.m_formFields.reserve(fieldRoots.size()); + for (const PDFObjectReference& fieldRootReference : fieldRoots) + { + form.m_formFields.emplace_back(PDFFormField::parse(storage, fieldRootReference, nullptr)); + } + + form.m_needAppearances = loader.readBooleanFromDictionary(formDictionary, "NeedAppearances", false); + form.m_signatureFlags = static_cast(loader.readIntegerFromDictionary(formDictionary, "SigFlags", 0)); + form.m_calculationOrder = loader.readReferenceArrayFromDictionary(formDictionary, "CO"); + form.m_resources = formDictionary->get("DR"); + form.m_defaultAppearance = loader.readOptionalStringFromDictionary(formDictionary, "DA"); + form.m_quadding = loader.readOptionalIntegerFromDictionary(formDictionary, "Q"); + form.m_xfa = formDictionary->get("XFA"); + } + + return form; +} + +PDFFormFieldPointer PDFFormField::parse(const PDFObjectStorage* storage, PDFObjectReference reference, PDFFormField* parentField) +{ + PDFFormFieldPointer result; + + if (const PDFDictionary* fieldDictionary = storage->getDictionaryFromObject(storage->getObjectByReference(reference))) + { + PDFFormField* formField = nullptr; + PDFFormFieldButton* formFieldButton = nullptr; + PDFFormFieldText* formFieldText = nullptr; + PDFFormFieldChoice* formFieldChoice = nullptr; + PDFFormFieldSignature* formFieldSignature = nullptr; + + constexpr const std::array, 4> fieldTypes = { + std::pair{ "Btn", FieldType::Button }, + std::pair{ "Tx", FieldType::Text }, + std::pair{ "Ch", FieldType::Choice }, + std::pair{ "Sig", FieldType::Signature } + }; + + PDFDocumentDataLoaderDecorator loader(storage); + FieldType formFieldType = parentField ? parentField->getFieldType() : FieldType::Invalid; + if (fieldDictionary->hasKey(FT)) + { + formFieldType = loader.readEnumByName(fieldDictionary->get("FT"), fieldTypes.begin(), fieldTypes.end(), FieldType::Invalid); + } + + switch (formFieldType) + { + case FieldType::Invalid: + formField = new PDFFormField(); + break; + + case FieldType::Button: + formFieldButton = new PDFFormFieldButton(); + formField = formFieldButton; + break; + + case FieldType::Text: + formFieldText = new PDFFormFieldText(); + formField = formFieldText; + break; + + case FieldType::Choice: + formFieldChoice = new PDFFormFieldChoice(); + formField = formFieldChoice; + break; + + case FieldType::Signature: + formFieldSignature = new PDFFormFieldSignature(); + formField = formFieldSignature; + break; + + default: + Q_ASSERT(false); + break; + } + result.reset(formField); + + std::vector kids = loader.readReferenceArrayFromDictionary(fieldDictionary, "Kids"); + if (kids.empty()) + { + // This means, that field's dictionary is merged with annotation's dictionary, + // so, we will add pointer to self to the form widgets. But we must test, if we + // really have merged annotation's dictionary - we test it by checking for 'Subtype' + // presence. + if (loader.readNameFromDictionary(fieldDictionary, "Subtype") == "Widget") + { + formField->m_widgets.emplace_back(PDFFormWidget::parse(storage, reference, formField)); + } + } + else + { + // Otherwise we must scan all references, and determine, if kid is another form field, + // or it is a widget annotation. Widget annotations has required field 'Subtype', which + // has value 'Widget', form field has required field 'Parent' (for non-root fields). + + for (const PDFObjectReference& kid : kids) + { + if (const PDFDictionary* childDictionary = storage->getDictionaryFromObject(storage->getObjectByReference(kid))) + { + const bool isWidget = loader.readNameFromDictionary(childDictionary, "Subtype") == "Widget"; + const bool isField = loader.readReferenceFromDictionary(childDictionary, "Parent").isValid(); + + if (isField) + { + // This is form field (potentially merged with widget) + formField->m_childFields.emplace_back(PDFFormField::parse(storage, kid, formField)); + } + else if (isWidget) + { + // This is pure widget (with no form field) + formField->m_widgets.emplace_back(PDFFormWidget::parse(storage, kid, formField)); + } + } + } + + } + + PDFObject parentV = parentField ? parentField->getValue() : PDFObject(); + PDFObject parentDV = parentField ? parentField->getDefaultValue() : PDFObject(); + FieldFlags parentFlags = parentField ? parentField->getFlags() : None; + + formField->m_selfReference = reference; + formField->m_fieldType = formFieldType; + formField->m_parentField = parentField; + formField->m_fieldNames[Partial] = loader.readTextStringFromDictionary(fieldDictionary, "T", QString()); + formField->m_fieldNames[UserCaption] = loader.readTextStringFromDictionary(fieldDictionary, "TU", QString()); + formField->m_fieldNames[Export] = loader.readTextStringFromDictionary(fieldDictionary, "TM", QString()); + formField->m_fieldNames[FullyQualified] = parentField ? QString("%1.%2").arg(parentField->getName(FullyQualified), formField->m_fieldNames[Partial]) : formField->m_fieldNames[Partial]; + formField->m_fieldFlags = fieldDictionary->hasKey("Ff") ? static_cast(loader.readIntegerFromDictionary(fieldDictionary, "Ff", 0)) : parentFlags; + formField->m_value = fieldDictionary->hasKey("V") ? fieldDictionary->get("V") : parentV; + formField->m_defaultValue = fieldDictionary->hasKey("DV") ? fieldDictionary->get("DV") : parentDV; + formField->m_additionalActions = PDFAnnotationAdditionalActions::parse(storage, fieldDictionary->get("AA")); + + if (formFieldButton) + { + formFieldButton->m_options = loader.readTextStringList(fieldDictionary->get("Opt")); + } + + if (formFieldText) + { + PDFInteger maxLengthDefault = 0; + + if (PDFFormFieldText* parentTextField = dynamic_cast(parentField)) + { + maxLengthDefault = parentTextField->getTextMaximalLength(); + } + + formFieldText->m_maxLength = loader.readIntegerFromDictionary(fieldDictionary, "MaxLen", maxLengthDefault); + } + } + + return result; +} + +PDFFormWidget::PDFFormWidget(PDFObjectReference widget, PDFFormField* parentField) : + m_widget(widget), + m_parentField(parentField) +{ + +} + +PDFFormWidget PDFFormWidget::parse(const PDFObjectStorage* storage, PDFObjectReference reference, PDFFormField* parentField) +{ + Q_UNUSED(storage); + return PDFFormWidget(reference, parentField); +} + +PDFFormFieldButton::ButtonType PDFFormFieldButton::getButtonType() const +{ + if (m_fieldFlags.testFlag(PushButton)) + { + return ButtonType::PushButton; + } + else if (m_fieldFlags.testFlag(Radio)) + { + return ButtonType::RadioButton; + } + + return ButtonType::CheckBox; +} + +} // namespace pdf diff --git a/PdfForQtLib/sources/pdfform.h b/PdfForQtLib/sources/pdfform.h new file mode 100644 index 0000000..26e1a71 --- /dev/null +++ b/PdfForQtLib/sources/pdfform.h @@ -0,0 +1,322 @@ +// Copyright (C) 2020 Jakub Melka +// +// This file is part of PdfForQt. +// +// PdfForQt is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// PdfForQt is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with PDFForQt. If not, see . + +#ifndef PDFFORM_H +#define PDFFORM_H + +#include "pdfobject.h" +#include "pdfannotation.h" + +#include + +namespace pdf +{ + +class PDFObjectStorage; +class PDFFormField; + +using PDFFormFieldPointer = QSharedPointer; +using PDFFormFields = std::vector; + +/// A simple proxy to the widget annotation +class PDFFormWidget +{ +public: + explicit inline PDFFormWidget() = default; + explicit inline PDFFormWidget(PDFObjectReference widget, PDFFormField* parentField); + + PDFObjectReference getWidget() const { return m_widget; } + PDFFormField* getParent() const { return m_parentField; } + + /// Parses form widget from the object reference. If some error occurs + /// then empty object is returned, no exception is thrown. + /// \param storage Storage + /// \param reference Widget reference + /// \param parentField Parent field + static PDFFormWidget parse(const PDFObjectStorage* storage, PDFObjectReference reference, PDFFormField* parentField); + +private: + PDFObjectReference m_widget; + PDFFormField* m_parentField; +}; + +using PDFFormWidgets = std::vector; + +/// Form field is terminal or non-terminal field (non-terminal fields +/// have children), fields represents various interactive widgets, such as +/// checks, radio buttons, text edits etc., which are editable and user +/// can interact with them. +class PDFFormField +{ +public: + explicit inline PDFFormField() = default; + + enum class FieldType + { + Invalid, + Button, + Text, + Choice, + Signature + }; + + enum NameType + { + Partial, ///< Partial name for this field + UserCaption, ///< Name used in GUI (for example, in message boxes) + FullyQualified, ///< Fully qualified name (according to the PDF specification 1.7) + Export, ///< Name for export + NameTypeEnd + }; + + using FieldNames = std::array; + + enum FieldFlag + { + None = 0, + + /// Field is read only, it doesn't respond on mouse clicks (if it is a button), + /// associated widget annotation will not interact with the user. Field can't + /// change it's value. Mainly used for calculable fields. + ReadOnly = 1 << 0, + + /// If set, field is required, when submitting form by submit action. If submit + /// action is triggered, then all fields with this flag must have a value. + Required = 1 << 1, + + /// If set, field value must not be exported by submit form action. + NoExport = 1 << 2, + + /// Text fields only. If set, then text can span multiple lines. Otherwise, + /// text is restricted to single line. + Multiline = 1 << 12, + + /// Text fields only. If set, field is intended to display text edit, which + /// can edit passwords. Password characters should not be echoed to the screen + /// and value of this field should not be stored in PDF file. + Password = 1 << 13, + + /// Only for radio buttons. If set, at least one radio button is checked. + /// If user clicks on checked radio button, it is not checked off. Otherwise + /// user can uncheck checked radio button (so no button is selected). + NoToggleToOff = 1 << 14, + + /// Valid only for buttons which have PushButton flag unset. If Radio flag is set, + /// then widget is radio button, otherwise widget is push button. + Radio = 1 << 15, + + /// Valid only for buttons. If set, button is push button. + PushButton = 1 << 16, + + /// For choice fields only. If set, choice field is combo box. + /// If clear, it is a list box. + Combo = 1 << 17, + + /// For choice fields combo boxes only. If set, combo box is editable, + /// i.e. user can enter custom text. If this flag is cleared, then combo box + /// is not editable and user can only select items from the drop down list. + Edit = 1 << 18, + + /// For choice fields only. If set, the field option's items should be sorted + /// alphabetically, but not by the viewer application, but by author of the form. + /// Viewer application should respect Opt array and display items in that order. + Sort = 1 << 19, + + /// Text fields only. Text field is used to select file path, whose contents + /// should be submitted as the value of the field. + FileSelect = 1 << 20, + + /// For choice fields only. If set, then user can select multiple choices + /// simultaneously, if clear, then only one item should be selected at the time. + MultiSelect = 1 << 21, + + /// Text fields only. If set, texts entered in this field should not be spell checked. + DoNotSpellcheck = 1 << 22, + + /// Text fields only. Allow only so much text, which fits field's area. If field's area is filled, + /// then do not allow the user to store more text in the field. + DoNotScroll = 1 << 23, + + /// Text fields only. If set, then MaxLen entry and annotation rectangle is used + /// to divide space equally for each character. Text is laid out into those spaces. + Comb = 1 << 24, + + /// Valid only for radio buttons. Radio buttons in a group of radio buttons, + /// which have same value for 'On' state, will turn On and Off in unison, if one + /// is checked, all are checked. If clear, radio buttons are mutually exclusive. + RadiosInUnison = 1 << 25, + + /// Text fields only. Value of this field should be a rich text. + RichText = 1 << 25, + + /// Choice fields only. If set, then when user selects choice by mouse, + /// data is immediately commited. Otherwise data are commited only, when + /// widget lose focus. + CommitOnSelectionChange = 1 << 26 + }; + + Q_DECLARE_FLAGS(FieldFlags, FieldFlag) + + PDFObjectReference getSelfReference() const { return m_selfReference; } + FieldType getFieldType() const { return m_fieldType; } + const PDFFormField* getParentField() const { return m_parentField; } + const PDFFormFields& getChildFields() const { return m_childFields; } + const PDFFormWidgets& getWidgets() const { return m_widgets; } + const QString& getName(NameType nameType) const { return m_fieldNames.at(nameType); } + FieldFlags getFlags() const { return m_fieldFlags; } + const PDFObject& getValue() const { return m_value; } + const PDFObject& getDefaultValue() const { return m_defaultValue; } + + /// Parses form field from the object reference. If some error occurs + /// then null pointer is returned, no exception is thrown. + /// \param storage Storage + /// \param reference Field reference + /// \param parentField Parent field (or nullptr, if it is root field) + static PDFFormFieldPointer parse(const PDFObjectStorage* storage, PDFObjectReference reference, PDFFormField* parentField); + +protected: + PDFObjectReference m_selfReference; + FieldType m_fieldType = FieldType::Invalid; + PDFFormField* m_parentField = nullptr; + PDFFormFields m_childFields; + PDFFormWidgets m_widgets; + FieldNames m_fieldNames; + FieldFlags m_fieldFlags = None; + PDFObject m_value; + PDFObject m_defaultValue; + PDFAnnotationAdditionalActions m_additionalActions; +}; + +/// Represents pushbutton, checkbox and radio button (which is distinguished +/// by flags). +class PDFFormFieldButton : public PDFFormField +{ +public: + explicit inline PDFFormFieldButton() = default; + + enum class ButtonType + { + PushButton, + RadioButton, + CheckBox + }; + + /// Determines button type from form field's flags + ButtonType getButtonType() const; + + const QStringList& getOptions() const { return m_options; } + +private: + friend static PDFFormFieldPointer PDFFormField::parse(const PDFObjectStorage* storage, PDFObjectReference reference, PDFFormField* parentField); + + /// List of names of 'On' state for radio buttons. In widget annotation's appearance + /// dictionaries, state names are computer generated numbers (for example /1, /3, ...), + /// which are indices to this string list. This allows to distinguish between + /// different widget annotations, even if they have same value in m_options array. + QStringList m_options; +}; + +class PDFFormFieldText : public PDFFormField +{ +public: + explicit inline PDFFormFieldText() = default; + + PDFInteger getTextMaximalLength() const { return m_maxLength; } + +private: + friend static PDFFormFieldPointer PDFFormField::parse(const PDFObjectStorage* storage, PDFObjectReference reference, PDFFormField* parentField); + + /// Maximal length of text in the field. If zero, + /// no maximal length is specified. + PDFInteger m_maxLength = 0; +}; + +class PDFFormFieldChoice : public PDFFormField +{ +public: + explicit inline PDFFormFieldChoice() = default; + +private: + friend static PDFFormFieldPointer PDFFormField::parse(const PDFObjectStorage* storage, PDFObjectReference reference, PDFFormField* parentField); + +}; + +class PDFFormFieldSignature : public PDFFormField +{ +public: + explicit inline PDFFormFieldSignature() = default; + +private: + friend static PDFFormFieldPointer PDFFormField::parse(const PDFObjectStorage* storage, PDFObjectReference reference, PDFFormField* parentField); + +}; + +/// This class represents interactive form. Interactive form fields can span multiple +/// document pages. So this object represents all interactive form fields in the document. +/// Fields forms tree-like structure, where leafs are usually widgets. Fields include +/// ordinary widgets, such as buttons, check boxes, combo boxes and text fields, and one +/// special - signature field, which represents digital signature. +class PDFForm +{ +public: + explicit inline PDFForm() = default; + + enum class FormType + { + None, + AcroForm, + XFAForm + }; + + enum SignatureFlag + { + None = 0x0000, + SignatureExists = 0x0001, ///< If set, at least one signature exists in the document + AppendOnly = 0x0002, ///< If set, signature may be invalidated during rewrite + }; + Q_DECLARE_FLAGS(SignatureFlags, SignatureFlag) + + const PDFFormFields& getFormFields() const { return m_formFields; } + bool isAppearanceUpdateNeeded() const { return m_needAppearances; } + SignatureFlags getSignatureFlags() const { return m_signatureFlags; } + const std::vector& getCalculationOrder() const { return m_calculationOrder; } + const PDFObject& getResources() const { return m_resources; } + const std::optional& getDefaultAppearance() const { return m_defaultAppearance; } + const std::optional& getQuadding() const { return m_quadding; } + const PDFObject& getXFA() const { return m_xfa; } + + /// Parses form from the object. If some error occurs + /// then empty form is returned, no exception is thrown. + /// \param storage Storage + /// \param reference Field reference + static PDFForm parse(const PDFObjectStorage* storage, PDFObject object); + +private: + FormType m_formType = FormType::None; + PDFFormFields m_formFields; + bool m_needAppearances = false; + SignatureFlags m_signatureFlags = None; + std::vector m_calculationOrder; + PDFObject m_resources; + std::optional m_defaultAppearance; + std::optional m_quadding; + PDFObject m_xfa; +}; + +} // namespace pdf + +#endif // PDFFORM_H diff --git a/PdfForQtLib/sources/pdfrenderer.cpp b/PdfForQtLib/sources/pdfrenderer.cpp index 6b73352..3151452 100644 --- a/PdfForQtLib/sources/pdfrenderer.cpp +++ b/PdfForQtLib/sources/pdfrenderer.cpp @@ -237,8 +237,9 @@ QImage PDFRasterizer::render(PDFInteger pageIndex, if (annotationManager) { + QList errors; PDFTextLayoutGetter textLayoutGetter(nullptr, pageIndex); - annotationManager->drawPage(&painter, pageIndex, compiledPage, textLayoutGetter, matrix); + annotationManager->drawPage(&painter, pageIndex, compiledPage, textLayoutGetter, matrix, errors); } } @@ -268,8 +269,9 @@ QImage PDFRasterizer::render(PDFInteger pageIndex, if (annotationManager) { + QList errors; PDFTextLayoutGetter textLayoutGetter(nullptr, pageIndex); - annotationManager->drawPage(&painter, pageIndex, compiledPage, textLayoutGetter, matrix); + annotationManager->drawPage(&painter, pageIndex, compiledPage, textLayoutGetter, matrix, errors); } } diff --git a/PdfForQtLib/sources/pdfstreamfilters.cpp b/PdfForQtLib/sources/pdfstreamfilters.cpp index f23166c..d629f30 100644 --- a/PdfForQtLib/sources/pdfstreamfilters.cpp +++ b/PdfForQtLib/sources/pdfstreamfilters.cpp @@ -443,7 +443,14 @@ QByteArray PDFFlateDecodeFilter::uncompress(const QByteArray& data) break; // No error, normal behaviour default: + { + if (errorMessage.isEmpty()) + { + errorMessage = PDFTranslationContext::tr("zlib code: %1").arg(error); + } + throw PDFException(PDFTranslationContext::tr("Error decompressing by flate method: %1").arg(errorMessage)); + } } return result; diff --git a/PdfForQtLib/sources/pdfwidgettool.cpp b/PdfForQtLib/sources/pdfwidgettool.cpp index 925863b..e2670ca 100644 --- a/PdfForQtLib/sources/pdfwidgettool.cpp +++ b/PdfForQtLib/sources/pdfwidgettool.cpp @@ -213,9 +213,11 @@ void PDFFindTextTool::drawPage(QPainter* painter, PDFInteger pageIndex, const PDFPrecompiledPage* compiledPage, PDFTextLayoutGetter& layoutGetter, - const QMatrix& pagePointToDevicePointMatrix) const + const QMatrix& pagePointToDevicePointMatrix, + QList& errors) const { Q_UNUSED(compiledPage); + Q_UNUSED(errors); const pdf::PDFTextSelection& textSelection = getTextSelection(); pdf::PDFTextSelectionPainter textSelectionPainter(&textSelection); @@ -507,9 +509,11 @@ void PDFSelectTextTool::drawPage(QPainter* painter, PDFInteger pageIndex, const PDFPrecompiledPage* compiledPage, PDFTextLayoutGetter& layoutGetter, - const QMatrix& pagePointToDevicePointMatrix) const + const QMatrix& pagePointToDevicePointMatrix, + QList& errors) const { Q_UNUSED(compiledPage); + Q_UNUSED(errors); pdf::PDFTextSelectionPainter textSelectionPainter(&m_textSelection); textSelectionPainter.draw(painter, pageIndex, layoutGetter, pagePointToDevicePointMatrix); @@ -966,10 +970,12 @@ void PDFPickTool::drawPage(QPainter* painter, PDFInteger pageIndex, const PDFPrecompiledPage* compiledPage, PDFTextLayoutGetter& layoutGetter, - const QMatrix& pagePointToDevicePointMatrix) const + const QMatrix& pagePointToDevicePointMatrix, + QList& errors) const { Q_UNUSED(compiledPage); Q_UNUSED(layoutGetter); + Q_UNUSED(errors); // If we are picking rectangles, then draw current selection rectangle if (m_mode == Mode::Rectangles && m_pageIndex == pageIndex && !m_pickedPoints.empty()) diff --git a/PdfForQtLib/sources/pdfwidgettool.h b/PdfForQtLib/sources/pdfwidgettool.h index e15f5ad..e8b3a63 100644 --- a/PdfForQtLib/sources/pdfwidgettool.h +++ b/PdfForQtLib/sources/pdfwidgettool.h @@ -150,7 +150,8 @@ public: PDFInteger pageIndex, const PDFPrecompiledPage* compiledPage, PDFTextLayoutGetter& layoutGetter, - const QMatrix& pagePointToDevicePointMatrix) const override; + const QMatrix& pagePointToDevicePointMatrix, + QList& errors) const override; protected: virtual void setActiveImpl(bool active) override; @@ -216,7 +217,8 @@ public: PDFInteger pageIndex, const PDFPrecompiledPage* compiledPage, PDFTextLayoutGetter& layoutGetter, - const QMatrix& pagePointToDevicePointMatrix) const override; + const QMatrix& pagePointToDevicePointMatrix, + QList& errors) const override; virtual void mousePressEvent(QWidget* widget, QMouseEvent* event) override; virtual void mouseReleaseEvent(QWidget* widget, QMouseEvent* event) override; @@ -308,7 +310,8 @@ public: virtual void drawPage(QPainter* painter, PDFInteger pageIndex, const PDFPrecompiledPage* compiledPage, PDFTextLayoutGetter& layoutGetter, - const QMatrix& pagePointToDevicePointMatrix) const override; + const QMatrix& pagePointToDevicePointMatrix, + QList& errors) const override; virtual void drawPostRendering(QPainter* painter, QRect rect) const override; virtual void mousePressEvent(QWidget* widget, QMouseEvent* event) override; virtual void mouseReleaseEvent(QWidget* widget, QMouseEvent* event) override; diff --git a/PdfForQtViewer/pdfadvancedfindwidget.cpp b/PdfForQtViewer/pdfadvancedfindwidget.cpp index 72e7a26..a359e27 100644 --- a/PdfForQtViewer/pdfadvancedfindwidget.cpp +++ b/PdfForQtViewer/pdfadvancedfindwidget.cpp @@ -163,9 +163,11 @@ void PDFAdvancedFindWidget::drawPage(QPainter* painter, pdf::PDFInteger pageIndex, const pdf::PDFPrecompiledPage* compiledPage, pdf::PDFTextLayoutGetter& layoutGetter, - const QMatrix& pagePointToDevicePointMatrix) const + const QMatrix& pagePointToDevicePointMatrix, + QList& errors) const { Q_UNUSED(compiledPage); + Q_UNUSED(errors); const pdf::PDFTextSelection& textSelection = getTextSelection(); pdf::PDFTextSelectionPainter textSelectionPainter(&textSelection); diff --git a/PdfForQtViewer/pdfadvancedfindwidget.h b/PdfForQtViewer/pdfadvancedfindwidget.h index f2bdfe7..d484765 100644 --- a/PdfForQtViewer/pdfadvancedfindwidget.h +++ b/PdfForQtViewer/pdfadvancedfindwidget.h @@ -53,7 +53,8 @@ public: pdf::PDFInteger pageIndex, const pdf::PDFPrecompiledPage* compiledPage, pdf::PDFTextLayoutGetter& layoutGetter, - const QMatrix& pagePointToDevicePointMatrix) const override; + const QMatrix& pagePointToDevicePointMatrix, + QList& errors) const override; void setDocument(const pdf::PDFDocument* document);