// 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_formType = FormType::AcroForm; 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"); if (!form.m_xfa.isNull()) { // Jakub Melka: handle XFA form form.m_formType = FormType::XFAForm; } } 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); } if (formFieldChoice) { // Parse options - it is array of options values. Option value can be either a single text // string, or array of two values - export value and text to be displayed. PDFObject options = storage->getObject(fieldDictionary->get("Opt")); if (options.isArray()) { const PDFArray* optionsArray = options.getArray(); formFieldChoice->m_options.reserve(optionsArray->getCount()); for (size_t i = 0; i < optionsArray->getCount(); ++i) { PDFFormFieldChoice::Option option; PDFObject optionItemObject = storage->getObject(optionsArray->getItem(i)); if (optionItemObject.isArray()) { QStringList stringList = loader.readTextStringList(optionItemObject); if (stringList.size() == 2) { option.exportString = stringList[0]; option.userString = stringList[1]; } } else { option.userString = loader.readTextString(optionItemObject, QString()); option.exportString = option.userString; } formFieldChoice->m_options.emplace_back(qMove(option)); } } formFieldChoice->m_topIndex = loader.readIntegerFromDictionary(fieldDictionary, "TI", 0); } } 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