Undo/redo manager, fixing form bugs

This commit is contained in:
Jakub Melka
2020-04-25 18:15:12 +02:00
parent 57b3711210
commit ba74169eaa
12 changed files with 532 additions and 55 deletions

View File

@@ -479,6 +479,8 @@ private:
PDFCatalog m_catalog;
};
using PDFDocumentPointer = QSharedPointer<PDFDocument>;
/// Helper class for document updates (for example, add/delete annotations,
/// fill form fields, do some other minor operations) and also for major
/// updates (document reset). It also contains modification flags, which are

View File

@@ -22,19 +22,19 @@
namespace pdf
{
PDFForm PDFForm::parse(const PDFObjectStorage* storage, PDFObject object)
PDFForm PDFForm::parse(const PDFDocument* document, PDFObject object)
{
PDFForm form;
if (const PDFDictionary* formDictionary = storage->getDictionaryFromObject(object))
if (const PDFDictionary* formDictionary = document->getDictionaryFromObject(object))
{
PDFDocumentDataLoaderDecorator loader(storage);
PDFDocumentDataLoaderDecorator loader(document);
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_formFields.emplace_back(PDFFormField::parse(&document->getStorage(), fieldRootReference, nullptr));
}
form.m_formType = FormType::AcroForm;
@@ -51,11 +51,73 @@ PDFForm PDFForm::parse(const PDFObjectStorage* storage, PDFObject object)
// Jakub Melka: handle XFA form
form.m_formType = FormType::XFAForm;
}
// As post-processing, delete all form fields, which are nullptr (are incorrectly defined)
form.m_formFields.erase(std::remove_if(form.m_formFields.begin(), form.m_formFields.end(), [](const auto& field){ return !field; }), form.m_formFields.end());
form.updateWidgetToFormFieldMapping();
// If we have form, then we must also look for 'rogue' form fields, which are
// incorrectly not in the 'Fields' entry of this form. We do this by iterating
// all pages, and their annotations and try to find these 'rogue' fields.
bool rogueFieldFound = false;
const size_t pageCount = document->getCatalog()->getPageCount();
for (size_t i = 0; i < pageCount; ++i)
{
const PDFPage* page = document->getCatalog()->getPage(i);
for (PDFObjectReference annotationReference : page->getAnnotations())
{
const PDFDictionary* annotationDictionary = document->getDictionaryFromObject(document->getObjectByReference(annotationReference));
if (form.m_widgetToFormField.count(annotationReference))
{
// This widget/form field is already present
continue;
}
if (loader.readNameFromDictionary(annotationDictionary, "Subtype") == "Widget")
{
rogueFieldFound = true;
form.m_formFields.emplace_back(PDFFormField::parse(&document->getStorage(), annotationReference, nullptr));
}
}
}
// As post-processing, delete all form fields, which are nullptr (are incorrectly defined)
form.m_formFields.erase(std::remove_if(form.m_formFields.begin(), form.m_formFields.end(), [](const auto& field){ return !field; }), form.m_formFields.end());
if (rogueFieldFound)
{
form.updateWidgetToFormFieldMapping();
}
}
return form;
}
void PDFForm::updateWidgetToFormFieldMapping()
{
m_widgetToFormField.clear();
if (isAcroForm() || isXFAForm())
{
for (const PDFFormFieldPointer& formFieldPtr : getFormFields())
{
formFieldPtr->fillWidgetToFormFieldMapping(m_widgetToFormField);
}
}
}
const PDFFormField* PDFForm::getFormFieldForWidget(PDFObjectReference widget) const
{
auto it = m_widgetToFormField.find(widget);
if (it != m_widgetToFormField.cend())
{
return it->second;
}
return nullptr;
}
void PDFFormField::fillWidgetToFormFieldMapping(PDFWidgetToFormFieldMapping& mapping)
{
for (const auto& childField : m_childFields)
@@ -142,6 +204,34 @@ PDFFormFieldPointer PDFFormField::parse(const PDFObjectStorage* storage, PDFObje
}
result.reset(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_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"));
// Generate fully qualified name. If partial name is empty, then fully qualified name
// is generated from parent fully qualified name (i.e. it is same as parent's name).
// This is according the PDF specification 1.7.
QStringList names;
if (parentField)
{
names << parentField->getName(FullyQualified);
}
names << formField->m_fieldNames[Partial];
names.removeAll(QString());
formField->m_fieldNames[FullyQualified] = names.join(".");
std::vector<PDFObjectReference> kids = loader.readReferenceArrayFromDictionary(fieldDictionary, "Kids");
if (kids.empty())
{
@@ -182,22 +272,6 @@ PDFFormFieldPointer PDFFormField::parse(const PDFObjectStorage* storage, PDFObje
}
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"));
@@ -297,17 +371,6 @@ PDFFormManager::~PDFFormManager()
}
const PDFFormField* PDFFormManager::getFormFieldForWidget(PDFObjectReference widget) const
{
auto it = m_widgetToFormField.find(widget);
if (it != m_widgetToFormField.cend())
{
return it->second;
}
return nullptr;
}
PDFAnnotationManager* PDFFormManager::getAnnotationManager() const
{
return m_annotationManager;
@@ -333,15 +396,13 @@ void PDFFormManager::setDocument(const PDFModifiedDocument& document)
{
if (m_document)
{
m_form = PDFForm::parse(&m_document->getStorage(), m_document->getCatalog()->getFormObject());
m_form = PDFForm::parse(m_document, m_document->getCatalog()->getFormObject());
}
else
{
// Clean the form
m_form = PDFForm();
}
updateWidgetToFormFieldMapping();
}
else if (document.hasFlag(PDFModifiedDocument::FormField))
{
@@ -372,17 +433,4 @@ void PDFFormManager::updateFieldValues()
}
}
void PDFFormManager::updateWidgetToFormFieldMapping()
{
m_widgetToFormField.clear();
if (hasAcroForm() || hasXFAForm())
{
for (const PDFFormFieldPointer& formFieldPtr : m_form.getFormFields())
{
formFieldPtr->fillWidgetToFormFieldMapping(m_widgetToFormField);
}
}
}
} // namespace pdf

View File

@@ -329,13 +329,23 @@ public:
const std::optional<PDFInteger>& getQuadding() const { return m_quadding; }
const PDFObject& getXFA() const { return m_xfa; }
bool isAcroForm() const { return getFormType() == PDFForm::FormType::AcroForm; }
bool isXFAForm() const { return getFormType() == PDFForm::FormType::XFAForm; }
/// 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;
/// Parses form from the object. If some error occurs
/// then empty form is returned, no exception is thrown.
/// \param storage Storage
/// \param document Document
/// \param reference Field reference
static PDFForm parse(const PDFObjectStorage* storage, PDFObject object);
static PDFForm parse(const PDFDocument* document, PDFObject object);
private:
void updateWidgetToFormFieldMapping();
FormType m_formType = FormType::None;
PDFFormFields m_formFields;
bool m_needAppearances = false;
@@ -345,6 +355,7 @@ private:
std::optional<QByteArray> m_defaultAppearance;
std::optional<PDFInteger> m_quadding;
PDFObject m_xfa;
PDFWidgetToFormFieldMapping m_widgetToFormField;
};
/// Form manager. Manages all form widgets functionality - triggers actions,
@@ -375,7 +386,7 @@ public:
/// 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;
const PDFFormField* getFormFieldForWidget(PDFObjectReference widget) const { return m_form.getFormFieldForWidget(widget); }
PDFAnnotationManager* getAnnotationManager() const;
void setAnnotationManager(PDFAnnotationManager* annotationManager);
@@ -391,14 +402,12 @@ public:
private:
void updateFieldValues();
void updateWidgetToFormFieldMapping();
PDFDrawWidgetProxy* m_proxy;
PDFAnnotationManager* m_annotationManager;
const PDFDocument* m_document;
FormAppearanceFlags m_flags;
PDFForm m_form;
PDFWidgetToFormFieldMapping m_widgetToFormField;
};
} // namespace pdf