AcroForms implementation (data structures start)

This commit is contained in:
Jakub Melka
2020-04-18 19:01:49 +02:00
parent 9b71031ec3
commit 247c2c98f7
15 changed files with 724 additions and 105 deletions

View File

@ -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 \

View File

@ -976,7 +976,8 @@ void PDFAnnotationManager::drawPage(QPainter* painter,
PDFInteger pageIndex,
const PDFPrecompiledPage* compiledPage,
PDFTextLayoutGetter& layoutGetter,
const QMatrix& pagePointToDevicePointMatrix) const
const QMatrix& pagePointToDevicePointMatrix,
QList<PDFRenderError>& errors) const
{
Q_UNUSED(compiledPage);
Q_UNUSED(layoutGetter);
@ -1002,6 +1003,8 @@ void PDFAnnotationManager::drawPage(QPainter* painter,
m_fontCache->setCacheShrinkEnabled(this, false);
for (const PageAnnotation& annotation : annotations.annotations)
{
try
{
const PDFAnnotation::Flags annotationFlags = annotation.annotation->getEffectiveFlags();
if (annotationFlags.testFlag(PDFAnnotation::Hidden) || // Annotation is completely hidden
@ -1080,6 +1083,17 @@ void PDFAnnotationManager::drawPage(QPainter* painter,
pdfPainter.processForm(AA, formBoundingBox, resources, transparencyGroup, content);
}
}
catch (PDFException exception)
{
errors.push_back(PDFRenderError(RenderErrorType::Error, exception.getMessage()));
continue;
}
catch (PDFRendererException exception)
{
errors.push_back(exception.getError());
continue;
}
}
m_fontCache->setCacheShrinkEnabled(this, true);
}

View File

@ -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<PDFRenderError>& errors) const override;
void setDocument(const PDFDocument* document, const PDFOptionalContentActivity* optionalContentActivity);

View File

@ -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<PDFReal> PDFDocumentDataLoaderDecorator::readNumberArrayFromDictionary(const PDFDictionary* dictionary,
const char* key,
std::vector<PDFReal> defaultValue)
std::vector<PDFReal> defaultValue) const
{
if (dictionary->hasKey(key))
{
@ -390,7 +390,7 @@ std::vector<PDFReal> PDFDocumentDataLoaderDecorator::readNumberArrayFromDictiona
return defaultValue;
}
std::vector<PDFInteger> PDFDocumentDataLoaderDecorator::readIntegerArrayFromDictionary(const PDFDictionary* dictionary, const char* key)
std::vector<PDFInteger> 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<PDFObjectReference> PDFDocumentDataLoaderDecorator::readReferenceArrayFromDictionary(const PDFDictionary* dictionary, const char* key)
std::vector<PDFObjectReference> 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<QByteArray> PDFDocumentDataLoaderDecorator::readOptionalStringFromDictionary(const PDFDictionary* dictionary, const char* key) const
{
if (dictionary->hasKey(key))
{
return readStringFromDictionary(dictionary, key);
}
return std::nullopt;
}
std::optional<PDFInteger> PDFDocumentDataLoaderDecorator::readOptionalIntegerFromDictionary(const PDFDictionary* dictionary, const char* key) const
{
if (dictionary->hasKey(key))
{
PDFInteger integer = readIntegerFromDictionary(dictionary, key, std::numeric_limits<PDFInteger>::max());
if (integer != std::numeric_limits<PDFInteger>::max())
{
return integer;
}
}
return std::nullopt;
}
std::vector<QByteArray> PDFDocumentDataLoaderDecorator::readStringArray(const PDFObject& object) const
{
const PDFObject& dereferencedObject = m_storage->getObject(object);

View File

@ -28,6 +28,8 @@
#include <QMatrix>
#include <QDateTime>
#include <optional>
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<PDFReal> readNumberArrayFromDictionary(const PDFDictionary* dictionary, const char* key, std::vector<PDFReal> defaultValue = std::vector<PDFReal>());
std::vector<PDFReal> readNumberArrayFromDictionary(const PDFDictionary* dictionary, const char* key, std::vector<PDFReal> defaultValue = std::vector<PDFReal>()) 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<PDFInteger> readIntegerArrayFromDictionary(const PDFDictionary* dictionary, const char* key);
std::vector<PDFInteger> 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<PDFObjectReference> readReferenceArrayFromDictionary(const PDFDictionary* dictionary, const char* key);
std::vector<PDFObjectReference> 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<QByteArray> 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<PDFInteger> readOptionalIntegerFromDictionary(const PDFDictionary* dictionary, const char* key) const;
private:
const PDFObjectStorage* m_storage;
};

View File

@ -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<PDFRenderError>& errors) const;
/// Performs drawing of additional graphics after all pages are drawn onto the painter.
/// \param painter Painter

View File

@ -752,12 +752,13 @@ void PDFDrawWidgetProxy::drawPages(QPainter* painter, QRect rect, PDFRenderer::F
painter->restore();
}
QList<PDFRenderError> 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<PDFRenderError>& errors = compiledPage->getErrors();
if (!errors.empty())
const QList<PDFRenderError>& pageErrors = compiledPage->getErrors();
if (!pageErrors.empty() || !drawInterfaceErrors.empty())
{
emit renderingError(item.pageIndex, errors);
QList<PDFRenderError> 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<PDFRenderError>& 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

View File

@ -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 <https://www.gnu.org/licenses/>.
#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<PDFObjectReference> 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<SignatureFlags>(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<std::pair<const char*, FieldType>, 4> fieldTypes = {
std::pair<const char*, FieldType>{ "Btn", FieldType::Button },
std::pair<const char*, FieldType>{ "Tx", FieldType::Text },
std::pair<const char*, FieldType>{ "Ch", FieldType::Choice },
std::pair<const char*, FieldType>{ "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<PDFObjectReference> 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<FieldFlags>(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<PDFFormFieldText*>(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

View File

@ -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 <https://www.gnu.org/licenses/>.
#ifndef PDFFORM_H
#define PDFFORM_H
#include "pdfobject.h"
#include "pdfannotation.h"
#include <optional>
namespace pdf
{
class PDFObjectStorage;
class PDFFormField;
using PDFFormFieldPointer = QSharedPointer<PDFFormField>;
using PDFFormFields = std::vector<PDFFormFieldPointer>;
/// 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<PDFFormWidget>;
/// 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<QString, NameTypeEnd>;
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<PDFObjectReference>& getCalculationOrder() const { return m_calculationOrder; }
const PDFObject& getResources() const { return m_resources; }
const std::optional<QByteArray>& getDefaultAppearance() const { return m_defaultAppearance; }
const std::optional<PDFInteger>& 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<PDFObjectReference> m_calculationOrder;
PDFObject m_resources;
std::optional<QByteArray> m_defaultAppearance;
std::optional<PDFInteger> m_quadding;
PDFObject m_xfa;
};
} // namespace pdf
#endif // PDFFORM_H

View File

@ -237,8 +237,9 @@ QImage PDFRasterizer::render(PDFInteger pageIndex,
if (annotationManager)
{
QList<PDFRenderError> 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<PDFRenderError> errors;
PDFTextLayoutGetter textLayoutGetter(nullptr, pageIndex);
annotationManager->drawPage(&painter, pageIndex, compiledPage, textLayoutGetter, matrix);
annotationManager->drawPage(&painter, pageIndex, compiledPage, textLayoutGetter, matrix, errors);
}
}

View File

@ -443,8 +443,15 @@ 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;
}

View File

@ -213,9 +213,11 @@ void PDFFindTextTool::drawPage(QPainter* painter,
PDFInteger pageIndex,
const PDFPrecompiledPage* compiledPage,
PDFTextLayoutGetter& layoutGetter,
const QMatrix& pagePointToDevicePointMatrix) const
const QMatrix& pagePointToDevicePointMatrix,
QList<PDFRenderError>& 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<PDFRenderError>& 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<PDFRenderError>& 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())

View File

@ -150,7 +150,8 @@ public:
PDFInteger pageIndex,
const PDFPrecompiledPage* compiledPage,
PDFTextLayoutGetter& layoutGetter,
const QMatrix& pagePointToDevicePointMatrix) const override;
const QMatrix& pagePointToDevicePointMatrix,
QList<PDFRenderError>& 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<PDFRenderError>& 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<PDFRenderError>& 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;

View File

@ -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<pdf::PDFRenderError>& errors) const
{
Q_UNUSED(compiledPage);
Q_UNUSED(errors);
const pdf::PDFTextSelection& textSelection = getTextSelection();
pdf::PDFTextSelectionPainter textSelectionPainter(&textSelection);

View File

@ -53,7 +53,8 @@ public:
pdf::PDFInteger pageIndex,
const pdf::PDFPrecompiledPage* compiledPage,
pdf::PDFTextLayoutGetter& layoutGetter,
const QMatrix& pagePointToDevicePointMatrix) const override;
const QMatrix& pagePointToDevicePointMatrix,
QList<pdf::PDFRenderError>& errors) const override;
void setDocument(const pdf::PDFDocument* document);