Commit c8f1e6bf14c4504fc52c9af8b62f31492f43127d
Committed by
GitHub
Merge pull request #1615 from m-holger/ffoh
Refactor QPDFAcroFormDocumentHelper
Showing
10 changed files
with
992 additions
and
520 deletions
include/qpdf/QPDFAcroFormDocumentHelper.hh
| @@ -225,21 +225,7 @@ class QPDFAcroFormDocumentHelper: public QPDFDocumentHelper | @@ -225,21 +225,7 @@ class QPDFAcroFormDocumentHelper: public QPDFDocumentHelper | ||
| 225 | std::set<QPDFObjGen>* new_fields = nullptr); | 225 | std::set<QPDFObjGen>* new_fields = nullptr); |
| 226 | 226 | ||
| 227 | private: | 227 | private: |
| 228 | - void analyze(); | ||
| 229 | - bool traverseField(QPDFObjectHandle field, QPDFObjectHandle const& parent, int depth); | ||
| 230 | - QPDFObjectHandle getOrCreateAcroForm(); | ||
| 231 | - void adjustInheritedFields( | ||
| 232 | - QPDFObjectHandle obj, | ||
| 233 | - bool override_da, | ||
| 234 | - std::string const& from_default_da, | ||
| 235 | - bool override_q, | ||
| 236 | - int from_default_q); | ||
| 237 | - void adjustDefaultAppearances( | ||
| 238 | - QPDFObjectHandle obj, | ||
| 239 | - std::map<std::string, std::map<std::string, std::string>> const& dr_map); | ||
| 240 | - void adjustAppearanceStream( | ||
| 241 | - QPDFObjectHandle stream, std::map<std::string, std::map<std::string, std::string>> dr_map); | ||
| 242 | - | 228 | + friend class QPDF::Doc; |
| 243 | class Members; | 229 | class Members; |
| 244 | 230 | ||
| 245 | std::shared_ptr<Members> m; | 231 | std::shared_ptr<Members> m; |
libqpdf/QPDF.cc
| @@ -11,6 +11,7 @@ | @@ -11,6 +11,7 @@ | ||
| 11 | #include <sstream> | 11 | #include <sstream> |
| 12 | #include <vector> | 12 | #include <vector> |
| 13 | 13 | ||
| 14 | +#include <qpdf/AcroForm.hh> | ||
| 14 | #include <qpdf/FileInputSource.hh> | 15 | #include <qpdf/FileInputSource.hh> |
| 15 | #include <qpdf/InputSource_private.hh> | 16 | #include <qpdf/InputSource_private.hh> |
| 16 | #include <qpdf/OffsetInputSource.hh> | 17 | #include <qpdf/OffsetInputSource.hh> |
| @@ -147,6 +148,21 @@ QPDF::QPDF() : | @@ -147,6 +148,21 @@ QPDF::QPDF() : | ||
| 147 | m->unique_id = unique_id.fetch_add(1ULL); | 148 | m->unique_id = unique_id.fetch_add(1ULL); |
| 148 | } | 149 | } |
| 149 | 150 | ||
| 151 | +/// @brief Initializes the AcroForm functionality for the document. | ||
| 152 | +/// @par | ||
| 153 | +/// This method creates a unique instance of QPDFAcroFormDocumentHelper and associates it | ||
| 154 | +/// with the document. It also updates the `acroform_` pointer to reference the AcroForm | ||
| 155 | +/// instance managed by the helper. | ||
| 156 | +/// | ||
| 157 | +/// The method has been separated out from `acroform` to avoid it being inlined | ||
| 158 | +/// unnecessarily. | ||
| 159 | +void | ||
| 160 | +QPDF::Doc::init_acroform() | ||
| 161 | +{ | ||
| 162 | + acroform_dh_ = std::make_unique<QPDFAcroFormDocumentHelper>(qpdf); | ||
| 163 | + acroform_ = acroform_dh_->m.get(); | ||
| 164 | +} | ||
| 165 | + | ||
| 150 | // Provide access to disconnect(). Disconnect will in due course be merged into the current ObjCache | 166 | // Provide access to disconnect(). Disconnect will in due course be merged into the current ObjCache |
| 151 | // (future Objects::Entry) to centralize all QPDF access to QPDFObject. | 167 | // (future Objects::Entry) to centralize all QPDF access to QPDFObject. |
| 152 | class Disconnect: BaseHandle | 168 | class Disconnect: BaseHandle |
libqpdf/QPDFAcroFormDocumentHelper.cc
| 1 | #include <qpdf/QPDFAcroFormDocumentHelper.hh> | 1 | #include <qpdf/QPDFAcroFormDocumentHelper.hh> |
| 2 | 2 | ||
| 3 | +#include <qpdf/AcroForm.hh> | ||
| 4 | + | ||
| 3 | #include <qpdf/Pl_Buffer.hh> | 5 | #include <qpdf/Pl_Buffer.hh> |
| 4 | #include <qpdf/QPDFObjectHandle_private.hh> | 6 | #include <qpdf/QPDFObjectHandle_private.hh> |
| 5 | #include <qpdf/QPDFPageDocumentHelper.hh> | 7 | #include <qpdf/QPDFPageDocumentHelper.hh> |
| @@ -7,51 +9,38 @@ | @@ -7,51 +9,38 @@ | ||
| 7 | #include <qpdf/QTC.hh> | 9 | #include <qpdf/QTC.hh> |
| 8 | #include <qpdf/QUtil.hh> | 10 | #include <qpdf/QUtil.hh> |
| 9 | #include <qpdf/ResourceFinder.hh> | 11 | #include <qpdf/ResourceFinder.hh> |
| 12 | +#include <qpdf/Util.hh> | ||
| 10 | 13 | ||
| 11 | #include <deque> | 14 | #include <deque> |
| 12 | #include <utility> | 15 | #include <utility> |
| 13 | 16 | ||
| 14 | using namespace qpdf; | 17 | using namespace qpdf; |
| 18 | +using namespace qpdf::impl; | ||
| 15 | using namespace std::literals; | 19 | using namespace std::literals; |
| 16 | 20 | ||
| 17 | -class QPDFAcroFormDocumentHelper::Members | ||
| 18 | -{ | ||
| 19 | - public: | ||
| 20 | - Members() = default; | ||
| 21 | - Members(Members const&) = delete; | ||
| 22 | - ~Members() = default; | ||
| 23 | - | ||
| 24 | - struct FieldData | ||
| 25 | - { | ||
| 26 | - std::vector<QPDFAnnotationObjectHelper> annotations; | ||
| 27 | - std::string name; | ||
| 28 | - }; | ||
| 29 | - | ||
| 30 | - bool cache_valid{false}; | ||
| 31 | - std::map<QPDFObjGen, FieldData> field_to; | ||
| 32 | - std::map<QPDFObjGen, QPDFFormFieldObjectHelper> annotation_to_field; | ||
| 33 | - std::map<std::string, std::set<QPDFObjGen>> name_to_fields; | ||
| 34 | - std::set<QPDFObjGen> bad_fields; | ||
| 35 | -}; | 21 | +using AcroForm = impl::AcroForm; |
| 36 | 22 | ||
| 37 | QPDFAcroFormDocumentHelper::QPDFAcroFormDocumentHelper(QPDF& qpdf) : | 23 | QPDFAcroFormDocumentHelper::QPDFAcroFormDocumentHelper(QPDF& qpdf) : |
| 38 | QPDFDocumentHelper(qpdf), | 24 | QPDFDocumentHelper(qpdf), |
| 39 | - m(std::make_shared<Members>()) | 25 | + m(std::make_shared<Members>(qpdf)) |
| 40 | { | 26 | { |
| 41 | - // We have to analyze up front. Otherwise, when we are adding annotations and fields, we are in | ||
| 42 | - // a temporarily unstable configuration where some widget annotations are not reachable. | ||
| 43 | - analyze(); | ||
| 44 | } | 27 | } |
| 45 | 28 | ||
| 46 | QPDFAcroFormDocumentHelper& | 29 | QPDFAcroFormDocumentHelper& |
| 47 | QPDFAcroFormDocumentHelper::get(QPDF& qpdf) | 30 | QPDFAcroFormDocumentHelper::get(QPDF& qpdf) |
| 48 | { | 31 | { |
| 49 | - return qpdf.doc().acroform(); | 32 | + return qpdf.doc().acroform_dh(); |
| 50 | } | 33 | } |
| 51 | 34 | ||
| 52 | void | 35 | void |
| 53 | QPDFAcroFormDocumentHelper::validate(bool repair) | 36 | QPDFAcroFormDocumentHelper::validate(bool repair) |
| 54 | { | 37 | { |
| 38 | + m->validate(repair); | ||
| 39 | +} | ||
| 40 | + | ||
| 41 | +void | ||
| 42 | +AcroForm::validate(bool repair) | ||
| 43 | +{ | ||
| 55 | invalidateCache(); | 44 | invalidateCache(); |
| 56 | analyze(); | 45 | analyze(); |
| 57 | } | 46 | } |
| @@ -59,19 +48,33 @@ QPDFAcroFormDocumentHelper::validate(bool repair) | @@ -59,19 +48,33 @@ QPDFAcroFormDocumentHelper::validate(bool repair) | ||
| 59 | void | 48 | void |
| 60 | QPDFAcroFormDocumentHelper::invalidateCache() | 49 | QPDFAcroFormDocumentHelper::invalidateCache() |
| 61 | { | 50 | { |
| 62 | - m->cache_valid = false; | ||
| 63 | - m->field_to.clear(); | ||
| 64 | - m->annotation_to_field.clear(); | 51 | + m->invalidateCache(); |
| 52 | +} | ||
| 53 | + | ||
| 54 | +void | ||
| 55 | +AcroForm::invalidateCache() | ||
| 56 | +{ | ||
| 57 | + cache_valid_ = false; | ||
| 58 | + fields_.clear(); | ||
| 59 | + annotation_to_field_.clear(); | ||
| 60 | + bad_fields_.clear(); | ||
| 61 | + name_to_fields_.clear(); | ||
| 65 | } | 62 | } |
| 66 | 63 | ||
| 67 | bool | 64 | bool |
| 68 | QPDFAcroFormDocumentHelper::hasAcroForm() | 65 | QPDFAcroFormDocumentHelper::hasAcroForm() |
| 69 | { | 66 | { |
| 67 | + return m->hasAcroForm(); | ||
| 68 | +} | ||
| 69 | + | ||
| 70 | +bool | ||
| 71 | +AcroForm::hasAcroForm() | ||
| 72 | +{ | ||
| 70 | return qpdf.getRoot().hasKey("/AcroForm"); | 73 | return qpdf.getRoot().hasKey("/AcroForm"); |
| 71 | } | 74 | } |
| 72 | 75 | ||
| 73 | QPDFObjectHandle | 76 | QPDFObjectHandle |
| 74 | -QPDFAcroFormDocumentHelper::getOrCreateAcroForm() | 77 | +AcroForm::getOrCreateAcroForm() |
| 75 | { | 78 | { |
| 76 | auto acroform = qpdf.getRoot().getKey("/AcroForm"); | 79 | auto acroform = qpdf.getRoot().getKey("/AcroForm"); |
| 77 | if (!acroform.isDictionary()) { | 80 | if (!acroform.isDictionary()) { |
| @@ -84,6 +87,12 @@ QPDFAcroFormDocumentHelper::getOrCreateAcroForm() | @@ -84,6 +87,12 @@ QPDFAcroFormDocumentHelper::getOrCreateAcroForm() | ||
| 84 | void | 87 | void |
| 85 | QPDFAcroFormDocumentHelper::addFormField(QPDFFormFieldObjectHelper ff) | 88 | QPDFAcroFormDocumentHelper::addFormField(QPDFFormFieldObjectHelper ff) |
| 86 | { | 89 | { |
| 90 | + m->addFormField(ff); | ||
| 91 | +} | ||
| 92 | + | ||
| 93 | +void | ||
| 94 | +AcroForm::addFormField(QPDFFormFieldObjectHelper ff) | ||
| 95 | +{ | ||
| 87 | auto acroform = getOrCreateAcroForm(); | 96 | auto acroform = getOrCreateAcroForm(); |
| 88 | auto fields = acroform.getKey("/Fields"); | 97 | auto fields = acroform.getKey("/Fields"); |
| 89 | if (!fields.isArray()) { | 98 | if (!fields.isArray()) { |
| @@ -96,6 +105,12 @@ QPDFAcroFormDocumentHelper::addFormField(QPDFFormFieldObjectHelper ff) | @@ -96,6 +105,12 @@ QPDFAcroFormDocumentHelper::addFormField(QPDFFormFieldObjectHelper ff) | ||
| 96 | void | 105 | void |
| 97 | QPDFAcroFormDocumentHelper::addAndRenameFormFields(std::vector<QPDFObjectHandle> fields) | 106 | QPDFAcroFormDocumentHelper::addAndRenameFormFields(std::vector<QPDFObjectHandle> fields) |
| 98 | { | 107 | { |
| 108 | + m->addAndRenameFormFields(fields); | ||
| 109 | +} | ||
| 110 | + | ||
| 111 | +void | ||
| 112 | +AcroForm::addAndRenameFormFields(std::vector<QPDFObjectHandle> fields) | ||
| 113 | +{ | ||
| 99 | analyze(); | 114 | analyze(); |
| 100 | std::map<std::string, std::string> renames; | 115 | std::map<std::string, std::string> renames; |
| 101 | QPDFObjGen::set seen; | 116 | QPDFObjGen::set seen; |
| @@ -147,6 +162,12 @@ QPDFAcroFormDocumentHelper::addAndRenameFormFields(std::vector<QPDFObjectHandle> | @@ -147,6 +162,12 @@ QPDFAcroFormDocumentHelper::addAndRenameFormFields(std::vector<QPDFObjectHandle> | ||
| 147 | void | 162 | void |
| 148 | QPDFAcroFormDocumentHelper::removeFormFields(std::set<QPDFObjGen> const& to_remove) | 163 | QPDFAcroFormDocumentHelper::removeFormFields(std::set<QPDFObjGen> const& to_remove) |
| 149 | { | 164 | { |
| 165 | + m->removeFormFields(to_remove); | ||
| 166 | +} | ||
| 167 | + | ||
| 168 | +void | ||
| 169 | +AcroForm::removeFormFields(std::set<QPDFObjGen> const& to_remove) | ||
| 170 | +{ | ||
| 150 | auto acroform = qpdf.getRoot().getKey("/AcroForm"); | 171 | auto acroform = qpdf.getRoot().getKey("/AcroForm"); |
| 151 | if (!acroform.isDictionary()) { | 172 | if (!acroform.isDictionary()) { |
| 152 | return; | 173 | return; |
| @@ -157,19 +178,19 @@ QPDFAcroFormDocumentHelper::removeFormFields(std::set<QPDFObjGen> const& to_remo | @@ -157,19 +178,19 @@ QPDFAcroFormDocumentHelper::removeFormFields(std::set<QPDFObjGen> const& to_remo | ||
| 157 | } | 178 | } |
| 158 | 179 | ||
| 159 | for (auto const& og: to_remove) { | 180 | for (auto const& og: to_remove) { |
| 160 | - auto it = m->field_to.find(og); | ||
| 161 | - if (it != m->field_to.end()) { | 181 | + auto it = fields_.find(og); |
| 182 | + if (it != fields_.end()) { | ||
| 162 | for (auto aoh: it->second.annotations) { | 183 | for (auto aoh: it->second.annotations) { |
| 163 | - m->annotation_to_field.erase(aoh.getObjectHandle().getObjGen()); | 184 | + annotation_to_field_.erase(aoh.getObjectHandle().getObjGen()); |
| 164 | } | 185 | } |
| 165 | auto const& name = it->second.name; | 186 | auto const& name = it->second.name; |
| 166 | if (!name.empty()) { | 187 | if (!name.empty()) { |
| 167 | - m->name_to_fields[name].erase(og); | ||
| 168 | - if (m->name_to_fields[name].empty()) { | ||
| 169 | - m->name_to_fields.erase(name); | 188 | + name_to_fields_[name].erase(og); |
| 189 | + if (name_to_fields_[name].empty()) { | ||
| 190 | + name_to_fields_.erase(name); | ||
| 170 | } | 191 | } |
| 171 | } | 192 | } |
| 172 | - m->field_to.erase(og); | 193 | + fields_.erase(og); |
| 173 | } | 194 | } |
| 174 | } | 195 | } |
| 175 | 196 | ||
| @@ -187,6 +208,12 @@ QPDFAcroFormDocumentHelper::removeFormFields(std::set<QPDFObjGen> const& to_remo | @@ -187,6 +208,12 @@ QPDFAcroFormDocumentHelper::removeFormFields(std::set<QPDFObjGen> const& to_remo | ||
| 187 | void | 208 | void |
| 188 | QPDFAcroFormDocumentHelper::setFormFieldName(QPDFFormFieldObjectHelper ff, std::string const& name) | 209 | QPDFAcroFormDocumentHelper::setFormFieldName(QPDFFormFieldObjectHelper ff, std::string const& name) |
| 189 | { | 210 | { |
| 211 | + m->setFormFieldName(ff, name); | ||
| 212 | +} | ||
| 213 | + | ||
| 214 | +void | ||
| 215 | +AcroForm::setFormFieldName(QPDFFormFieldObjectHelper ff, std::string const& name) | ||
| 216 | +{ | ||
| 190 | ff.setFieldAttribute("/T", name); | 217 | ff.setFieldAttribute("/T", name); |
| 191 | traverseField(ff, ff["/Parent"], 0); | 218 | traverseField(ff, ff["/Parent"], 0); |
| 192 | } | 219 | } |
| @@ -194,9 +221,15 @@ QPDFAcroFormDocumentHelper::setFormFieldName(QPDFFormFieldObjectHelper ff, std:: | @@ -194,9 +221,15 @@ QPDFAcroFormDocumentHelper::setFormFieldName(QPDFFormFieldObjectHelper ff, std:: | ||
| 194 | std::vector<QPDFFormFieldObjectHelper> | 221 | std::vector<QPDFFormFieldObjectHelper> |
| 195 | QPDFAcroFormDocumentHelper::getFormFields() | 222 | QPDFAcroFormDocumentHelper::getFormFields() |
| 196 | { | 223 | { |
| 224 | + return m->getFormFields(); | ||
| 225 | +} | ||
| 226 | + | ||
| 227 | +std::vector<QPDFFormFieldObjectHelper> | ||
| 228 | +AcroForm::getFormFields() | ||
| 229 | +{ | ||
| 197 | analyze(); | 230 | analyze(); |
| 198 | std::vector<QPDFFormFieldObjectHelper> result; | 231 | std::vector<QPDFFormFieldObjectHelper> result; |
| 199 | - for (auto const& [og, data]: m->field_to) { | 232 | + for (auto const& [og, data]: fields_) { |
| 200 | if (!data.annotations.empty()) { | 233 | if (!data.annotations.empty()) { |
| 201 | result.emplace_back(qpdf.getObject(og)); | 234 | result.emplace_back(qpdf.getObject(og)); |
| 202 | } | 235 | } |
| @@ -207,10 +240,16 @@ QPDFAcroFormDocumentHelper::getFormFields() | @@ -207,10 +240,16 @@ QPDFAcroFormDocumentHelper::getFormFields() | ||
| 207 | std::set<QPDFObjGen> | 240 | std::set<QPDFObjGen> |
| 208 | QPDFAcroFormDocumentHelper::getFieldsWithQualifiedName(std::string const& name) | 241 | QPDFAcroFormDocumentHelper::getFieldsWithQualifiedName(std::string const& name) |
| 209 | { | 242 | { |
| 243 | + return m->getFieldsWithQualifiedName(name); | ||
| 244 | +} | ||
| 245 | + | ||
| 246 | +std::set<QPDFObjGen> | ||
| 247 | +AcroForm::getFieldsWithQualifiedName(std::string const& name) | ||
| 248 | +{ | ||
| 210 | analyze(); | 249 | analyze(); |
| 211 | // Keep from creating an empty entry | 250 | // Keep from creating an empty entry |
| 212 | - auto iter = m->name_to_fields.find(name); | ||
| 213 | - if (iter != m->name_to_fields.end()) { | 251 | + auto iter = name_to_fields_.find(name); |
| 252 | + if (iter != name_to_fields_.end()) { | ||
| 214 | return iter->second; | 253 | return iter->second; |
| 215 | } | 254 | } |
| 216 | return {}; | 255 | return {}; |
| @@ -219,11 +258,17 @@ QPDFAcroFormDocumentHelper::getFieldsWithQualifiedName(std::string const& name) | @@ -219,11 +258,17 @@ QPDFAcroFormDocumentHelper::getFieldsWithQualifiedName(std::string const& name) | ||
| 219 | std::vector<QPDFAnnotationObjectHelper> | 258 | std::vector<QPDFAnnotationObjectHelper> |
| 220 | QPDFAcroFormDocumentHelper::getAnnotationsForField(QPDFFormFieldObjectHelper h) | 259 | QPDFAcroFormDocumentHelper::getAnnotationsForField(QPDFFormFieldObjectHelper h) |
| 221 | { | 260 | { |
| 261 | + return m->getAnnotationsForField(h); | ||
| 262 | +} | ||
| 263 | + | ||
| 264 | +std::vector<QPDFAnnotationObjectHelper> | ||
| 265 | +AcroForm::getAnnotationsForField(QPDFFormFieldObjectHelper h) | ||
| 266 | +{ | ||
| 222 | analyze(); | 267 | analyze(); |
| 223 | std::vector<QPDFAnnotationObjectHelper> result; | 268 | std::vector<QPDFAnnotationObjectHelper> result; |
| 224 | QPDFObjGen og(h.getObjectHandle().getObjGen()); | 269 | QPDFObjGen og(h.getObjectHandle().getObjGen()); |
| 225 | - if (m->field_to.contains(og)) { | ||
| 226 | - result = m->field_to[og].annotations; | 270 | + if (fields_.contains(og)) { |
| 271 | + result = fields_[og].annotations; | ||
| 227 | } | 272 | } |
| 228 | return result; | 273 | return result; |
| 229 | } | 274 | } |
| @@ -231,12 +276,24 @@ QPDFAcroFormDocumentHelper::getAnnotationsForField(QPDFFormFieldObjectHelper h) | @@ -231,12 +276,24 @@ QPDFAcroFormDocumentHelper::getAnnotationsForField(QPDFFormFieldObjectHelper h) | ||
| 231 | std::vector<QPDFAnnotationObjectHelper> | 276 | std::vector<QPDFAnnotationObjectHelper> |
| 232 | QPDFAcroFormDocumentHelper::getWidgetAnnotationsForPage(QPDFPageObjectHelper h) | 277 | QPDFAcroFormDocumentHelper::getWidgetAnnotationsForPage(QPDFPageObjectHelper h) |
| 233 | { | 278 | { |
| 279 | + return m->getWidgetAnnotationsForPage(h); | ||
| 280 | +} | ||
| 281 | + | ||
| 282 | +std::vector<QPDFAnnotationObjectHelper> | ||
| 283 | +AcroForm::getWidgetAnnotationsForPage(QPDFPageObjectHelper h) | ||
| 284 | +{ | ||
| 234 | return h.getAnnotations("/Widget"); | 285 | return h.getAnnotations("/Widget"); |
| 235 | } | 286 | } |
| 236 | 287 | ||
| 237 | std::vector<QPDFFormFieldObjectHelper> | 288 | std::vector<QPDFFormFieldObjectHelper> |
| 238 | QPDFAcroFormDocumentHelper::getFormFieldsForPage(QPDFPageObjectHelper ph) | 289 | QPDFAcroFormDocumentHelper::getFormFieldsForPage(QPDFPageObjectHelper ph) |
| 239 | { | 290 | { |
| 291 | + return m->getFormFieldsForPage(ph); | ||
| 292 | +} | ||
| 293 | + | ||
| 294 | +std::vector<QPDFFormFieldObjectHelper> | ||
| 295 | +AcroForm::getFormFieldsForPage(QPDFPageObjectHelper ph) | ||
| 296 | +{ | ||
| 240 | analyze(); | 297 | analyze(); |
| 241 | QPDFObjGen::set todo; | 298 | QPDFObjGen::set todo; |
| 242 | std::vector<QPDFFormFieldObjectHelper> result; | 299 | std::vector<QPDFFormFieldObjectHelper> result; |
| @@ -252,25 +309,31 @@ QPDFAcroFormDocumentHelper::getFormFieldsForPage(QPDFPageObjectHelper ph) | @@ -252,25 +309,31 @@ QPDFAcroFormDocumentHelper::getFormFieldsForPage(QPDFPageObjectHelper ph) | ||
| 252 | QPDFFormFieldObjectHelper | 309 | QPDFFormFieldObjectHelper |
| 253 | QPDFAcroFormDocumentHelper::getFieldForAnnotation(QPDFAnnotationObjectHelper h) | 310 | QPDFAcroFormDocumentHelper::getFieldForAnnotation(QPDFAnnotationObjectHelper h) |
| 254 | { | 311 | { |
| 312 | + return m->getFieldForAnnotation(h); | ||
| 313 | +} | ||
| 314 | + | ||
| 315 | +QPDFFormFieldObjectHelper | ||
| 316 | +AcroForm::getFieldForAnnotation(QPDFAnnotationObjectHelper h) | ||
| 317 | +{ | ||
| 255 | QPDFObjectHandle oh = h.getObjectHandle(); | 318 | QPDFObjectHandle oh = h.getObjectHandle(); |
| 256 | if (!oh.isDictionaryOfType("", "/Widget")) { | 319 | if (!oh.isDictionaryOfType("", "/Widget")) { |
| 257 | return Null::temp(); | 320 | return Null::temp(); |
| 258 | } | 321 | } |
| 259 | analyze(); | 322 | analyze(); |
| 260 | QPDFObjGen og(oh.getObjGen()); | 323 | QPDFObjGen og(oh.getObjGen()); |
| 261 | - if (m->annotation_to_field.contains(og)) { | ||
| 262 | - return m->annotation_to_field[og]; | 324 | + if (annotation_to_field_.contains(og)) { |
| 325 | + return annotation_to_field_[og]; | ||
| 263 | } | 326 | } |
| 264 | return Null::temp(); | 327 | return Null::temp(); |
| 265 | } | 328 | } |
| 266 | 329 | ||
| 267 | void | 330 | void |
| 268 | -QPDFAcroFormDocumentHelper::analyze() | 331 | +AcroForm::analyze() |
| 269 | { | 332 | { |
| 270 | - if (m->cache_valid) { | 333 | + if (cache_valid_) { |
| 271 | return; | 334 | return; |
| 272 | } | 335 | } |
| 273 | - m->cache_valid = true; | 336 | + cache_valid_ = true; |
| 274 | QPDFObjectHandle acroform = qpdf.getRoot().getKey("/AcroForm"); | 337 | QPDFObjectHandle acroform = qpdf.getRoot().getKey("/AcroForm"); |
| 275 | if (!(acroform.isDictionary() && acroform.hasKey("/Fields"))) { | 338 | if (!(acroform.isDictionary() && acroform.hasKey("/Fields"))) { |
| 276 | return; | 339 | return; |
| @@ -293,11 +356,11 @@ QPDFAcroFormDocumentHelper::analyze() | @@ -293,11 +356,11 @@ QPDFAcroFormDocumentHelper::analyze() | ||
| 293 | // a file that contains this kind of error will probably not | 356 | // a file that contains this kind of error will probably not |
| 294 | // actually work with most viewers. | 357 | // actually work with most viewers. |
| 295 | 358 | ||
| 296 | - for (auto const& ph: QPDFPageDocumentHelper(qpdf).getAllPages()) { | 359 | + for (QPDFPageObjectHelper ph: pages) { |
| 297 | for (auto const& iter: getWidgetAnnotationsForPage(ph)) { | 360 | for (auto const& iter: getWidgetAnnotationsForPage(ph)) { |
| 298 | QPDFObjectHandle annot(iter.getObjectHandle()); | 361 | QPDFObjectHandle annot(iter.getObjectHandle()); |
| 299 | QPDFObjGen og(annot.getObjGen()); | 362 | QPDFObjGen og(annot.getObjGen()); |
| 300 | - if (!m->annotation_to_field.contains(og)) { | 363 | + if (!annotation_to_field_.contains(og)) { |
| 301 | // This is not supposed to happen, but it's easy enough for us to handle this case. | 364 | // This is not supposed to happen, but it's easy enough for us to handle this case. |
| 302 | // Treat the annotation as its own field. This could allow qpdf to sensibly handle a | 365 | // Treat the annotation as its own field. This could allow qpdf to sensibly handle a |
| 303 | // case such as a PDF creator adding a self-contained annotation (merged with the | 366 | // case such as a PDF creator adding a self-contained annotation (merged with the |
| @@ -306,16 +369,15 @@ QPDFAcroFormDocumentHelper::analyze() | @@ -306,16 +369,15 @@ QPDFAcroFormDocumentHelper::analyze() | ||
| 306 | annot.warn( | 369 | annot.warn( |
| 307 | "this widget annotation is not reachable from /AcroForm in the document " | 370 | "this widget annotation is not reachable from /AcroForm in the document " |
| 308 | "catalog"); | 371 | "catalog"); |
| 309 | - m->annotation_to_field[og] = QPDFFormFieldObjectHelper(annot); | ||
| 310 | - m->field_to[og].annotations.emplace_back(annot); | 372 | + annotation_to_field_[og] = QPDFFormFieldObjectHelper(annot); |
| 373 | + fields_[og].annotations.emplace_back(annot); | ||
| 311 | } | 374 | } |
| 312 | } | 375 | } |
| 313 | } | 376 | } |
| 314 | } | 377 | } |
| 315 | 378 | ||
| 316 | bool | 379 | bool |
| 317 | -QPDFAcroFormDocumentHelper::traverseField( | ||
| 318 | - QPDFObjectHandle field, QPDFObjectHandle const& parent, int depth) | 380 | +AcroForm::traverseField(QPDFObjectHandle field, QPDFObjectHandle const& parent, int depth) |
| 319 | { | 381 | { |
| 320 | if (depth > 100) { | 382 | if (depth > 100) { |
| 321 | // Arbitrarily cut off recursion at a fixed depth to avoid specially crafted files that | 383 | // Arbitrarily cut off recursion at a fixed depth to avoid specially crafted files that |
| @@ -339,8 +401,7 @@ QPDFAcroFormDocumentHelper::traverseField( | @@ -339,8 +401,7 @@ QPDFAcroFormDocumentHelper::traverseField( | ||
| 339 | return false; | 401 | return false; |
| 340 | } | 402 | } |
| 341 | QPDFObjGen og(field.getObjGen()); | 403 | QPDFObjGen og(field.getObjGen()); |
| 342 | - if (m->field_to.contains(og) || m->annotation_to_field.contains(og) || | ||
| 343 | - m->bad_fields.contains(og)) { | 404 | + if (fields_.contains(og) || annotation_to_field_.contains(og) || bad_fields_.contains(og)) { |
| 344 | field.warn("loop detected while traversing /AcroForm"); | 405 | field.warn("loop detected while traversing /AcroForm"); |
| 345 | return false; | 406 | return false; |
| 346 | } | 407 | } |
| @@ -368,8 +429,8 @@ QPDFAcroFormDocumentHelper::traverseField( | @@ -368,8 +429,8 @@ QPDFAcroFormDocumentHelper::traverseField( | ||
| 368 | 429 | ||
| 369 | if (is_annotation) { | 430 | if (is_annotation) { |
| 370 | QPDFObjectHandle our_field = (is_field ? field : parent); | 431 | QPDFObjectHandle our_field = (is_field ? field : parent); |
| 371 | - m->field_to[our_field.getObjGen()].annotations.emplace_back(field); | ||
| 372 | - m->annotation_to_field[og] = QPDFFormFieldObjectHelper(our_field); | 432 | + fields_[our_field.getObjGen()].annotations.emplace_back(field); |
| 433 | + annotation_to_field_[og] = QPDFFormFieldObjectHelper(our_field); | ||
| 373 | } | 434 | } |
| 374 | 435 | ||
| 375 | if (is_field && depth != 0 && field["/Parent"] != parent) { | 436 | if (is_field && depth != 0 && field["/Parent"] != parent) { |
| @@ -392,22 +453,22 @@ QPDFAcroFormDocumentHelper::traverseField( | @@ -392,22 +453,22 @@ QPDFAcroFormDocumentHelper::traverseField( | ||
| 392 | if (is_field && field.hasKey("/T")) { | 453 | if (is_field && field.hasKey("/T")) { |
| 393 | QPDFFormFieldObjectHelper foh(field); | 454 | QPDFFormFieldObjectHelper foh(field); |
| 394 | std::string name = foh.getFullyQualifiedName(); | 455 | std::string name = foh.getFullyQualifiedName(); |
| 395 | - auto old = m->field_to.find(og); | ||
| 396 | - if (old != m->field_to.end() && !old->second.name.empty()) { | 456 | + auto old = fields_.find(og); |
| 457 | + if (old != fields_.end() && !old->second.name.empty()) { | ||
| 397 | // We might be updating after a name change, so remove any old information | 458 | // We might be updating after a name change, so remove any old information |
| 398 | - m->name_to_fields[old->second.name].erase(og); | 459 | + name_to_fields_[old->second.name].erase(og); |
| 399 | } | 460 | } |
| 400 | - m->field_to[og].name = name; | ||
| 401 | - m->name_to_fields[name].insert(og); | 461 | + fields_[og].name = name; |
| 462 | + name_to_fields_[name].insert(og); | ||
| 402 | } | 463 | } |
| 403 | 464 | ||
| 404 | for (auto const& kid: Kids) { | 465 | for (auto const& kid: Kids) { |
| 405 | - if (m->bad_fields.contains(kid)) { | 466 | + if (bad_fields_.contains(kid)) { |
| 406 | continue; | 467 | continue; |
| 407 | } | 468 | } |
| 408 | 469 | ||
| 409 | if (!traverseField(kid, field, 1 + depth)) { | 470 | if (!traverseField(kid, field, 1 + depth)) { |
| 410 | - m->bad_fields.insert(kid); | 471 | + bad_fields_.insert(kid); |
| 411 | } | 472 | } |
| 412 | } | 473 | } |
| 413 | return true; | 474 | return true; |
| @@ -416,6 +477,12 @@ QPDFAcroFormDocumentHelper::traverseField( | @@ -416,6 +477,12 @@ QPDFAcroFormDocumentHelper::traverseField( | ||
| 416 | bool | 477 | bool |
| 417 | QPDFAcroFormDocumentHelper::getNeedAppearances() | 478 | QPDFAcroFormDocumentHelper::getNeedAppearances() |
| 418 | { | 479 | { |
| 480 | + return m->getNeedAppearances(); | ||
| 481 | +} | ||
| 482 | + | ||
| 483 | +bool | ||
| 484 | +AcroForm::getNeedAppearances() | ||
| 485 | +{ | ||
| 419 | bool result = false; | 486 | bool result = false; |
| 420 | QPDFObjectHandle acroform = qpdf.getRoot().getKey("/AcroForm"); | 487 | QPDFObjectHandle acroform = qpdf.getRoot().getKey("/AcroForm"); |
| 421 | if (acroform.isDictionary() && acroform.getKey("/NeedAppearances").isBool()) { | 488 | if (acroform.isDictionary() && acroform.getKey("/NeedAppearances").isBool()) { |
| @@ -427,6 +494,12 @@ QPDFAcroFormDocumentHelper::getNeedAppearances() | @@ -427,6 +494,12 @@ QPDFAcroFormDocumentHelper::getNeedAppearances() | ||
| 427 | void | 494 | void |
| 428 | QPDFAcroFormDocumentHelper::setNeedAppearances(bool val) | 495 | QPDFAcroFormDocumentHelper::setNeedAppearances(bool val) |
| 429 | { | 496 | { |
| 497 | + m->setNeedAppearances(val); | ||
| 498 | +} | ||
| 499 | + | ||
| 500 | +void | ||
| 501 | +AcroForm::setNeedAppearances(bool val) | ||
| 502 | +{ | ||
| 430 | QPDFObjectHandle acroform = qpdf.getRoot().getKey("/AcroForm"); | 503 | QPDFObjectHandle acroform = qpdf.getRoot().getKey("/AcroForm"); |
| 431 | if (!acroform.isDictionary()) { | 504 | if (!acroform.isDictionary()) { |
| 432 | qpdf.getRoot().warn( | 505 | qpdf.getRoot().warn( |
| @@ -444,6 +517,12 @@ QPDFAcroFormDocumentHelper::setNeedAppearances(bool val) | @@ -444,6 +517,12 @@ QPDFAcroFormDocumentHelper::setNeedAppearances(bool val) | ||
| 444 | void | 517 | void |
| 445 | QPDFAcroFormDocumentHelper::generateAppearancesIfNeeded() | 518 | QPDFAcroFormDocumentHelper::generateAppearancesIfNeeded() |
| 446 | { | 519 | { |
| 520 | + m->generateAppearancesIfNeeded(); | ||
| 521 | +} | ||
| 522 | + | ||
| 523 | +void | ||
| 524 | +AcroForm::generateAppearancesIfNeeded() | ||
| 525 | +{ | ||
| 447 | if (!getNeedAppearances()) { | 526 | if (!getNeedAppearances()) { |
| 448 | return; | 527 | return; |
| 449 | } | 528 | } |
| @@ -470,6 +549,12 @@ QPDFAcroFormDocumentHelper::generateAppearancesIfNeeded() | @@ -470,6 +549,12 @@ QPDFAcroFormDocumentHelper::generateAppearancesIfNeeded() | ||
| 470 | void | 549 | void |
| 471 | QPDFAcroFormDocumentHelper::disableDigitalSignatures() | 550 | QPDFAcroFormDocumentHelper::disableDigitalSignatures() |
| 472 | { | 551 | { |
| 552 | + m->disableDigitalSignatures(); | ||
| 553 | +} | ||
| 554 | + | ||
| 555 | +void | ||
| 556 | +AcroForm::disableDigitalSignatures() | ||
| 557 | +{ | ||
| 473 | qpdf.removeSecurityRestrictions(); | 558 | qpdf.removeSecurityRestrictions(); |
| 474 | std::set<QPDFObjGen> to_remove; | 559 | std::set<QPDFObjGen> to_remove; |
| 475 | auto fields = getFormFields(); | 560 | auto fields = getFormFields(); |
| @@ -491,7 +576,7 @@ QPDFAcroFormDocumentHelper::disableDigitalSignatures() | @@ -491,7 +576,7 @@ QPDFAcroFormDocumentHelper::disableDigitalSignatures() | ||
| 491 | } | 576 | } |
| 492 | 577 | ||
| 493 | void | 578 | void |
| 494 | -QPDFAcroFormDocumentHelper::adjustInheritedFields( | 579 | +AcroForm::adjustInheritedFields( |
| 495 | QPDFObjectHandle obj, | 580 | QPDFObjectHandle obj, |
| 496 | bool override_da, | 581 | bool override_da, |
| 497 | std::string const& from_default_da, | 582 | std::string const& from_default_da, |
| @@ -598,7 +683,7 @@ ResourceReplacer::handleToken(QPDFTokenizer::Token const& token) | @@ -598,7 +683,7 @@ ResourceReplacer::handleToken(QPDFTokenizer::Token const& token) | ||
| 598 | } | 683 | } |
| 599 | 684 | ||
| 600 | void | 685 | void |
| 601 | -QPDFAcroFormDocumentHelper::adjustDefaultAppearances( | 686 | +AcroForm::adjustDefaultAppearances( |
| 602 | QPDFObjectHandle obj, std::map<std::string, std::map<std::string, std::string>> const& dr_map) | 687 | QPDFObjectHandle obj, std::map<std::string, std::map<std::string, std::string>> const& dr_map) |
| 603 | { | 688 | { |
| 604 | // This method is called on a field that has been copied from another file but whose /DA still | 689 | // This method is called on a field that has been copied from another file but whose /DA still |
| @@ -656,7 +741,7 @@ QPDFAcroFormDocumentHelper::adjustDefaultAppearances( | @@ -656,7 +741,7 @@ QPDFAcroFormDocumentHelper::adjustDefaultAppearances( | ||
| 656 | } | 741 | } |
| 657 | 742 | ||
| 658 | void | 743 | void |
| 659 | -QPDFAcroFormDocumentHelper::adjustAppearanceStream( | 744 | +AcroForm::adjustAppearanceStream( |
| 660 | QPDFObjectHandle stream, std::map<std::string, std::map<std::string, std::string>> dr_map) | 745 | QPDFObjectHandle stream, std::map<std::string, std::map<std::string, std::string>> dr_map) |
| 661 | { | 746 | { |
| 662 | // We don't have to modify appearance streams or their resource dictionaries for them to display | 747 | // We don't have to modify appearance streams or their resource dictionaries for them to display |
| @@ -748,7 +833,6 @@ QPDFAcroFormDocumentHelper::transformAnnotations( | @@ -748,7 +833,6 @@ QPDFAcroFormDocumentHelper::transformAnnotations( | ||
| 748 | QPDF* from_qpdf, | 833 | QPDF* from_qpdf, |
| 749 | QPDFAcroFormDocumentHelper* from_afdh) | 834 | QPDFAcroFormDocumentHelper* from_afdh) |
| 750 | { | 835 | { |
| 751 | - Array old_annots = std::move(a_old_annots); | ||
| 752 | if (!from_qpdf) { | 836 | if (!from_qpdf) { |
| 753 | // Assume these are from the same QPDF. | 837 | // Assume these are from the same QPDF. |
| 754 | from_qpdf = &qpdf; | 838 | from_qpdf = &qpdf; |
| @@ -756,6 +840,23 @@ QPDFAcroFormDocumentHelper::transformAnnotations( | @@ -756,6 +840,23 @@ QPDFAcroFormDocumentHelper::transformAnnotations( | ||
| 756 | } else if (from_qpdf != &qpdf && !from_afdh) { | 840 | } else if (from_qpdf != &qpdf && !from_afdh) { |
| 757 | from_afdh = &QPDFAcroFormDocumentHelper::get(*from_qpdf); | 841 | from_afdh = &QPDFAcroFormDocumentHelper::get(*from_qpdf); |
| 758 | } | 842 | } |
| 843 | + m->transformAnnotations( | ||
| 844 | + a_old_annots, new_annots, new_fields, old_fields, cm, from_qpdf, from_afdh->m.get()); | ||
| 845 | +} | ||
| 846 | + | ||
| 847 | +void | ||
| 848 | +AcroForm::transformAnnotations( | ||
| 849 | + QPDFObjectHandle a_old_annots, | ||
| 850 | + std::vector<QPDFObjectHandle>& new_annots, | ||
| 851 | + std::vector<QPDFObjectHandle>& new_fields, | ||
| 852 | + std::set<QPDFObjGen>& old_fields, | ||
| 853 | + QPDFMatrix const& cm, | ||
| 854 | + QPDF* from_qpdf, | ||
| 855 | + AcroForm* from_afdh) | ||
| 856 | +{ | ||
| 857 | + qpdf_expect(from_qpdf); | ||
| 858 | + qpdf_expect(from_afdh); | ||
| 859 | + Array old_annots = std::move(a_old_annots); | ||
| 759 | const bool foreign = from_qpdf != &qpdf; | 860 | const bool foreign = from_qpdf != &qpdf; |
| 760 | 861 | ||
| 761 | // It's possible that we will transform annotations that don't include any form fields. This | 862 | // It's possible that we will transform annotations that don't include any form fields. This |
| @@ -1056,6 +1157,16 @@ QPDFAcroFormDocumentHelper::fixCopiedAnnotations( | @@ -1056,6 +1157,16 @@ QPDFAcroFormDocumentHelper::fixCopiedAnnotations( | ||
| 1056 | QPDFAcroFormDocumentHelper& from_afdh, | 1157 | QPDFAcroFormDocumentHelper& from_afdh, |
| 1057 | std::set<QPDFObjGen>* added_fields) | 1158 | std::set<QPDFObjGen>* added_fields) |
| 1058 | { | 1159 | { |
| 1160 | + m->fixCopiedAnnotations(to_page, from_page, *from_afdh.m, added_fields); | ||
| 1161 | +} | ||
| 1162 | + | ||
| 1163 | +void | ||
| 1164 | +AcroForm::fixCopiedAnnotations( | ||
| 1165 | + QPDFObjectHandle to_page, | ||
| 1166 | + QPDFObjectHandle from_page, | ||
| 1167 | + AcroForm& from_afdh, | ||
| 1168 | + std::set<QPDFObjGen>* added_fields) | ||
| 1169 | +{ | ||
| 1059 | auto old_annots = from_page.getKey("/Annots"); | 1170 | auto old_annots = from_page.getKey("/Annots"); |
| 1060 | if (old_annots.empty() || !old_annots.isArray()) { | 1171 | if (old_annots.empty() || !old_annots.isArray()) { |
| 1061 | return; | 1172 | return; |
| @@ -1070,7 +1181,7 @@ QPDFAcroFormDocumentHelper::fixCopiedAnnotations( | @@ -1070,7 +1181,7 @@ QPDFAcroFormDocumentHelper::fixCopiedAnnotations( | ||
| 1070 | new_fields, | 1181 | new_fields, |
| 1071 | old_fields, | 1182 | old_fields, |
| 1072 | QPDFMatrix(), | 1183 | QPDFMatrix(), |
| 1073 | - &(from_afdh.getQPDF()), | 1184 | + &(from_afdh.qpdf), |
| 1074 | &from_afdh); | 1185 | &from_afdh); |
| 1075 | 1186 | ||
| 1076 | to_page.replaceKey("/Annots", QPDFObjectHandle::newArray(new_annots)); | 1187 | to_page.replaceKey("/Annots", QPDFObjectHandle::newArray(new_annots)); |
libqpdf/QPDFFormFieldObjectHelper.cc
| 1 | #include <qpdf/QPDFFormFieldObjectHelper.hh> | 1 | #include <qpdf/QPDFFormFieldObjectHelper.hh> |
| 2 | 2 | ||
| 3 | -#include <qpdf/FormField.hh> | 3 | +#include <qpdf/AcroForm.hh> |
| 4 | 4 | ||
| 5 | #include <qpdf/Pl_QPDFTokenizer.hh> | 5 | #include <qpdf/Pl_QPDFTokenizer.hh> |
| 6 | #include <qpdf/QIntC.hh> | 6 | #include <qpdf/QIntC.hh> |
| @@ -16,15 +16,15 @@ | @@ -16,15 +16,15 @@ | ||
| 16 | 16 | ||
| 17 | using namespace qpdf; | 17 | using namespace qpdf; |
| 18 | 18 | ||
| 19 | -using FormField = qpdf::impl::FormField; | 19 | +using FormNode = qpdf::impl::FormNode; |
| 20 | 20 | ||
| 21 | -const QPDFObjectHandle FormField::null_oh; | 21 | +const QPDFObjectHandle FormNode::null_oh; |
| 22 | 22 | ||
| 23 | -class QPDFFormFieldObjectHelper::Members: public FormField | 23 | +class QPDFFormFieldObjectHelper::Members: public FormNode |
| 24 | { | 24 | { |
| 25 | public: | 25 | public: |
| 26 | Members(QPDFObjectHandle const& oh) : | 26 | Members(QPDFObjectHandle const& oh) : |
| 27 | - FormField(oh) | 27 | + FormNode(oh) |
| 28 | { | 28 | { |
| 29 | } | 29 | } |
| 30 | }; | 30 | }; |
| @@ -59,8 +59,8 @@ QPDFFormFieldObjectHelper::getTopLevelField(bool* is_different) | @@ -59,8 +59,8 @@ QPDFFormFieldObjectHelper::getTopLevelField(bool* is_different) | ||
| 59 | return Null::if_null(m->root_field(is_different).oh()); | 59 | return Null::if_null(m->root_field(is_different).oh()); |
| 60 | } | 60 | } |
| 61 | 61 | ||
| 62 | -FormField | ||
| 63 | -FormField::root_field(bool* is_different) | 62 | +FormNode |
| 63 | +FormNode::root_field(bool* is_different) | ||
| 64 | { | 64 | { |
| 65 | if (is_different) { | 65 | if (is_different) { |
| 66 | *is_different = false; | 66 | *is_different = false; |
| @@ -87,7 +87,7 @@ QPDFFormFieldObjectHelper::getInheritableFieldValue(std::string const& name) | @@ -87,7 +87,7 @@ QPDFFormFieldObjectHelper::getInheritableFieldValue(std::string const& name) | ||
| 87 | } | 87 | } |
| 88 | 88 | ||
| 89 | QPDFObjectHandle const& | 89 | QPDFObjectHandle const& |
| 90 | -FormField::inherited(std::string const& name, bool acroform) const | 90 | +FormNode::inherited(std::string const& name, bool acroform) const |
| 91 | { | 91 | { |
| 92 | if (!obj) { | 92 | if (!obj) { |
| 93 | return null_oh; | 93 | return null_oh; |
| @@ -111,7 +111,7 @@ QPDFFormFieldObjectHelper::getInheritableFieldValueAsString(std::string const& n | @@ -111,7 +111,7 @@ QPDFFormFieldObjectHelper::getInheritableFieldValueAsString(std::string const& n | ||
| 111 | } | 111 | } |
| 112 | 112 | ||
| 113 | std::string | 113 | std::string |
| 114 | -FormField::inheritable_string(std::string const& name) const | 114 | +FormNode::inheritable_string(std::string const& name) const |
| 115 | { | 115 | { |
| 116 | if (auto fv = inheritable_value<String>(name)) { | 116 | if (auto fv = inheritable_value<String>(name)) { |
| 117 | return fv.utf8_value(); | 117 | return fv.utf8_value(); |
| @@ -144,7 +144,7 @@ QPDFFormFieldObjectHelper::getFullyQualifiedName() | @@ -144,7 +144,7 @@ QPDFFormFieldObjectHelper::getFullyQualifiedName() | ||
| 144 | } | 144 | } |
| 145 | 145 | ||
| 146 | std::string | 146 | std::string |
| 147 | -FormField::fully_qualified_name() const | 147 | +FormNode::fully_qualified_name() const |
| 148 | { | 148 | { |
| 149 | std::string result; | 149 | std::string result; |
| 150 | auto node = *this; | 150 | auto node = *this; |
| @@ -169,7 +169,7 @@ QPDFFormFieldObjectHelper::getPartialName() | @@ -169,7 +169,7 @@ QPDFFormFieldObjectHelper::getPartialName() | ||
| 169 | } | 169 | } |
| 170 | 170 | ||
| 171 | std::string | 171 | std::string |
| 172 | -FormField::partial_name() const | 172 | +FormNode::partial_name() const |
| 173 | { | 173 | { |
| 174 | if (auto pn = T()) { | 174 | if (auto pn = T()) { |
| 175 | return pn.utf8_value(); | 175 | return pn.utf8_value(); |
| @@ -184,7 +184,7 @@ QPDFFormFieldObjectHelper::getAlternativeName() | @@ -184,7 +184,7 @@ QPDFFormFieldObjectHelper::getAlternativeName() | ||
| 184 | } | 184 | } |
| 185 | 185 | ||
| 186 | std::string | 186 | std::string |
| 187 | -FormField::alternative_name() const | 187 | +FormNode::alternative_name() const |
| 188 | { | 188 | { |
| 189 | if (auto an = TU()) { | 189 | if (auto an = TU()) { |
| 190 | return an.utf8_value(); | 190 | return an.utf8_value(); |
| @@ -199,7 +199,7 @@ QPDFFormFieldObjectHelper::getMappingName() | @@ -199,7 +199,7 @@ QPDFFormFieldObjectHelper::getMappingName() | ||
| 199 | } | 199 | } |
| 200 | 200 | ||
| 201 | std::string | 201 | std::string |
| 202 | -FormField::mapping_name() const | 202 | +FormNode::mapping_name() const |
| 203 | { | 203 | { |
| 204 | if (auto mn = TM()) { | 204 | if (auto mn = TM()) { |
| 205 | return mn.utf8_value(); | 205 | return mn.utf8_value(); |
| @@ -220,7 +220,7 @@ QPDFFormFieldObjectHelper::getValueAsString() | @@ -220,7 +220,7 @@ QPDFFormFieldObjectHelper::getValueAsString() | ||
| 220 | } | 220 | } |
| 221 | 221 | ||
| 222 | std::string | 222 | std::string |
| 223 | -FormField::value() const | 223 | +FormNode::value() const |
| 224 | { | 224 | { |
| 225 | return inheritable_string("/V"); | 225 | return inheritable_string("/V"); |
| 226 | } | 226 | } |
| @@ -238,7 +238,7 @@ QPDFFormFieldObjectHelper::getDefaultValueAsString() | @@ -238,7 +238,7 @@ QPDFFormFieldObjectHelper::getDefaultValueAsString() | ||
| 238 | } | 238 | } |
| 239 | 239 | ||
| 240 | std::string | 240 | std::string |
| 241 | -FormField::default_value() const | 241 | +FormNode::default_value() const |
| 242 | { | 242 | { |
| 243 | return inheritable_string("/DV"); | 243 | return inheritable_string("/DV"); |
| 244 | } | 244 | } |
| @@ -250,7 +250,7 @@ QPDFFormFieldObjectHelper::getDefaultResources() | @@ -250,7 +250,7 @@ QPDFFormFieldObjectHelper::getDefaultResources() | ||
| 250 | } | 250 | } |
| 251 | 251 | ||
| 252 | QPDFObjectHandle | 252 | QPDFObjectHandle |
| 253 | -FormField::getDefaultResources() | 253 | +FormNode::getDefaultResources() |
| 254 | { | 254 | { |
| 255 | return from_AcroForm("/DR"); | 255 | return from_AcroForm("/DR"); |
| 256 | } | 256 | } |
| @@ -262,7 +262,7 @@ QPDFFormFieldObjectHelper::getDefaultAppearance() | @@ -262,7 +262,7 @@ QPDFFormFieldObjectHelper::getDefaultAppearance() | ||
| 262 | } | 262 | } |
| 263 | 263 | ||
| 264 | std::string | 264 | std::string |
| 265 | -FormField::default_appearance() const | 265 | +FormNode::default_appearance() const |
| 266 | { | 266 | { |
| 267 | if (auto DA = inheritable_value<String>("/DA")) { | 267 | if (auto DA = inheritable_value<String>("/DA")) { |
| 268 | return DA.utf8_value(); | 268 | return DA.utf8_value(); |
| @@ -280,7 +280,7 @@ QPDFFormFieldObjectHelper::getQuadding() | @@ -280,7 +280,7 @@ QPDFFormFieldObjectHelper::getQuadding() | ||
| 280 | } | 280 | } |
| 281 | 281 | ||
| 282 | int | 282 | int |
| 283 | -FormField::getQuadding() | 283 | +FormNode::getQuadding() |
| 284 | { | 284 | { |
| 285 | auto fv = inheritable_value<QPDFObjectHandle>("/Q"); | 285 | auto fv = inheritable_value<QPDFObjectHandle>("/Q"); |
| 286 | bool looked_in_acroform = false; | 286 | bool looked_in_acroform = false; |
| @@ -302,7 +302,7 @@ QPDFFormFieldObjectHelper::getFlags() | @@ -302,7 +302,7 @@ QPDFFormFieldObjectHelper::getFlags() | ||
| 302 | } | 302 | } |
| 303 | 303 | ||
| 304 | int | 304 | int |
| 305 | -FormField::getFlags() | 305 | +FormNode::getFlags() |
| 306 | { | 306 | { |
| 307 | auto f = inheritable_value<QPDFObjectHandle>("/Ff"); | 307 | auto f = inheritable_value<QPDFObjectHandle>("/Ff"); |
| 308 | return f.isInteger() ? f.getIntValueAsInt() : 0; | 308 | return f.isInteger() ? f.getIntValueAsInt() : 0; |
| @@ -315,7 +315,7 @@ QPDFFormFieldObjectHelper::isText() | @@ -315,7 +315,7 @@ QPDFFormFieldObjectHelper::isText() | ||
| 315 | } | 315 | } |
| 316 | 316 | ||
| 317 | bool | 317 | bool |
| 318 | -FormField::isText() | 318 | +FormNode::isText() |
| 319 | { | 319 | { |
| 320 | return FT() == "/Tx"; | 320 | return FT() == "/Tx"; |
| 321 | } | 321 | } |
| @@ -327,7 +327,7 @@ QPDFFormFieldObjectHelper::isCheckbox() | @@ -327,7 +327,7 @@ QPDFFormFieldObjectHelper::isCheckbox() | ||
| 327 | } | 327 | } |
| 328 | 328 | ||
| 329 | bool | 329 | bool |
| 330 | -FormField::isCheckbox() | 330 | +FormNode::isCheckbox() |
| 331 | { | 331 | { |
| 332 | return FT() == "/Btn" && (getFlags() & (ff_btn_radio | ff_btn_pushbutton)) == 0; | 332 | return FT() == "/Btn" && (getFlags() & (ff_btn_radio | ff_btn_pushbutton)) == 0; |
| 333 | } | 333 | } |
| @@ -339,7 +339,7 @@ QPDFFormFieldObjectHelper::isChecked() | @@ -339,7 +339,7 @@ QPDFFormFieldObjectHelper::isChecked() | ||
| 339 | } | 339 | } |
| 340 | 340 | ||
| 341 | bool | 341 | bool |
| 342 | -FormField::isChecked() | 342 | +FormNode::isChecked() |
| 343 | { | 343 | { |
| 344 | return isCheckbox() && V<Name>() != "/Off"; | 344 | return isCheckbox() && V<Name>() != "/Off"; |
| 345 | } | 345 | } |
| @@ -351,7 +351,7 @@ QPDFFormFieldObjectHelper::isRadioButton() | @@ -351,7 +351,7 @@ QPDFFormFieldObjectHelper::isRadioButton() | ||
| 351 | } | 351 | } |
| 352 | 352 | ||
| 353 | bool | 353 | bool |
| 354 | -FormField::isRadioButton() | 354 | +FormNode::isRadioButton() |
| 355 | { | 355 | { |
| 356 | return FT() == "/Btn" && (getFlags() & ff_btn_radio) == ff_btn_radio; | 356 | return FT() == "/Btn" && (getFlags() & ff_btn_radio) == ff_btn_radio; |
| 357 | } | 357 | } |
| @@ -363,7 +363,7 @@ QPDFFormFieldObjectHelper::isPushbutton() | @@ -363,7 +363,7 @@ QPDFFormFieldObjectHelper::isPushbutton() | ||
| 363 | } | 363 | } |
| 364 | 364 | ||
| 365 | bool | 365 | bool |
| 366 | -FormField::isPushbutton() | 366 | +FormNode::isPushbutton() |
| 367 | { | 367 | { |
| 368 | return FT() == "/Btn" && (getFlags() & ff_btn_pushbutton) == ff_btn_pushbutton; | 368 | return FT() == "/Btn" && (getFlags() & ff_btn_pushbutton) == ff_btn_pushbutton; |
| 369 | } | 369 | } |
| @@ -375,7 +375,7 @@ QPDFFormFieldObjectHelper::isChoice() | @@ -375,7 +375,7 @@ QPDFFormFieldObjectHelper::isChoice() | ||
| 375 | } | 375 | } |
| 376 | 376 | ||
| 377 | bool | 377 | bool |
| 378 | -FormField::isChoice() | 378 | +FormNode::isChoice() |
| 379 | { | 379 | { |
| 380 | return FT() == "/Ch"; | 380 | return FT() == "/Ch"; |
| 381 | } | 381 | } |
| @@ -387,7 +387,7 @@ QPDFFormFieldObjectHelper::getChoices() | @@ -387,7 +387,7 @@ QPDFFormFieldObjectHelper::getChoices() | ||
| 387 | } | 387 | } |
| 388 | 388 | ||
| 389 | std::vector<std::string> | 389 | std::vector<std::string> |
| 390 | -FormField::getChoices() | 390 | +FormNode::getChoices() |
| 391 | { | 391 | { |
| 392 | if (!isChoice()) { | 392 | if (!isChoice()) { |
| 393 | return {}; | 393 | return {}; |
| @@ -413,7 +413,7 @@ QPDFFormFieldObjectHelper::setFieldAttribute(std::string const& key, QPDFObjectH | @@ -413,7 +413,7 @@ QPDFFormFieldObjectHelper::setFieldAttribute(std::string const& key, QPDFObjectH | ||
| 413 | } | 413 | } |
| 414 | 414 | ||
| 415 | void | 415 | void |
| 416 | -FormField::setFieldAttribute(std::string const& key, QPDFObjectHandle value) | 416 | +FormNode::setFieldAttribute(std::string const& key, QPDFObjectHandle value) |
| 417 | { | 417 | { |
| 418 | oh().replaceKey(key, value); | 418 | oh().replaceKey(key, value); |
| 419 | } | 419 | } |
| @@ -425,7 +425,7 @@ QPDFFormFieldObjectHelper::setFieldAttribute(std::string const& key, std::string | @@ -425,7 +425,7 @@ QPDFFormFieldObjectHelper::setFieldAttribute(std::string const& key, std::string | ||
| 425 | } | 425 | } |
| 426 | 426 | ||
| 427 | void | 427 | void |
| 428 | -FormField::setFieldAttribute(std::string const& key, std::string const& utf8_value) | 428 | +FormNode::setFieldAttribute(std::string const& key, std::string const& utf8_value) |
| 429 | { | 429 | { |
| 430 | oh().replaceKey(key, QPDFObjectHandle::newUnicodeString(utf8_value)); | 430 | oh().replaceKey(key, QPDFObjectHandle::newUnicodeString(utf8_value)); |
| 431 | } | 431 | } |
| @@ -437,7 +437,7 @@ QPDFFormFieldObjectHelper::setV(QPDFObjectHandle value, bool need_appearances) | @@ -437,7 +437,7 @@ QPDFFormFieldObjectHelper::setV(QPDFObjectHandle value, bool need_appearances) | ||
| 437 | } | 437 | } |
| 438 | 438 | ||
| 439 | void | 439 | void |
| 440 | -FormField::setV(QPDFObjectHandle value, bool need_appearances) | 440 | +FormNode::setV(QPDFObjectHandle value, bool need_appearances) |
| 441 | { | 441 | { |
| 442 | Name name = value; | 442 | Name name = value; |
| 443 | if (FT() == "/Btn") { | 443 | if (FT() == "/Btn") { |
| @@ -485,13 +485,13 @@ QPDFFormFieldObjectHelper::setV(std::string const& utf8_value, bool need_appeara | @@ -485,13 +485,13 @@ QPDFFormFieldObjectHelper::setV(std::string const& utf8_value, bool need_appeara | ||
| 485 | } | 485 | } |
| 486 | 486 | ||
| 487 | void | 487 | void |
| 488 | -FormField::setV(std::string const& utf8_value, bool need_appearances) | 488 | +FormNode::setV(std::string const& utf8_value, bool need_appearances) |
| 489 | { | 489 | { |
| 490 | setV(QPDFObjectHandle::newUnicodeString(utf8_value), need_appearances); | 490 | setV(QPDFObjectHandle::newUnicodeString(utf8_value), need_appearances); |
| 491 | } | 491 | } |
| 492 | 492 | ||
| 493 | void | 493 | void |
| 494 | -FormField::setRadioButtonValue(QPDFObjectHandle name) | 494 | +FormNode::setRadioButtonValue(QPDFObjectHandle name) |
| 495 | { | 495 | { |
| 496 | // Set the value of a radio button field. This has the following specific behavior: | 496 | // Set the value of a radio button field. This has the following specific behavior: |
| 497 | // * If this is a radio button field that has a parent that is also a radio button field and has | 497 | // * If this is a radio button field that has a parent that is also a radio button field and has |
| @@ -503,7 +503,7 @@ FormField::setRadioButtonValue(QPDFObjectHandle name) | @@ -503,7 +503,7 @@ FormField::setRadioButtonValue(QPDFObjectHandle name) | ||
| 503 | // Note that we never turn on /NeedAppearances when setting a radio button field. | 503 | // Note that we never turn on /NeedAppearances when setting a radio button field. |
| 504 | QPDFObjectHandle parent = oh().getKey("/Parent"); | 504 | QPDFObjectHandle parent = oh().getKey("/Parent"); |
| 505 | if (parent.isDictionary() && parent.getKey("/Parent").null()) { | 505 | if (parent.isDictionary() && parent.getKey("/Parent").null()) { |
| 506 | - FormField ph(parent); | 506 | + FormNode ph(parent); |
| 507 | if (ph.isRadioButton()) { | 507 | if (ph.isRadioButton()) { |
| 508 | // This is most likely one of the individual buttons. Try calling on the parent. | 508 | // This is most likely one of the individual buttons. Try calling on the parent. |
| 509 | ph.setRadioButtonValue(name); | 509 | ph.setRadioButtonValue(name); |
| @@ -546,7 +546,7 @@ FormField::setRadioButtonValue(QPDFObjectHandle name) | @@ -546,7 +546,7 @@ FormField::setRadioButtonValue(QPDFObjectHandle name) | ||
| 546 | } | 546 | } |
| 547 | 547 | ||
| 548 | void | 548 | void |
| 549 | -FormField::setCheckBoxValue(bool value) | 549 | +FormNode::setCheckBoxValue(bool value) |
| 550 | { | 550 | { |
| 551 | QPDFObjectHandle AP = oh().getKey("/AP"); | 551 | QPDFObjectHandle AP = oh().getKey("/AP"); |
| 552 | QPDFObjectHandle annot; | 552 | QPDFObjectHandle annot; |
| @@ -601,7 +601,7 @@ QPDFFormFieldObjectHelper::generateAppearance(QPDFAnnotationObjectHelper& aoh) | @@ -601,7 +601,7 @@ QPDFFormFieldObjectHelper::generateAppearance(QPDFAnnotationObjectHelper& aoh) | ||
| 601 | } | 601 | } |
| 602 | 602 | ||
| 603 | void | 603 | void |
| 604 | -FormField::generateAppearance(QPDFAnnotationObjectHelper& aoh) | 604 | +FormNode::generateAppearance(QPDFAnnotationObjectHelper& aoh) |
| 605 | { | 605 | { |
| 606 | // Ignore field types we don't know how to generate appearances for. Button fields don't really | 606 | // Ignore field types we don't know how to generate appearances for. Button fields don't really |
| 607 | // need them -- see code in QPDFAcroFormDocumentHelper::generateAppearancesIfNeeded. | 607 | // need them -- see code in QPDFAcroFormDocumentHelper::generateAppearancesIfNeeded. |
| @@ -877,7 +877,7 @@ namespace | @@ -877,7 +877,7 @@ namespace | ||
| 877 | } // namespace | 877 | } // namespace |
| 878 | 878 | ||
| 879 | QPDFObjectHandle | 879 | QPDFObjectHandle |
| 880 | -FormField::getFontFromResource(QPDFObjectHandle resources, std::string const& name) | 880 | +FormNode::getFontFromResource(QPDFObjectHandle resources, std::string const& name) |
| 881 | { | 881 | { |
| 882 | QPDFObjectHandle result; | 882 | QPDFObjectHandle result; |
| 883 | if (resources.isDictionary() && resources.getKey("/Font").isDictionary() && | 883 | if (resources.isDictionary() && resources.getKey("/Font").isDictionary() && |
| @@ -888,7 +888,7 @@ FormField::getFontFromResource(QPDFObjectHandle resources, std::string const& na | @@ -888,7 +888,7 @@ FormField::getFontFromResource(QPDFObjectHandle resources, std::string const& na | ||
| 888 | } | 888 | } |
| 889 | 889 | ||
| 890 | void | 890 | void |
| 891 | -FormField::generateTextAppearance(QPDFAnnotationObjectHelper& aoh) | 891 | +FormNode::generateTextAppearance(QPDFAnnotationObjectHelper& aoh) |
| 892 | { | 892 | { |
| 893 | QPDFObjectHandle AS = aoh.getAppearanceStream("/N"); | 893 | QPDFObjectHandle AS = aoh.getAppearanceStream("/N"); |
| 894 | if (AS.null()) { | 894 | if (AS.null()) { |
libqpdf/QPDFJob.cc
| @@ -4,6 +4,7 @@ | @@ -4,6 +4,7 @@ | ||
| 4 | #include <iostream> | 4 | #include <iostream> |
| 5 | #include <memory> | 5 | #include <memory> |
| 6 | 6 | ||
| 7 | +#include <qpdf/AcroForm.hh> | ||
| 7 | #include <qpdf/ClosedFileInputSource.hh> | 8 | #include <qpdf/ClosedFileInputSource.hh> |
| 8 | #include <qpdf/FileInputSource.hh> | 9 | #include <qpdf/FileInputSource.hh> |
| 9 | #include <qpdf/Pipeline_private.hh> | 10 | #include <qpdf/Pipeline_private.hh> |
| @@ -1866,7 +1867,7 @@ QPDFJob::doUnderOverlayForPage( | @@ -1866,7 +1867,7 @@ QPDFJob::doUnderOverlayForPage( | ||
| 1866 | if (!(uo.pdf && pagenos[pageno.idx].contains(uo_idx))) { | 1867 | if (!(uo.pdf && pagenos[pageno.idx].contains(uo_idx))) { |
| 1867 | return ""; | 1868 | return ""; |
| 1868 | } | 1869 | } |
| 1869 | - auto& dest_afdh = dest_page.qpdf()->doc().acroform(); | 1870 | + auto& dest_afdh = dest_page.qpdf()->doc().acroform_dh(); |
| 1870 | 1871 | ||
| 1871 | auto const& pages = uo.pdf->doc().pages().all(); | 1872 | auto const& pages = uo.pdf->doc().pages().all(); |
| 1872 | std::string content; | 1873 | std::string content; |
| @@ -1887,7 +1888,8 @@ QPDFJob::doUnderOverlayForPage( | @@ -1887,7 +1888,8 @@ QPDFJob::doUnderOverlayForPage( | ||
| 1887 | QPDFMatrix cm; | 1888 | QPDFMatrix cm; |
| 1888 | std::string new_content = dest_page.placeFormXObject( | 1889 | std::string new_content = dest_page.placeFormXObject( |
| 1889 | fo[from_no.no][uo_idx], name, dest_page.getTrimBox().getArrayAsRectangle(), cm); | 1890 | fo[from_no.no][uo_idx], name, dest_page.getTrimBox().getArrayAsRectangle(), cm); |
| 1890 | - dest_page.copyAnnotations(from_page, cm, &dest_afdh, &from_page.qpdf()->doc().acroform()); | 1891 | + dest_page.copyAnnotations( |
| 1892 | + from_page, cm, &dest_afdh, &from_page.qpdf()->doc().acroform_dh()); | ||
| 1891 | if (!new_content.empty()) { | 1893 | if (!new_content.empty()) { |
| 1892 | resources.mergeResources(Dictionary({{"/XObject", Dictionary::empty()}})); | 1894 | resources.mergeResources(Dictionary({{"/XObject", Dictionary::empty()}})); |
| 1893 | auto xobject = resources.getKey("/XObject"); | 1895 | auto xobject = resources.getKey("/XObject"); |
| @@ -2107,7 +2109,7 @@ QPDFJob::handleTransformations(QPDF& pdf) | @@ -2107,7 +2109,7 @@ QPDFJob::handleTransformations(QPDF& pdf) | ||
| 2107 | QPDFAcroFormDocumentHelper* afdh_ptr = nullptr; | 2109 | QPDFAcroFormDocumentHelper* afdh_ptr = nullptr; |
| 2108 | auto afdh = [&]() -> QPDFAcroFormDocumentHelper& { | 2110 | auto afdh = [&]() -> QPDFAcroFormDocumentHelper& { |
| 2109 | if (!afdh_ptr) { | 2111 | if (!afdh_ptr) { |
| 2110 | - afdh_ptr = &pdf.doc().acroform(); | 2112 | + afdh_ptr = &pdf.doc().acroform_dh(); |
| 2111 | } | 2113 | } |
| 2112 | return *afdh_ptr; | 2114 | return *afdh_ptr; |
| 2113 | }; | 2115 | }; |
| @@ -2978,8 +2980,7 @@ QPDFJob::doSplitPages(QPDF& pdf) | @@ -2978,8 +2980,7 @@ QPDFJob::doSplitPages(QPDF& pdf) | ||
| 2978 | QPDF outpdf; | 2980 | QPDF outpdf; |
| 2979 | outpdf.doc().config(m->d_cfg); | 2981 | outpdf.doc().config(m->d_cfg); |
| 2980 | outpdf.emptyPDF(); | 2982 | outpdf.emptyPDF(); |
| 2981 | - QPDFAcroFormDocumentHelper* out_afdh = | ||
| 2982 | - afdh.hasAcroForm() ? &outpdf.doc().acroform() : nullptr; | 2983 | + impl::AcroForm* out_afdh = afdh.hasAcroForm() ? &outpdf.doc().acroform() : nullptr; |
| 2983 | for (size_t pageno = first; pageno <= last; ++pageno) { | 2984 | for (size_t pageno = first; pageno <= last; ++pageno) { |
| 2984 | QPDFObjectHandle page = pages.at(pageno - 1); | 2985 | QPDFObjectHandle page = pages.at(pageno - 1); |
| 2985 | outpdf.addPage(page, false); | 2986 | outpdf.addPage(page, false); |
libqpdf/QPDF_pages.cc
| 1 | #include <qpdf/QPDFPageDocumentHelper.hh> | 1 | #include <qpdf/QPDFPageDocumentHelper.hh> |
| 2 | #include <qpdf/QPDF_private.hh> | 2 | #include <qpdf/QPDF_private.hh> |
| 3 | 3 | ||
| 4 | -#include <qpdf/QPDFAcroFormDocumentHelper.hh> | 4 | +#include <qpdf/AcroForm.hh> |
| 5 | #include <qpdf/QPDFExc.hh> | 5 | #include <qpdf/QPDFExc.hh> |
| 6 | #include <qpdf/QPDFObjectHandle_private.hh> | 6 | #include <qpdf/QPDFObjectHandle_private.hh> |
| 7 | #include <qpdf/QTC.hh> | 7 | #include <qpdf/QTC.hh> |
| @@ -660,7 +660,7 @@ void | @@ -660,7 +660,7 @@ void | ||
| 660 | Pages::flatten_annotations_for_page( | 660 | Pages::flatten_annotations_for_page( |
| 661 | QPDFPageObjectHelper& page, | 661 | QPDFPageObjectHelper& page, |
| 662 | QPDFObjectHandle& resources, | 662 | QPDFObjectHandle& resources, |
| 663 | - QPDFAcroFormDocumentHelper& afdh, | 663 | + impl::AcroForm& afdh, |
| 664 | int required_flags, | 664 | int required_flags, |
| 665 | int forbidden_flags) | 665 | int forbidden_flags) |
| 666 | { | 666 | { |
libqpdf/qpdf/AcroForm.hh
0 โ 100644
| 1 | +#ifndef ACRO_FORM_HH | ||
| 2 | +#define ACRO_FORM_HH | ||
| 3 | + | ||
| 4 | +#include <qpdf/QPDFObjectHandle_private.hh> | ||
| 5 | +#include <qpdf/QPDFObjectHelper.hh> | ||
| 6 | +#include <qpdf/QPDF_private.hh> | ||
| 7 | + | ||
| 8 | +#include <vector> | ||
| 9 | + | ||
| 10 | +class QPDFAnnotationObjectHelper; | ||
| 11 | + | ||
| 12 | +namespace qpdf::impl | ||
| 13 | +{ | ||
| 14 | + /// @class AcroForm | ||
| 15 | + /// @brief Represents the interactive form dictionary and the interactive form tree within a | ||
| 16 | + /// PDF document. | ||
| 17 | + /// @par | ||
| 18 | + /// The AcroForm class deals with interactive forms defined in section 12.7 of the PDF | ||
| 19 | + /// specification. This defines a tree structure consisting of an interactive form or | ||
| 20 | + /// `/AcroForm` dictionary (section 12.7.3) at its root. The attributes of the | ||
| 21 | + /// `/AcroForm` dictionary are defined in table 224 of the PDF 2.0 / table 220 of the | ||
| 22 | + /// PDF 1.7 specification. | ||
| 23 | + /// @par | ||
| 24 | + /// The nodes of the interactive forms tree are represented by the FormNode class. | ||
| 25 | + /// | ||
| 26 | + /// @since 12.3 | ||
| 27 | + class AcroForm: public Doc::Common | ||
| 28 | + { | ||
| 29 | + public: | ||
| 30 | + AcroForm() = delete; | ||
| 31 | + AcroForm(AcroForm const&) = delete; | ||
| 32 | + AcroForm(AcroForm&&) = delete; | ||
| 33 | + AcroForm& operator=(AcroForm const&) = delete; | ||
| 34 | + AcroForm& operator=(AcroForm&&) = delete; | ||
| 35 | + ~AcroForm() = default; | ||
| 36 | + | ||
| 37 | + AcroForm(impl::Doc& doc) : | ||
| 38 | + Common(doc) | ||
| 39 | + { | ||
| 40 | + // We have to analyze up front. Otherwise, when we are adding annotations and fields, we | ||
| 41 | + // are in a temporarily unstable configuration where some widget annotations are not | ||
| 42 | + // reachable. | ||
| 43 | + validate(); | ||
| 44 | + } | ||
| 45 | + | ||
| 46 | + // Re-validate the AcroForm structure. This is useful if you have modified the structure of | ||
| 47 | + // the AcroForm dictionary in a way that would invalidate the cache. | ||
| 48 | + // | ||
| 49 | + // If repair is true, the document will be repaired if possible if the validation encounters | ||
| 50 | + // errors. | ||
| 51 | + void validate(bool repair = true); | ||
| 52 | + | ||
| 53 | + // This class lazily creates an internal cache of the mapping among form fields, | ||
| 54 | + // annotations, and pages. Methods within this class preserve the validity of this cache. | ||
| 55 | + // However, if you modify pages' annotation dictionaries, the document's /AcroForm | ||
| 56 | + // dictionary, or any form fields manually in a way that alters the association between | ||
| 57 | + // forms, fields, annotations, and pages, it may cause this cache to become invalid. This | ||
| 58 | + // method marks the cache invalid and forces it to be regenerated the next time it is | ||
| 59 | + // needed. | ||
| 60 | + void invalidateCache(); | ||
| 61 | + | ||
| 62 | + bool hasAcroForm(); | ||
| 63 | + | ||
| 64 | + // Add a form field, initializing the document's AcroForm dictionary if needed, updating the | ||
| 65 | + // cache if necessary. Note that if you are adding fields that are copies of other fields, | ||
| 66 | + // this method may result in multiple fields existing with the same qualified name, which | ||
| 67 | + // can have unexpected side effects. In that case, you should use addAndRenameFormFields() | ||
| 68 | + // instead. | ||
| 69 | + void addFormField(QPDFFormFieldObjectHelper); | ||
| 70 | + | ||
| 71 | + // Add a collection of form fields making sure that their fully qualified names don't | ||
| 72 | + // conflict with already present form fields. Fields within the collection of new fields | ||
| 73 | + // that have the same name as each other will continue to do so. | ||
| 74 | + void addAndRenameFormFields(std::vector<QPDFObjectHandle> fields); | ||
| 75 | + | ||
| 76 | + // Remove fields from the fields array | ||
| 77 | + void removeFormFields(std::set<QPDFObjGen> const&); | ||
| 78 | + | ||
| 79 | + // Set the name of a field, updating internal records of field names. Name should be UTF-8 | ||
| 80 | + // encoded. | ||
| 81 | + void setFormFieldName(QPDFFormFieldObjectHelper, std::string const& name); | ||
| 82 | + | ||
| 83 | + // Return a vector of all terminal fields in a document. Terminal fields are fields that | ||
| 84 | + // have no children that are also fields. Terminal fields may still have children that are | ||
| 85 | + // annotations. Intermediate nodes in the fields tree are not included in this list, but you | ||
| 86 | + // can still reach them through the getParent method of the field object helper. | ||
| 87 | + std::vector<QPDFFormFieldObjectHelper> getFormFields(); | ||
| 88 | + | ||
| 89 | + // Return all the form fields that have the given fully-qualified name and also have an | ||
| 90 | + // explicit "/T" attribute. For this information to be accurate, any changes to field names | ||
| 91 | + // must be done through setFormFieldName() above. | ||
| 92 | + std::set<QPDFObjGen> getFieldsWithQualifiedName(std::string const& name); | ||
| 93 | + | ||
| 94 | + // Return the annotations associated with a terminal field. Note that in the case of a field | ||
| 95 | + // having a single annotation, the underlying object will typically be the same as the | ||
| 96 | + // underlying object for the field. | ||
| 97 | + std::vector<QPDFAnnotationObjectHelper> getAnnotationsForField(QPDFFormFieldObjectHelper); | ||
| 98 | + | ||
| 99 | + /// Retrieves a list of widget annotations for the specified page. | ||
| 100 | + /// | ||
| 101 | + /// A widget annotation represents the visual part of a form field in a PDF. | ||
| 102 | + /// This function filters annotations on the given page, returning only those | ||
| 103 | + /// annotations whose subtype is "/Widget". | ||
| 104 | + /// | ||
| 105 | + /// @param page A `QPDFPageObjectHelper` representing the page from which to | ||
| 106 | + /// extract widget annotations. | ||
| 107 | + /// | ||
| 108 | + /// @return A vector of `QPDFAnnotationObjectHelper` objects corresponding to | ||
| 109 | + /// the widget annotations found on the specified page. | ||
| 110 | + std::vector<QPDFAnnotationObjectHelper> | ||
| 111 | + getWidgetAnnotationsForPage(QPDFPageObjectHelper page); | ||
| 112 | + | ||
| 113 | + // Return top-level form fields for a page. | ||
| 114 | + std::vector<QPDFFormFieldObjectHelper> getFormFieldsForPage(QPDFPageObjectHelper); | ||
| 115 | + | ||
| 116 | + // Return the terminal field that is associated with this annotation. If the annotation | ||
| 117 | + // dictionary is merged with the field dictionary, the underlying object will be the same, | ||
| 118 | + // but this is not always the case. Note that if you call this method with an annotation | ||
| 119 | + // that is not a widget annotation, there will not be an associated field, and this method | ||
| 120 | + // will return a helper associated with a null object (isNull() == true). | ||
| 121 | + QPDFFormFieldObjectHelper getFieldForAnnotation(QPDFAnnotationObjectHelper); | ||
| 122 | + | ||
| 123 | + // Return the current value of /NeedAppearances. If /NeedAppearances is missing, return | ||
| 124 | + // false as that is how PDF viewers are supposed to interpret it. | ||
| 125 | + bool getNeedAppearances(); | ||
| 126 | + | ||
| 127 | + // Indicate whether appearance streams must be regenerated. If you modify a field value, you | ||
| 128 | + // should call setNeedAppearances(true) unless you also generate an appearance stream for | ||
| 129 | + // the corresponding annotation at the same time. If you generate appearance streams for all | ||
| 130 | + // fields, you can call setNeedAppearances(false). If you use | ||
| 131 | + // QPDFFormFieldObjectHelper::setV, it will automatically call this method unless you tell | ||
| 132 | + // it not to. | ||
| 133 | + void setNeedAppearances(bool); | ||
| 134 | + | ||
| 135 | + // If /NeedAppearances is false, do nothing. Otherwise generate appearance streams for all | ||
| 136 | + // widget annotations that need them. See comments in QPDFFormFieldObjectHelper.hh for | ||
| 137 | + // generateAppearance for limitations. For checkbox and radio button fields, this code | ||
| 138 | + // ensures that appearance state is consistent with the field's value and uses any | ||
| 139 | + // pre-existing appearance streams. | ||
| 140 | + void generateAppearancesIfNeeded(); | ||
| 141 | + | ||
| 142 | + // Disable Digital Signature Fields. Remove all digital signature fields from the document, | ||
| 143 | + // leaving any annotation showing the content of the field intact. This also calls | ||
| 144 | + // QPDF::removeSecurityRestrictions. | ||
| 145 | + void disableDigitalSignatures(); | ||
| 146 | + | ||
| 147 | + // Note: this method works on all annotations, not just ones with associated fields. For | ||
| 148 | + // each annotation in old_annots, apply the given transformation matrix to create a new | ||
| 149 | + // annotation. New annotations are appended to new_annots. If the annotation is associated | ||
| 150 | + // with a form field, a new form field is created that points to the new annotation and is | ||
| 151 | + // appended to new_fields, and the old field is added to old_fields. | ||
| 152 | + // | ||
| 153 | + // old_annots may belong to a different QPDF object. In that case, you should pass in | ||
| 154 | + // from_qpdf, and copyForeignObject will be called automatically. If this is the case, for | ||
| 155 | + // efficiency, you may pass in a QPDFAcroFormDocumentHelper for the other file to avoid the | ||
| 156 | + // expensive process of creating one for each call to transformAnnotations. New fields and | ||
| 157 | + // annotations are not added to the document or pages. You have to do that yourself after | ||
| 158 | + // calling transformAnnotations. If this operation will leave orphaned fields behind, such | ||
| 159 | + // as if you are replacing the old annotations with the new ones on the same page and the | ||
| 160 | + // fields and annotations are not shared, you will also need to remove the old fields to | ||
| 161 | + // prevent them from hanging around unreferenced. | ||
| 162 | + void transformAnnotations( | ||
| 163 | + QPDFObjectHandle old_annots, | ||
| 164 | + std::vector<QPDFObjectHandle>& new_annots, | ||
| 165 | + std::vector<QPDFObjectHandle>& new_fields, | ||
| 166 | + std::set<QPDFObjGen>& old_fields, | ||
| 167 | + QPDFMatrix const& cm, | ||
| 168 | + QPDF* from_qpdf, | ||
| 169 | + AcroForm* from_afdh); | ||
| 170 | + | ||
| 171 | + // Copy form fields and annotations from one page to another, allowing the from page to be | ||
| 172 | + // in a different QPDF or in the same QPDF. This would typically be called after calling | ||
| 173 | + // addPage to add field/annotation awareness. When just copying the page by itself, | ||
| 174 | + // annotations end up being shared, and fields end up being omitted because there is no | ||
| 175 | + // reference to the field from the page. This method ensures that each separate copy of a | ||
| 176 | + // page has private annotations and that fields and annotations are properly updated to | ||
| 177 | + // resolve conflicts that may occur from common resource and field names across documents. | ||
| 178 | + // It is basically a wrapper around transformAnnotations that handles updating the receiving | ||
| 179 | + // page. If new_fields is non-null, any newly created fields are added to it. | ||
| 180 | + void fixCopiedAnnotations( | ||
| 181 | + QPDFObjectHandle to_page, | ||
| 182 | + QPDFObjectHandle from_page, | ||
| 183 | + AcroForm& from_afdh, | ||
| 184 | + std::set<QPDFObjGen>* new_fields = nullptr); | ||
| 185 | + | ||
| 186 | + private: | ||
| 187 | + struct FieldData | ||
| 188 | + { | ||
| 189 | + std::vector<QPDFAnnotationObjectHelper> annotations; | ||
| 190 | + std::string name; | ||
| 191 | + }; | ||
| 192 | + | ||
| 193 | + /// Analyzes the AcroForm structure in the PDF document and updates the internal | ||
| 194 | + /// cache with the form fields and their corresponding widget annotations. | ||
| 195 | + /// | ||
| 196 | + /// The function performs the following steps: | ||
| 197 | + /// - Checks if the cache is valid. If it is, the function exits early. | ||
| 198 | + /// - Retrieves the `/AcroForm` dictionary from the PDF and checks if it contains | ||
| 199 | + /// a `/Fields` key. | ||
| 200 | + /// - If `/Fields` exist and is an array, iterates through the fields and traverses | ||
| 201 | + /// them to map annotations bidirectionally to form fields. | ||
| 202 | + /// - Logs a warning if the `/Fields` key is present but not an array, and initializes | ||
| 203 | + /// it to an empty array. | ||
| 204 | + /// - Ensures that all widget annotations are processed, including any annotations | ||
| 205 | + /// that might not be reachable from the `/AcroForm`. Treats such annotations as | ||
| 206 | + /// their own fields. | ||
| 207 | + /// - Provides a workaround for PDF documents containing inconsistencies, such as | ||
| 208 | + /// widget annotations on a page not being referenced in `/AcroForm`. | ||
| 209 | + /// | ||
| 210 | + /// This function allows precise navigation and manipulation of form fields and | ||
| 211 | + /// their related annotations, facilitating advanced PDF document processing. | ||
| 212 | + void analyze(); | ||
| 213 | + | ||
| 214 | + /// Recursively traverses the structure of form fields and annotations in a PDF's /AcroForm. | ||
| 215 | + /// | ||
| 216 | + /// The method is designed to process form fields in a hierarchical /AcroForm structure. | ||
| 217 | + /// It captures field and annotation data, resolves parent-child relationships, detects | ||
| 218 | + /// loops, and avoids stack overflow from excessive recursion depth. | ||
| 219 | + /// | ||
| 220 | + /// @param field The current field or annotation to process. | ||
| 221 | + /// @param parent The parent field object. If the current field is a top-level field, parent | ||
| 222 | + /// will be a null object. | ||
| 223 | + /// @param depth The current recursion depth to limit stack usage and avoid infinite loops. | ||
| 224 | + /// | ||
| 225 | + /// @return True if the field was processed successfully, false otherwise. | ||
| 226 | + /// | ||
| 227 | + /// - Recursion is limited to a depth of 100 to prevent stack overflow with maliciously | ||
| 228 | + /// crafted files. | ||
| 229 | + /// - The function skips non-indirect and invalid objects (e.g., non-dictionaries or objects | ||
| 230 | + /// with invalid parent references). | ||
| 231 | + /// - Detects and warns about loops in the /AcroForm hierarchy. | ||
| 232 | + /// - Differentiates between terminal fields, annotations, and composite fields based on | ||
| 233 | + /// dictionary keys. | ||
| 234 | + /// - Tracks processed fields and annotations using internal maps to prevent reprocessing | ||
| 235 | + /// and detect loops. | ||
| 236 | + /// - Updates name-to-field mappings for terminal fields with a valid fully qualified name. | ||
| 237 | + /// - Ensures the integrity of parent-child relationships within the field hierarchy. | ||
| 238 | + /// - Any invalid child objects are logged and skipped during traversal. | ||
| 239 | + bool traverseField(QPDFObjectHandle field, QPDFObjectHandle const& parent, int depth); | ||
| 240 | + | ||
| 241 | + /// Retrieves or creates the /AcroForm dictionary in the PDF document's root. | ||
| 242 | + /// | ||
| 243 | + /// - If the /AcroForm key exists in the document root and is a dictionary, | ||
| 244 | + /// it is returned as is. | ||
| 245 | + /// - If the /AcroForm key does not exist or is not a dictionary, a new | ||
| 246 | + /// dictionary is created, stored as the /AcroForm entry in the document root, | ||
| 247 | + /// and then returned. | ||
| 248 | + /// | ||
| 249 | + /// @return A QPDFObjectHandle representing the /AcroForm dictionary. | ||
| 250 | + QPDFObjectHandle getOrCreateAcroForm(); | ||
| 251 | + | ||
| 252 | + /// Adjusts inherited field properties for an AcroForm field object. | ||
| 253 | + /// | ||
| 254 | + /// This method ensures that the `/DA` (default appearance) and `/Q` (quadding) keys | ||
| 255 | + /// of the specified field object are overridden if necessary, based on the provided | ||
| 256 | + /// parameters. The overriding is performed only if the respective `override_da` or | ||
| 257 | + /// `override_q` flags are set to true, and when the original object's values differ from | ||
| 258 | + /// the provided defaults. No changes are made to fields that have explicit values for `/DA` | ||
| 259 | + /// or `/Q`. | ||
| 260 | + /// | ||
| 261 | + /// The function is primarily used for adjusting inherited form field properties in cases | ||
| 262 | + /// where the document structure or inherited values have changed (e.g., when working with | ||
| 263 | + /// fields in a PDF document). | ||
| 264 | + /// | ||
| 265 | + /// @param obj The `QPDFObjectHandle` instance representing the form field object to be | ||
| 266 | + /// adjusted. | ||
| 267 | + /// @param override_da A boolean flag indicating whether to override the `/DA` key. | ||
| 268 | + /// @param from_default_da The default appearance string to apply if overriding the `/DA` | ||
| 269 | + /// key. | ||
| 270 | + /// @param override_q A boolean flag indicating whether to override the `/Q` key. | ||
| 271 | + /// @param from_default_q The default quadding value (alignment) to apply if overriding the | ||
| 272 | + /// `/Q` key. | ||
| 273 | + void adjustInheritedFields( | ||
| 274 | + QPDFObjectHandle obj, | ||
| 275 | + bool override_da, | ||
| 276 | + std::string const& from_default_da, | ||
| 277 | + bool override_q, | ||
| 278 | + int from_default_q); | ||
| 279 | + | ||
| 280 | + /// Adjusts the default appearances (/DA) of an AcroForm field object. | ||
| 281 | + /// | ||
| 282 | + /// This method ensures that form fields copied from another PDF document | ||
| 283 | + /// have their default appearances resource references updated to correctly | ||
| 284 | + /// point to the appropriate resources in the current document's resource | ||
| 285 | + /// dictionary (/DR). It resolves name conflicts between the dictionaries | ||
| 286 | + /// of the source and destination documents by using a mapping provided in | ||
| 287 | + /// `dr_map`. | ||
| 288 | + /// | ||
| 289 | + /// The method parses the /DA string, processes its resource references, | ||
| 290 | + /// and regenerates the /DA with updated references. | ||
| 291 | + /// | ||
| 292 | + /// @param obj The AcroForm field object whose /DA is being adjusted. | ||
| 293 | + /// @param dr_map A mapping between resource names in the source document's | ||
| 294 | + /// resource dictionary and their corresponding names in the current | ||
| 295 | + /// document's resource dictionary. | ||
| 296 | + void adjustDefaultAppearances( | ||
| 297 | + QPDFObjectHandle obj, | ||
| 298 | + std::map<std::string, std::map<std::string, std::string>> const& dr_map); | ||
| 299 | + | ||
| 300 | + /// Modifies the appearance stream of an AcroForm field to ensure its resources | ||
| 301 | + /// align with the resource dictionary and appearance settings. This method | ||
| 302 | + /// ensures proper resource handling to avoid any conflicts when regenerating | ||
| 303 | + /// the appearance stream. | ||
| 304 | + /// | ||
| 305 | + /// Adjustments include: | ||
| 306 | + /// - Creating a private resource dictionary for the stream if not already present. | ||
| 307 | + /// - Merging top-level resource keys into the stream's resource dictionary. | ||
| 308 | + /// - Resolving naming conflicts between existing and remapped resource keys. | ||
| 309 | + /// - Removing empty sub-dictionaries from the resource dictionary. | ||
| 310 | + /// - Attaching a token filter to rewrite resource references in the stream content. | ||
| 311 | + /// | ||
| 312 | + /// If conflicts between keys are encountered or the stream cannot be parsed successfully, | ||
| 313 | + /// appropriate warnings will be generated instead of halting execution. | ||
| 314 | + /// | ||
| 315 | + /// @param stream The QPDFObjectHandle representation of the PDF appearance stream to be | ||
| 316 | + /// adjusted. | ||
| 317 | + /// @param dr_map A mapping of resource types and their corresponding name remappings | ||
| 318 | + /// used for resolving resource conflicts and regenerating appearances. | ||
| 319 | + void adjustAppearanceStream( | ||
| 320 | + QPDFObjectHandle stream, | ||
| 321 | + std::map<std::string, std::map<std::string, std::string>> dr_map); | ||
| 322 | + | ||
| 323 | + std::map<QPDFObjGen, FieldData> fields_; | ||
| 324 | + std::map<QPDFObjGen, QPDFFormFieldObjectHelper> annotation_to_field_; | ||
| 325 | + std::map<std::string, std::set<QPDFObjGen>> name_to_fields_; | ||
| 326 | + std::set<QPDFObjGen> bad_fields_; | ||
| 327 | + bool cache_valid_{false}; | ||
| 328 | + | ||
| 329 | + }; // class Acroform | ||
| 330 | + | ||
| 331 | + /// @class FormNode | ||
| 332 | + /// @brief Represents a node in the interactive forms tree of a PDF document. | ||
| 333 | + /// | ||
| 334 | + /// This class models nodes that may be either form field dictionaries or widget annotation | ||
| 335 | + /// dictionaries, as defined in the PDF specification (sections 12.7 and 12.5.6.19). | ||
| 336 | + /// | ||
| 337 | + /// For a detailed description of the attributes that this class can expose, refer to the | ||
| 338 | + /// corresponding tables in the PDF 2.0 (Table 226) or PDF 1.7 (Table 220) specifications. | ||
| 339 | + class FormNode: public qpdf::BaseDictionary | ||
| 340 | + { | ||
| 341 | + public: | ||
| 342 | + FormNode() = default; | ||
| 343 | + FormNode(FormNode const&) = default; | ||
| 344 | + FormNode& operator=(FormNode const&) = default; | ||
| 345 | + FormNode(FormNode&&) = default; | ||
| 346 | + FormNode& operator=(FormNode&&) = default; | ||
| 347 | + ~FormNode() = default; | ||
| 348 | + | ||
| 349 | + FormNode(QPDFObjectHandle const& oh) : | ||
| 350 | + BaseDictionary(oh) | ||
| 351 | + { | ||
| 352 | + } | ||
| 353 | + | ||
| 354 | + FormNode(QPDFObjectHandle&& oh) : | ||
| 355 | + BaseDictionary(std::move(oh)) | ||
| 356 | + { | ||
| 357 | + } | ||
| 358 | + | ||
| 359 | + /// Retrieves the /Parent form field of the current field. | ||
| 360 | + /// | ||
| 361 | + /// This function accesses the parent field in the hierarchical structure of form fields, if | ||
| 362 | + /// it exists. The parent is determined based on the /Parent attribute in the field | ||
| 363 | + /// dictionary. | ||
| 364 | + /// | ||
| 365 | + /// @return A FormNode object representing the parent field. If the current field has no | ||
| 366 | + /// parent, an empty FormNode object is returned. | ||
| 367 | + FormNode | ||
| 368 | + Parent() | ||
| 369 | + { | ||
| 370 | + return {get("/Parent")}; | ||
| 371 | + } | ||
| 372 | + | ||
| 373 | + /// @brief Returns the top-level field associated with the current field. | ||
| 374 | + /// | ||
| 375 | + /// The function traverses the hierarchy of parent fields to identify the highest-level | ||
| 376 | + /// field in the tree. Typically, this will be the current field itself unless it has a | ||
| 377 | + /// parent field. Optionally, it can indicate whether the top-level field is different from | ||
| 378 | + /// the current field. | ||
| 379 | + /// | ||
| 380 | + /// @param is_different A pointer to a boolean that, if provided, will be set to true if the | ||
| 381 | + /// top-level field differs from the current field; otherwise, it will be set to | ||
| 382 | + /// false. | ||
| 383 | + /// | ||
| 384 | + /// @return The top-level field in the form field hierarchy. | ||
| 385 | + FormNode root_field(bool* is_different = nullptr); | ||
| 386 | + | ||
| 387 | + /// @brief Retrieves the inherited value of the specified attribute. | ||
| 388 | + /// | ||
| 389 | + /// @param name The name of the attribute to retrieve. | ||
| 390 | + /// @param acroform If true, checks the document's /AcroForm dictionary for the attribute | ||
| 391 | + /// if it is not found in the field hierarchy. | ||
| 392 | + /// | ||
| 393 | + /// @return A constant reference to the QPDFObjectHandle representing the value of the | ||
| 394 | + /// specified attribute, if found. If the attribute is not found in the field | ||
| 395 | + /// hierarchy or the /AcroForm dictionary (when `acroform` is true), returns a | ||
| 396 | + /// reference to a static null object handle. | ||
| 397 | + QPDFObjectHandle const& inherited(std::string const& name, bool acroform = false) const; | ||
| 398 | + | ||
| 399 | + /// @brief Retrieves the value of a specified field, accounting for inheritance through the | ||
| 400 | + /// hierarchy of ancestor nodes in the form field tree. | ||
| 401 | + /// | ||
| 402 | + /// This function attempts to retrieve the value of the specified field. If the `inherit` | ||
| 403 | + /// parameter is set to `true` and the field value is not found at the current level, the | ||
| 404 | + /// method traverses up the parent hierarchy to find the value. The traversal stops when a | ||
| 405 | + /// value is found, when the root node is reached, or when a loop detection mechanism | ||
| 406 | + /// prevents further traversal. | ||
| 407 | + /// | ||
| 408 | + /// @tparam T The return type of the field value. | ||
| 409 | + /// @param name The name of the field to retrieve the value for. | ||
| 410 | + /// @param inherit If set to `true`, the function will attempt to retrieve the value by | ||
| 411 | + /// inheritance from the parent hierarchy of the form field. Defaults to `true`. | ||
| 412 | + /// @return Returns the field's value if found; otherwise, returns a default-constructed | ||
| 413 | + /// value of type `T`. | ||
| 414 | + template <class T> | ||
| 415 | + T | ||
| 416 | + inheritable_value(std::string const& name, bool inherit = true, bool acroform = false) const | ||
| 417 | + { | ||
| 418 | + if (auto& v = get(name)) { | ||
| 419 | + return {v}; | ||
| 420 | + } | ||
| 421 | + return {inherit ? inherited(name, acroform) : null_oh}; | ||
| 422 | + } | ||
| 423 | + | ||
| 424 | + /// @brief Retrieves an inherited field string attribute as a string. | ||
| 425 | + /// | ||
| 426 | + /// @param name The name of the field for which the value is to be retrieved. | ||
| 427 | + /// @return The inherited field value as a UTF-8 encoded string, or an empty string if the | ||
| 428 | + /// value does not exist or is not of String type. | ||
| 429 | + std::string inheritable_string(std::string const& name) const; | ||
| 430 | + | ||
| 431 | + /// @brief Retrieves the field type (/FT attribute). | ||
| 432 | + /// | ||
| 433 | + /// @param inherit If set to `true`, the function will attempt to retrieve the value by | ||
| 434 | + /// inheritance from the parent hierarchy of the form field. Defaults to `true`. | ||
| 435 | + /// @return Returns the field type if found; otherwise, returns a default-constructed | ||
| 436 | + /// `Name`. | ||
| 437 | + Name | ||
| 438 | + FT(bool inherit = true) const | ||
| 439 | + { | ||
| 440 | + return inheritable_value<Name>("/FT"); | ||
| 441 | + } | ||
| 442 | + | ||
| 443 | + /// @brief Retrieves the partial field name (/T attribute). | ||
| 444 | + /// | ||
| 445 | + /// @return Returns the partial field name if found; otherwise, returns a | ||
| 446 | + /// default-constructed `String`. | ||
| 447 | + String | ||
| 448 | + T() const | ||
| 449 | + { | ||
| 450 | + return {get("/T")}; | ||
| 451 | + } | ||
| 452 | + | ||
| 453 | + /// @brief Retrieves the alternative name (/TU attribute). | ||
| 454 | + /// | ||
| 455 | + /// @return Returns the alternative name if found; otherwise, returns a default-constructed | ||
| 456 | + /// `String`. | ||
| 457 | + String | ||
| 458 | + TU() const | ||
| 459 | + { | ||
| 460 | + return {get("/TU")}; | ||
| 461 | + } | ||
| 462 | + | ||
| 463 | + /// @brief Retrieves the mapping name (/TM attribute). | ||
| 464 | + /// | ||
| 465 | + /// @return Returns the mapping name if found; otherwise, returns a default-constructed | ||
| 466 | + /// `String`. | ||
| 467 | + String | ||
| 468 | + TM() const | ||
| 469 | + { | ||
| 470 | + return {get("/TM")}; | ||
| 471 | + } | ||
| 472 | + | ||
| 473 | + /// @brief Retrieves the fully qualified name of the form field. | ||
| 474 | + /// | ||
| 475 | + /// This method constructs the fully qualified name of the form field by traversing through | ||
| 476 | + /// its parent hierarchy. The fully qualified name is constructed by concatenating the /T | ||
| 477 | + /// (field name) attribute of each parent node with periods as separators, starting from the | ||
| 478 | + /// root of the hierarchy. | ||
| 479 | + /// | ||
| 480 | + /// If the field has no parent hierarchy, the result will simply be the /T attribute of the | ||
| 481 | + /// current field. In cases of potential circular references, loop detection is applied. | ||
| 482 | + /// | ||
| 483 | + /// @return A string representing the fully qualified name of the field. | ||
| 484 | + std::string fully_qualified_name() const; | ||
| 485 | + | ||
| 486 | + /// @brief Retrieves the partial name (/T attribute) of the form field. | ||
| 487 | + /// | ||
| 488 | + /// This method returns the value of the field's /T attribute, which is the partial name | ||
| 489 | + /// used to identify the field within its parent hierarchy. If the attribute is not set, an | ||
| 490 | + /// empty string is returned. | ||
| 491 | + /// | ||
| 492 | + /// @return A string representing the partial name of the field in UTF-8 encoding, or an | ||
| 493 | + /// empty string if the /T attribute is not present. | ||
| 494 | + std::string partial_name() const; | ||
| 495 | + | ||
| 496 | + /// @brief Retrieves the alternative name for the form field. | ||
| 497 | + /// | ||
| 498 | + /// This method attempts to return the alternative name (/TU) of the form field, which is | ||
| 499 | + /// the field name intended to be presented, to users as a UTF-8 string, if it exists. If | ||
| 500 | + /// the alternative name is not present, the method falls back to the fully qualified name | ||
| 501 | + /// of the form field. | ||
| 502 | + /// | ||
| 503 | + /// @return The alternative name of the form field as a string, or the | ||
| 504 | + /// fully qualified name if the alternative name is unavailable. | ||
| 505 | + std::string alternative_name() const; | ||
| 506 | + | ||
| 507 | + /// @brief Retrieves the mapping field name (/TM) for the form field. | ||
| 508 | + /// | ||
| 509 | + /// If the mapping name (/TM) is present, it is returned as a UTF-8 string. If not, it falls | ||
| 510 | + /// back to the 'alternative name', which is obtained using the `alternative_name()` method. | ||
| 511 | + /// | ||
| 512 | + /// @return The mapping field name (/TM) as a UTF-8 string or the alternative name if the | ||
| 513 | + /// mapping name is absent. | ||
| 514 | + std::string mapping_name() const; | ||
| 515 | + | ||
| 516 | + /// @brief Retrieves the field value (`/V` attribute) of a specified field, accounting for | ||
| 517 | + /// inheritance through the hierarchy of ancestor nodes in the form field tree. | ||
| 518 | + /// | ||
| 519 | + /// This function attempts to retrieve the `/V` attribute. If the `inherit` | ||
| 520 | + /// parameter is set to `true` and the `/V` is not found at the current level, the | ||
| 521 | + /// method traverses up the parent hierarchy to find the value. The traversal stops when | ||
| 522 | + /// `/V` is found, when the root node is reached, or when a loop detection mechanism | ||
| 523 | + /// prevents further traversal. | ||
| 524 | + /// | ||
| 525 | + /// @tparam T The return type. | ||
| 526 | + /// @param inherit If set to `true`, the function will attempt to retrieve `/V` by | ||
| 527 | + /// inheritance from the parent hierarchy of the form field. Defaults to `true`. | ||
| 528 | + /// @return Returns the field's value if found; otherwise, returns a default-constructed | ||
| 529 | + /// value of type `T`. | ||
| 530 | + template <class T> | ||
| 531 | + T | ||
| 532 | + V(bool inherit = true) const | ||
| 533 | + { | ||
| 534 | + return inheritable_value<T>("/V", inherit); | ||
| 535 | + } | ||
| 536 | + | ||
| 537 | + /// @brief Retrieves the field value (`/V` attribute) of a specified field, accounting for | ||
| 538 | + /// inheritance through the hierarchy of ancestor nodes in the form field tree. | ||
| 539 | + /// | ||
| 540 | + /// This function attempts to retrieve the `/V` attribute. If the `inherit` | ||
| 541 | + /// parameter is set to `true` and the `/V` is not found at the current level, the | ||
| 542 | + /// method traverses up the parent hierarchy to find the value. The traversal stops when | ||
| 543 | + /// `/V` is found, when the root node is reached, or when a loop detection mechanism | ||
| 544 | + /// prevents further traversal. | ||
| 545 | + /// | ||
| 546 | + /// @param inherit If set to `true`, the function will attempt to retrieve `/V` by | ||
| 547 | + /// inheritance from the parent hierarchy of the form field. Defaults to `true`. | ||
| 548 | + /// @return Returns the field's value if found; otherwise, returns a default-constructed | ||
| 549 | + /// object handle. | ||
| 550 | + QPDFObjectHandle const& | ||
| 551 | + V(bool inherit = true) const | ||
| 552 | + { | ||
| 553 | + if (auto& v = get("/V")) { | ||
| 554 | + return v; | ||
| 555 | + } | ||
| 556 | + return {inherit ? inherited("/V") : null_oh}; | ||
| 557 | + } | ||
| 558 | + | ||
| 559 | + /// @brief Retrieves the field value `/V` attribute of the form field, considering | ||
| 560 | + /// inheritance, if the value is a String. | ||
| 561 | + /// | ||
| 562 | + /// This function extracts the value of the form field, accounting for potential inheritance | ||
| 563 | + /// through the form hierarchy. It returns the value if it is a String, and an empty string | ||
| 564 | + /// otherwise. | ||
| 565 | + /// | ||
| 566 | + /// @return A string containing the actual or inherited `/V` attribute of the form field, or | ||
| 567 | + /// an empty string if the value is not present or not a String. | ||
| 568 | + std::string value() const; | ||
| 569 | + | ||
| 570 | + /// @brief Retrieves the field default value (`/DV` attribute) of a specified field, | ||
| 571 | + /// accounting for inheritance through the hierarchy of ancestor nodes in the form | ||
| 572 | + /// field tree. | ||
| 573 | + /// | ||
| 574 | + /// This function attempts to retrieve the `/DV` attribute. If the `inherit` parameter is | ||
| 575 | + /// set to `true` and the `/DV` is not found at the current level, the method traverses up | ||
| 576 | + /// the parent hierarchy to find the value. The traversal stops when | ||
| 577 | + /// `/DV` is found, when the root node is reached, or when a loop detection mechanism | ||
| 578 | + /// prevents further traversal. | ||
| 579 | + /// | ||
| 580 | + /// @tparam T The return type. | ||
| 581 | + /// @param inherit If set to `true`, the function will attempt to retrieve `/DV` by | ||
| 582 | + /// inheritance from the parent hierarchy of the form field. Defaults to `true`. | ||
| 583 | + /// @return Returns the field's default value if found; otherwise, returns a | ||
| 584 | + /// default-constructed value of type `T`. | ||
| 585 | + QPDFObjectHandle const& | ||
| 586 | + DV(bool inherit = true) const | ||
| 587 | + { | ||
| 588 | + if (auto& v = get("/DV")) { | ||
| 589 | + return v; | ||
| 590 | + } | ||
| 591 | + return {inherit ? inherited("/DV") : null_oh}; | ||
| 592 | + } | ||
| 593 | + | ||
| 594 | + /// @brief Retrieves the default value `/DV` attribute of the form field, considering | ||
| 595 | + /// inheritance, if the default value is a String. | ||
| 596 | + /// | ||
| 597 | + /// This function extracts the default value of the form field, accounting for potential | ||
| 598 | + /// inheritance through the form hierarchy. It returns the value if it is a String, and an | ||
| 599 | + /// empty string otherwise. | ||
| 600 | + /// | ||
| 601 | + /// @return A string containing the actual or inherited `/DV` attribute of the form field, | ||
| 602 | + /// or an empty string if the value is not present or not a String. | ||
| 603 | + std::string default_value() const; | ||
| 604 | + | ||
| 605 | + /// @brief Returns the default appearance string for the form field, considering inheritance | ||
| 606 | + /// from the field tree hierarchy and the document's /AcroForm dictionary. | ||
| 607 | + /// | ||
| 608 | + /// This method retrieves the field's /DA (default appearance) attribute. If the attribute | ||
| 609 | + /// is not directly available, it checks the parent fields in the hierarchy for an inherited | ||
| 610 | + /// value. If no value is found in the field hierarchy, it attempts to retrieve the /DA | ||
| 611 | + /// attribute from the document's /AcroForm dictionary. The method returns an empty string | ||
| 612 | + /// if no default appearance string is available or applicable. | ||
| 613 | + /// | ||
| 614 | + /// @return A string representing the default appearance, or an empty string if | ||
| 615 | + /// no value is found. | ||
| 616 | + std::string default_appearance() const; | ||
| 617 | + | ||
| 618 | + // Return the default resource dictionary for the field. This comes not from the field but | ||
| 619 | + // from the document-level /AcroForm dictionary. While several PDF generates put a /DR key | ||
| 620 | + // in the form field's dictionary, experimentation suggests that many popular readers, | ||
| 621 | + // including Adobe Acrobat and Acrobat Reader, ignore any /DR item on the field. | ||
| 622 | + QPDFObjectHandle getDefaultResources(); | ||
| 623 | + | ||
| 624 | + // Return the quadding value, taking inheritance from the field tree into account. Returns 0 | ||
| 625 | + // if quadding is not specified. Look in /AcroForm if not found in the field hierarchy. | ||
| 626 | + int getQuadding(); | ||
| 627 | + | ||
| 628 | + // Return field flags from /Ff. The value is a logical or of pdf_form_field_flag_e as | ||
| 629 | + // defined in qpdf/Constants.h// | ||
| 630 | + int getFlags(); | ||
| 631 | + | ||
| 632 | + // Methods for testing for particular types of form fields | ||
| 633 | + | ||
| 634 | + // Returns true if field is of type /Tx | ||
| 635 | + bool isText(); | ||
| 636 | + // Returns true if field is of type /Btn and flags do not indicate some other type of | ||
| 637 | + // button. | ||
| 638 | + bool isCheckbox(); | ||
| 639 | + | ||
| 640 | + // Returns true if field is a checkbox and is checked. | ||
| 641 | + bool isChecked(); | ||
| 642 | + | ||
| 643 | + // Returns true if field is of type /Btn and flags indicate that it is a radio button | ||
| 644 | + bool isRadioButton(); | ||
| 645 | + | ||
| 646 | + // Returns true if field is of type /Btn and flags indicate that it is a pushbutton | ||
| 647 | + bool isPushbutton(); | ||
| 648 | + | ||
| 649 | + // Returns true if fields if of type /Ch | ||
| 650 | + bool isChoice(); | ||
| 651 | + | ||
| 652 | + // Returns choices display values as UTF-8 strings | ||
| 653 | + std::vector<std::string> getChoices(); | ||
| 654 | + | ||
| 655 | + // Set an attribute to the given value. If you have a QPDFAcroFormDocumentHelper and you | ||
| 656 | + // want to set the name of a field, use QPDFAcroFormDocumentHelper::setFormFieldName | ||
| 657 | + // instead. | ||
| 658 | + void setFieldAttribute(std::string const& key, QPDFObjectHandle value); | ||
| 659 | + | ||
| 660 | + // Set an attribute to the given value as a Unicode string (UTF-16 BE encoded). The input | ||
| 661 | + // string should be UTF-8 encoded. If you have a QPDFAcroFormDocumentHelper and you want to | ||
| 662 | + // set the name of a field, use QPDFAcroFormDocumentHelper::setFormFieldName instead. | ||
| 663 | + void setFieldAttribute(std::string const& key, std::string const& utf8_value); | ||
| 664 | + | ||
| 665 | + // Set /V (field value) to the given value. If need_appearances is true and the field type | ||
| 666 | + // is either /Tx (text) or /Ch (choice), set /NeedAppearances to true. You can explicitly | ||
| 667 | + // tell this method not to set /NeedAppearances if you are going to generate an appearance | ||
| 668 | + // stream yourself. Starting with qpdf 8.3.0, this method handles fields of type /Btn | ||
| 669 | + // (checkboxes, radio buttons, pushbuttons) specially. When setting a checkbox value, any | ||
| 670 | + // value other than /Off will be treated as on, and the actual value set will be based on | ||
| 671 | + // the appearance stream's /N dictionary, so the value that ends up in /V may not exactly | ||
| 672 | + // match the value you pass in. | ||
| 673 | + void setV(QPDFObjectHandle value, bool need_appearances = true); | ||
| 674 | + | ||
| 675 | + // Set /V (field value) to the given string value encoded as a Unicode string. The input | ||
| 676 | + // value should be UTF-8 encoded. See comments above about /NeedAppearances. | ||
| 677 | + void setV(std::string const& utf8_value, bool need_appearances = true); | ||
| 678 | + | ||
| 679 | + // Update the appearance stream for this field. Note that qpdf's ability to generate | ||
| 680 | + // appearance streams is limited. We only generate appearance streams for streams of type | ||
| 681 | + // text or choice. The appearance uses the default parameters provided in the file, and it | ||
| 682 | + // only supports ASCII characters. Quadding is currently ignored. While this functionality | ||
| 683 | + // is limited, it should do a decent job on properly constructed PDF files when field values | ||
| 684 | + // are restricted to ASCII characters. | ||
| 685 | + void generateAppearance(QPDFAnnotationObjectHelper&); | ||
| 686 | + | ||
| 687 | + private: | ||
| 688 | + /// @brief Retrieves an entry from the document's /AcroForm dictionary using the specified | ||
| 689 | + /// name. | ||
| 690 | + /// | ||
| 691 | + /// The method accesses the AcroForm dictionary within the root object of the PDF document. | ||
| 692 | + /// If the AcroForm dictionary contains the given field name, it retrieves the | ||
| 693 | + /// corresponding entry. Otherwise, it returns a default-constructed object handle. | ||
| 694 | + /// | ||
| 695 | + /// @param name The name of the form field to retrieve. | ||
| 696 | + /// @return An object handle corresponding to the specified name within the AcroForm | ||
| 697 | + /// dictionary. | ||
| 698 | + QPDFObjectHandle const& | ||
| 699 | + from_AcroForm(std::string const& name) const | ||
| 700 | + { | ||
| 701 | + return {qpdf() ? qpdf()->getRoot()["/AcroForm"][name] : null_oh}; | ||
| 702 | + } | ||
| 703 | + | ||
| 704 | + void setRadioButtonValue(QPDFObjectHandle name); | ||
| 705 | + void setCheckBoxValue(bool value); | ||
| 706 | + void generateTextAppearance(QPDFAnnotationObjectHelper&); | ||
| 707 | + QPDFObjectHandle | ||
| 708 | + getFontFromResource(QPDFObjectHandle resources, std::string const& font_name); | ||
| 709 | + | ||
| 710 | + static const QPDFObjectHandle null_oh; | ||
| 711 | + }; // class FormNode | ||
| 712 | +} // namespace qpdf::impl | ||
| 713 | + | ||
| 714 | +class QPDFAcroFormDocumentHelper::Members: public qpdf::impl::AcroForm | ||
| 715 | +{ | ||
| 716 | + public: | ||
| 717 | + Members(QPDF& qpdf) : | ||
| 718 | + AcroForm(qpdf.doc()) | ||
| 719 | + { | ||
| 720 | + } | ||
| 721 | +}; | ||
| 722 | + | ||
| 723 | +#endif // ACRO_FORM_HH |
libqpdf/qpdf/FormField.hh deleted
| 1 | -#ifndef FORMFIELD_HH | ||
| 2 | -#define FORMFIELD_HH | ||
| 3 | - | ||
| 4 | -#include <qpdf/QPDFObjectHandle_private.hh> | ||
| 5 | -#include <qpdf/QPDFObjectHelper.hh> | ||
| 6 | - | ||
| 7 | -#include <vector> | ||
| 8 | - | ||
| 9 | -class QPDFAnnotationObjectHelper; | ||
| 10 | - | ||
| 11 | -namespace qpdf::impl | ||
| 12 | -{ | ||
| 13 | - // This object helper helps with form fields for interactive forms. Please see comments in | ||
| 14 | - // QPDFAcroFormDocumentHelper.hh for additional details. | ||
| 15 | - class FormField: public qpdf::BaseDictionary | ||
| 16 | - { | ||
| 17 | - public: | ||
| 18 | - FormField() = default; | ||
| 19 | - FormField(FormField const&) = default; | ||
| 20 | - FormField& operator=(FormField const&) = default; | ||
| 21 | - FormField(FormField&&) = default; | ||
| 22 | - FormField& operator=(FormField&&) = default; | ||
| 23 | - ~FormField() = default; | ||
| 24 | - | ||
| 25 | - FormField(QPDFObjectHandle const& oh) : | ||
| 26 | - BaseDictionary(oh) | ||
| 27 | - { | ||
| 28 | - } | ||
| 29 | - | ||
| 30 | - FormField(QPDFObjectHandle&& oh) : | ||
| 31 | - BaseDictionary(std::move(oh)) | ||
| 32 | - { | ||
| 33 | - } | ||
| 34 | - | ||
| 35 | - /// Retrieves the /Parent form field of the current field. | ||
| 36 | - /// | ||
| 37 | - /// This function accesses the parent field in the hierarchical structure of form fields, if | ||
| 38 | - /// it exists. The parent is determined based on the /Parent attribute in the field | ||
| 39 | - /// dictionary. | ||
| 40 | - /// | ||
| 41 | - /// @return A FormField object representing the parent field. If the current field has no | ||
| 42 | - /// parent, an empty FormField object is returned. | ||
| 43 | - FormField | ||
| 44 | - Parent() | ||
| 45 | - { | ||
| 46 | - return {get("/Parent")}; | ||
| 47 | - } | ||
| 48 | - | ||
| 49 | - /// @brief Returns the top-level field associated with the current field. | ||
| 50 | - /// | ||
| 51 | - /// The function traverses the hierarchy of parent fields to identify the highest-level | ||
| 52 | - /// field in the tree. Typically, this will be the current field itself unless it has a | ||
| 53 | - /// parent field. Optionally, it can indicate whether the top-level field is different from | ||
| 54 | - /// the current field. | ||
| 55 | - /// | ||
| 56 | - /// @param is_different A pointer to a boolean that, if provided, will be set to true if the | ||
| 57 | - /// top-level field differs from the current field; otherwise, it will be set to | ||
| 58 | - /// false. | ||
| 59 | - /// | ||
| 60 | - /// @return The top-level field in the form field hierarchy. | ||
| 61 | - FormField root_field(bool* is_different = nullptr); | ||
| 62 | - | ||
| 63 | - /// @brief Retrieves the inherited value of the specified attribute. | ||
| 64 | - /// | ||
| 65 | - /// @param name The name of the attribute to retrieve. | ||
| 66 | - /// @param acroform If true, checks the document's /AcroForm dictionary for the attribute | ||
| 67 | - /// if it is not found in the field hierarchy. | ||
| 68 | - /// | ||
| 69 | - /// @return A constant reference to the QPDFObjectHandle representing the value of the | ||
| 70 | - /// specified attribute, if found. If the attribute is not found in the field | ||
| 71 | - /// hierarchy or the /AcroForm dictionary (when `acroform` is true), returns a | ||
| 72 | - /// reference to a static null object handle. | ||
| 73 | - QPDFObjectHandle const& inherited(std::string const& name, bool acroform = false) const; | ||
| 74 | - | ||
| 75 | - /// @brief Retrieves the value of a specified field, accounting for inheritance through the | ||
| 76 | - /// hierarchy of ancestor nodes in the form field tree. | ||
| 77 | - /// | ||
| 78 | - /// This function attempts to retrieve the value of the specified field. If the `inherit` | ||
| 79 | - /// parameter is set to `true` and the field value is not found at the current level, the | ||
| 80 | - /// method traverses up the parent hierarchy to find the value. The traversal stops when a | ||
| 81 | - /// value is found, when the root node is reached, or when a loop detection mechanism | ||
| 82 | - /// prevents further traversal. | ||
| 83 | - /// | ||
| 84 | - /// @tparam T The return type of the field value. | ||
| 85 | - /// @param name The name of the field to retrieve the value for. | ||
| 86 | - /// @param inherit If set to `true`, the function will attempt to retrieve the value by | ||
| 87 | - /// inheritance from the parent hierarchy of the form field. Defaults to `true`. | ||
| 88 | - /// @return Returns the field's value if found; otherwise, returns a default-constructed | ||
| 89 | - /// value of type `T`. | ||
| 90 | - template <class T> | ||
| 91 | - T | ||
| 92 | - inheritable_value(std::string const& name, bool inherit = true, bool acroform = false) const | ||
| 93 | - { | ||
| 94 | - if (auto& v = get(name)) { | ||
| 95 | - return {v}; | ||
| 96 | - } | ||
| 97 | - return {inherit ? inherited(name, acroform) : null_oh}; | ||
| 98 | - } | ||
| 99 | - | ||
| 100 | - /// @brief Retrieves an inherited field string attribute as a string. | ||
| 101 | - /// | ||
| 102 | - /// @param name The name of the field for which the value is to be retrieved. | ||
| 103 | - /// @return The inherited field value as a UTF-8 encoded string, or an empty string if the | ||
| 104 | - /// value does not exist or is not of String type. | ||
| 105 | - std::string inheritable_string(std::string const& name) const; | ||
| 106 | - | ||
| 107 | - /// @brief Retrieves the field type (/FT attribute). | ||
| 108 | - /// | ||
| 109 | - /// @param inherit If set to `true`, the function will attempt to retrieve the value by | ||
| 110 | - /// inheritance from the parent hierarchy of the form field. Defaults to `true`. | ||
| 111 | - /// @return Returns the field type if found; otherwise, returns a default-constructed | ||
| 112 | - /// `Name`. | ||
| 113 | - Name | ||
| 114 | - FT(bool inherit = true) const | ||
| 115 | - { | ||
| 116 | - return inheritable_value<Name>("/FT"); | ||
| 117 | - } | ||
| 118 | - | ||
| 119 | - /// @brief Retrieves the partial field name (/T attribute). | ||
| 120 | - /// | ||
| 121 | - /// @return Returns the partial field name if found; otherwise, returns a | ||
| 122 | - /// default-constructed `String`. | ||
| 123 | - String | ||
| 124 | - T() const | ||
| 125 | - { | ||
| 126 | - return {get("/T")}; | ||
| 127 | - } | ||
| 128 | - | ||
| 129 | - /// @brief Retrieves the alternative name (/TU attribute). | ||
| 130 | - /// | ||
| 131 | - /// @return Returns the alternative name if found; otherwise, returns a default-constructed | ||
| 132 | - /// `String`. | ||
| 133 | - String | ||
| 134 | - TU() const | ||
| 135 | - { | ||
| 136 | - return {get("/TU")}; | ||
| 137 | - } | ||
| 138 | - | ||
| 139 | - /// @brief Retrieves the mapping name (/TM attribute). | ||
| 140 | - /// | ||
| 141 | - /// @return Returns the mapping name if found; otherwise, returns a default-constructed | ||
| 142 | - /// `String`. | ||
| 143 | - String | ||
| 144 | - TM() const | ||
| 145 | - { | ||
| 146 | - return {get("/TM")}; | ||
| 147 | - } | ||
| 148 | - | ||
| 149 | - /// @brief Retrieves the fully qualified name of the form field. | ||
| 150 | - /// | ||
| 151 | - /// This method constructs the fully qualified name of the form field by traversing through | ||
| 152 | - /// its parent hierarchy. The fully qualified name is constructed by concatenating the /T | ||
| 153 | - /// (field name) attribute of each parent node with periods as separators, starting from the | ||
| 154 | - /// root of the hierarchy. | ||
| 155 | - /// | ||
| 156 | - /// If the field has no parent hierarchy, the result will simply be the /T attribute of the | ||
| 157 | - /// current field. In cases of potential circular references, loop detection is applied. | ||
| 158 | - /// | ||
| 159 | - /// @return A string representing the fully qualified name of the field. | ||
| 160 | - std::string fully_qualified_name() const; | ||
| 161 | - | ||
| 162 | - /// @brief Retrieves the partial name (/T attribute) of the form field. | ||
| 163 | - /// | ||
| 164 | - /// This method returns the value of the field's /T attribute, which is the partial name | ||
| 165 | - /// used to identify the field within its parent hierarchy. If the attribute is not set, an | ||
| 166 | - /// empty string is returned. | ||
| 167 | - /// | ||
| 168 | - /// @return A string representing the partial name of the field in UTF-8 encoding, or an | ||
| 169 | - /// empty string if the /T attribute is not present. | ||
| 170 | - std::string partial_name() const; | ||
| 171 | - | ||
| 172 | - /// @brief Retrieves the alternative name for the form field. | ||
| 173 | - /// | ||
| 174 | - /// This method attempts to return the alternative name (/TU) of the form field, which is | ||
| 175 | - /// the field name intended to be presented, to users as a UTF-8 string, if it exists. If | ||
| 176 | - /// the alternative name is not present, the method falls back to the fully qualified name | ||
| 177 | - /// of the form field. | ||
| 178 | - /// | ||
| 179 | - /// @return The alternative name of the form field as a string, or the | ||
| 180 | - /// fully qualified name if the alternative name is unavailable. | ||
| 181 | - std::string alternative_name() const; | ||
| 182 | - | ||
| 183 | - /// @brief Retrieves the mapping field name (/TM) for the form field. | ||
| 184 | - /// | ||
| 185 | - /// If the mapping name (/TM) is present, it is returned as a UTF-8 string. If not, it falls | ||
| 186 | - /// back to the 'alternative name', which is obtained using the `alternative_name()` method. | ||
| 187 | - /// | ||
| 188 | - /// @return The mapping field name (/TM) as a UTF-8 string or the alternative name if the | ||
| 189 | - /// mapping name is absent. | ||
| 190 | - std::string mapping_name() const; | ||
| 191 | - | ||
| 192 | - /// @brief Retrieves the field value (`/V` attribute) of a specified field, accounting for | ||
| 193 | - /// inheritance through thehierarchy of ancestor nodes in the form field tree. | ||
| 194 | - /// | ||
| 195 | - /// This function attempts to retrieve the `/V` attribute. If the `inherit` | ||
| 196 | - /// parameter is set to `true` and the `/V` is not found at the current level, the | ||
| 197 | - /// method traverses up the parent hierarchy to find the value. The traversal stops when | ||
| 198 | - /// `/V` is found, when the root node is reached, or when a loop detection mechanism | ||
| 199 | - /// prevents further traversal. | ||
| 200 | - /// | ||
| 201 | - /// @tparam T The return type. | ||
| 202 | - /// @param inherit If set to `true`, the function will attempt to retrieve `/V` by | ||
| 203 | - /// inheritance from the parent hierarchy of the form field. Defaults to `true`. | ||
| 204 | - /// @return Returns the field's value if found; otherwise, returns a default-constructed | ||
| 205 | - /// value of type `T`. | ||
| 206 | - template <class T> | ||
| 207 | - T | ||
| 208 | - V(bool inherit = true) const | ||
| 209 | - { | ||
| 210 | - return inheritable_value<T>("/V", inherit); | ||
| 211 | - } | ||
| 212 | - | ||
| 213 | - /// @brief Retrieves the field value (`/V` attribute) of a specified field, accounting for | ||
| 214 | - /// inheritance through the hierarchy of ancestor nodes in the form field tree. | ||
| 215 | - /// | ||
| 216 | - /// This function attempts to retrieve the `/V` attribute. If the `inherit` | ||
| 217 | - /// parameter is set to `true` and the `/V` is not found at the current level, the | ||
| 218 | - /// method traverses up the parent hierarchy to find the value. The traversal stops when | ||
| 219 | - /// `/V` is found, when the root node is reached, or when a loop detection mechanism | ||
| 220 | - /// prevents further traversal. | ||
| 221 | - /// | ||
| 222 | - /// @param inherit If set to `true`, the function will attempt to retrieve `/V` by | ||
| 223 | - /// inheritance from the parent hierarchy of the form field. Defaults to `true`. | ||
| 224 | - /// @return Returns the field's value if found; otherwise, returns a default-constructed | ||
| 225 | - /// object handle. | ||
| 226 | - QPDFObjectHandle const& | ||
| 227 | - V(bool inherit = true) const | ||
| 228 | - { | ||
| 229 | - if (auto& v = get("/V")) { | ||
| 230 | - return v; | ||
| 231 | - } | ||
| 232 | - return {inherit ? inherited("/V") : null_oh}; | ||
| 233 | - } | ||
| 234 | - | ||
| 235 | - /// @brief Retrieves the field value `/V` attribute of the form field, considering | ||
| 236 | - /// inheritance, if the value is a String. | ||
| 237 | - /// | ||
| 238 | - /// This function extracts the value of the form field, accounting for potential inheritance | ||
| 239 | - /// through the form hierarchy. It returns the value if it is a String, and an empty string | ||
| 240 | - /// otherwise. | ||
| 241 | - /// | ||
| 242 | - /// @return A string containing the actual or inherited `/V` attribute of the form field, or | ||
| 243 | - /// an empty string if the value is not present or not a String. | ||
| 244 | - std::string value() const; | ||
| 245 | - | ||
| 246 | - /// @brief Retrieves the field default value (`/DV` attribute) of a specified field, | ||
| 247 | - /// accounting for inheritance through the hierarchy of ancestor nodes in the form | ||
| 248 | - /// field tree. | ||
| 249 | - /// | ||
| 250 | - /// This function attempts to retrieve the `/DV` attribute. If the `inherit` parameter is | ||
| 251 | - /// set to `true` and the `/DV` is not found at the current level, the method traverses up | ||
| 252 | - /// the parent hierarchy to find the value. The traversal stops when | ||
| 253 | - /// `/DV` is found, when the root node is reached, or when a loop detection mechanism | ||
| 254 | - /// prevents further traversal. | ||
| 255 | - /// | ||
| 256 | - /// @tparam T The return type. | ||
| 257 | - /// @param inherit If set to `true`, the function will attempt to retrieve `/DV` by | ||
| 258 | - /// inheritance from the parent hierarchy of the form field. Defaults to `true`. | ||
| 259 | - /// @return Returns the field's default value if found; otherwise, returns a | ||
| 260 | - /// default-constructed value of type `T`. | ||
| 261 | - QPDFObjectHandle const& | ||
| 262 | - DV(bool inherit = true) const | ||
| 263 | - { | ||
| 264 | - if (auto& v = get("/DV")) { | ||
| 265 | - return v; | ||
| 266 | - } | ||
| 267 | - return {inherit ? inherited("/DV") : null_oh}; | ||
| 268 | - } | ||
| 269 | - | ||
| 270 | - /// @brief Retrieves the default value `/DV` attribute of the form field, considering | ||
| 271 | - /// inheritance, if the default value is a String. | ||
| 272 | - /// | ||
| 273 | - /// This function extracts the default value of the form field, accounting for potential | ||
| 274 | - /// inheritance through the form hierarchy. It returns the value if it is a String, and an | ||
| 275 | - /// empty string otherwise. | ||
| 276 | - /// | ||
| 277 | - /// @return A string containing the actual or inherited `/V` attribute of the form field, or | ||
| 278 | - /// an empty string if the value is not present or not a String. | ||
| 279 | - std::string default_value() const; | ||
| 280 | - | ||
| 281 | - /// @brief Returns the default appearance string for the form field, considering inheritance | ||
| 282 | - /// from the field tree hierarchy and the document's /AcroForm dictionary. | ||
| 283 | - /// | ||
| 284 | - /// This method retrieves the field's /DA (default appearance) attribute. If the attribute | ||
| 285 | - /// is not directly available, it checks the parent fields in the hierarchy for an inherited | ||
| 286 | - /// value. If no value is found in the field hierarchy, it attempts to retrieve the /DA | ||
| 287 | - /// attribute from the document's /AcroForm dictionary. The method returns an empty string | ||
| 288 | - /// if no default appearance string is available or applicable. | ||
| 289 | - /// | ||
| 290 | - /// @return A string representing the default appearance, or an empty string if | ||
| 291 | - /// no value is found. | ||
| 292 | - std::string default_appearance() const; | ||
| 293 | - | ||
| 294 | - // Return the default resource dictionary for the field. This comes not from the field but | ||
| 295 | - // from the document-level /AcroForm dictionary. While several PDF generates put a /DR key | ||
| 296 | - // in the form field's dictionary, experimentation suggests that many popular readers, | ||
| 297 | - // including Adobe Acrobat and Acrobat Reader, ignore any /DR item on the field. | ||
| 298 | - QPDFObjectHandle getDefaultResources(); | ||
| 299 | - | ||
| 300 | - // Return the quadding value, taking inheritance from the field tree into account. Returns 0 | ||
| 301 | - // if quadding is not specified. Look in /AcroForm if not found in the field hierarchy. | ||
| 302 | - int getQuadding(); | ||
| 303 | - | ||
| 304 | - // Return field flags from /Ff. The value is a logical or of pdf_form_field_flag_e as | ||
| 305 | - // defined in qpdf/Constants.h// | ||
| 306 | - int getFlags(); | ||
| 307 | - | ||
| 308 | - // Methods for testing for particular types of form fields | ||
| 309 | - | ||
| 310 | - // Returns true if field is of type /Tx | ||
| 311 | - bool isText(); | ||
| 312 | - // Returns true if field is of type /Btn and flags do not indicate some other type of | ||
| 313 | - // button. | ||
| 314 | - bool isCheckbox(); | ||
| 315 | - | ||
| 316 | - // Returns true if field is a checkbox and is checked. | ||
| 317 | - bool isChecked(); | ||
| 318 | - | ||
| 319 | - // Returns true if field is of type /Btn and flags indicate that it is a radio button | ||
| 320 | - bool isRadioButton(); | ||
| 321 | - | ||
| 322 | - // Returns true if field is of type /Btn and flags indicate that it is a pushbutton | ||
| 323 | - bool isPushbutton(); | ||
| 324 | - | ||
| 325 | - // Returns true if fields if of type /Ch | ||
| 326 | - bool isChoice(); | ||
| 327 | - | ||
| 328 | - // Returns choices display values as UTF-8 strings | ||
| 329 | - std::vector<std::string> getChoices(); | ||
| 330 | - | ||
| 331 | - // Set an attribute to the given value. If you have a QPDFAcroFormDocumentHelper and you | ||
| 332 | - // want to set the name of a field, use QPDFAcroFormDocumentHelper::setFormFieldName | ||
| 333 | - // instead. | ||
| 334 | - void setFieldAttribute(std::string const& key, QPDFObjectHandle value); | ||
| 335 | - | ||
| 336 | - // Set an attribute to the given value as a Unicode string (UTF-16 BE encoded). The input | ||
| 337 | - // string should be UTF-8 encoded. If you have a QPDFAcroFormDocumentHelper and you want to | ||
| 338 | - // set the name of a field, use QPDFAcroFormDocumentHelper::setFormFieldName instead. | ||
| 339 | - void setFieldAttribute(std::string const& key, std::string const& utf8_value); | ||
| 340 | - | ||
| 341 | - // Set /V (field value) to the given value. If need_appearances is true and the field type | ||
| 342 | - // is either /Tx (text) or /Ch (choice), set /NeedAppearances to true. You can explicitly | ||
| 343 | - // tell this method not to set /NeedAppearances if you are going to generate an appearance | ||
| 344 | - // stream yourself. Starting with qpdf 8.3.0, this method handles fields of type /Btn | ||
| 345 | - // (checkboxes, radio buttons, pushbuttons) specially. When setting a checkbox value, any | ||
| 346 | - // value other than /Off will be treated as on, and the actual value set will be based on | ||
| 347 | - // the appearance stream's /N dictionary, so the value that ends up in /V may not exactly | ||
| 348 | - // match the value you pass in. | ||
| 349 | - void setV(QPDFObjectHandle value, bool need_appearances = true); | ||
| 350 | - | ||
| 351 | - // Set /V (field value) to the given string value encoded as a Unicode string. The input | ||
| 352 | - // value should be UTF-8 encoded. See comments above about /NeedAppearances. | ||
| 353 | - void setV(std::string const& utf8_value, bool need_appearances = true); | ||
| 354 | - | ||
| 355 | - // Update the appearance stream for this field. Note that qpdf's ability to generate | ||
| 356 | - // appearance streams is limited. We only generate appearance streams for streams of type | ||
| 357 | - // text or choice. The appearance uses the default parameters provided in the file, and it | ||
| 358 | - // only supports ASCII characters. Quadding is currently ignored. While this functionality | ||
| 359 | - // is limited, it should do a decent job on properly constructed PDF files when field values | ||
| 360 | - // are restricted to ASCII characters. | ||
| 361 | - void generateAppearance(QPDFAnnotationObjectHelper&); | ||
| 362 | - | ||
| 363 | - private: | ||
| 364 | - /// @brief Retrieves an entry from the document's /AcroForm dictionary using the specified | ||
| 365 | - /// name. | ||
| 366 | - /// | ||
| 367 | - /// The method accesses the AcroForm dictionary within the root object of the PDF document. | ||
| 368 | - /// If the the AcroForm dictionary contains the given field name, it retrieves the | ||
| 369 | - /// corresponding entry. Otherwise, it returns a default-constructed object handle. | ||
| 370 | - /// | ||
| 371 | - /// @param name The name of the form field to retrieve. | ||
| 372 | - /// @return A object handle corresponding to the specified name within the AcroForm | ||
| 373 | - /// dictionary. | ||
| 374 | - QPDFObjectHandle const& | ||
| 375 | - from_AcroForm(std::string const& name) const | ||
| 376 | - { | ||
| 377 | - return {qpdf() ? qpdf()->getRoot()["/AcroForm"][name] : null_oh}; | ||
| 378 | - } | ||
| 379 | - | ||
| 380 | - void setRadioButtonValue(QPDFObjectHandle name); | ||
| 381 | - void setCheckBoxValue(bool value); | ||
| 382 | - void generateTextAppearance(QPDFAnnotationObjectHelper&); | ||
| 383 | - QPDFObjectHandle | ||
| 384 | - getFontFromResource(QPDFObjectHandle resources, std::string const& font_name); | ||
| 385 | - | ||
| 386 | - static const QPDFObjectHandle null_oh; | ||
| 387 | - }; | ||
| 388 | -} // namespace qpdf::impl | ||
| 389 | - | ||
| 390 | -#endif // FORMFIELD_HH |
libqpdf/qpdf/QPDF_private.hh
| @@ -28,8 +28,9 @@ namespace qpdf | @@ -28,8 +28,9 @@ namespace qpdf | ||
| 28 | 28 | ||
| 29 | namespace impl | 29 | namespace impl |
| 30 | { | 30 | { |
| 31 | + class AcroForm; | ||
| 31 | using Doc = QPDF::Doc; | 32 | using Doc = QPDF::Doc; |
| 32 | - } | 33 | + } // namespace impl |
| 33 | 34 | ||
| 34 | class Doc: public QPDF | 35 | class Doc: public QPDF |
| 35 | { | 36 | { |
| @@ -374,11 +375,33 @@ class QPDF::Doc | @@ -374,11 +375,33 @@ class QPDF::Doc | ||
| 374 | bool reconstructed_xref() const; | 375 | bool reconstructed_xref() const; |
| 375 | 376 | ||
| 376 | QPDFAcroFormDocumentHelper& | 377 | QPDFAcroFormDocumentHelper& |
| 378 | + acroform_dh() | ||
| 379 | + { | ||
| 380 | + if (!acroform_) { | ||
| 381 | + no_inspection(); | ||
| 382 | + init_acroform(); | ||
| 383 | + } | ||
| 384 | + return *acroform_dh_; | ||
| 385 | + } | ||
| 386 | + | ||
| 387 | + /// @brief Retrieves the shared impl::AcroForm instance associated with the document. | ||
| 388 | + /// | ||
| 389 | + /// @note The AcroForm class caches the form field structure for efficiency. If any part | ||
| 390 | + /// of the form field structure is modified directly the `validate` method MUST be | ||
| 391 | + /// called before calling any other AcroForm methods in order to refresh the cache. | ||
| 392 | + /// | ||
| 393 | + /// If the AcroForm instance has not already been initialized, the `init_acroform()` | ||
| 394 | + /// function is called to initialize it. | ||
| 395 | + /// | ||
| 396 | + /// @return A reference to the shared AcroForm object of the document. | ||
| 397 | + /// | ||
| 398 | + /// @since 12.3 | ||
| 399 | + impl::AcroForm& | ||
| 377 | acroform() | 400 | acroform() |
| 378 | { | 401 | { |
| 379 | if (!acroform_) { | 402 | if (!acroform_) { |
| 380 | no_inspection(); | 403 | no_inspection(); |
| 381 | - acroform_ = std::make_unique<QPDFAcroFormDocumentHelper>(qpdf); | 404 | + init_acroform(); |
| 382 | } | 405 | } |
| 383 | return *acroform_; | 406 | return *acroform_; |
| 384 | } | 407 | } |
| @@ -438,8 +461,11 @@ class QPDF::Doc | @@ -438,8 +461,11 @@ class QPDF::Doc | ||
| 438 | qpdf::Doc::Config cf; | 461 | qpdf::Doc::Config cf; |
| 439 | 462 | ||
| 440 | private: | 463 | private: |
| 464 | + void init_acroform(); | ||
| 465 | + | ||
| 441 | // Document Helpers; | 466 | // Document Helpers; |
| 442 | - std::unique_ptr<QPDFAcroFormDocumentHelper> acroform_; | 467 | + std::unique_ptr<QPDFAcroFormDocumentHelper> acroform_dh_; |
| 468 | + impl::AcroForm* acroform_{nullptr}; | ||
| 443 | std::unique_ptr<QPDFEmbeddedFileDocumentHelper> embedded_files_; | 469 | std::unique_ptr<QPDFEmbeddedFileDocumentHelper> embedded_files_; |
| 444 | std::unique_ptr<QPDFOutlineDocumentHelper> outlines_; | 470 | std::unique_ptr<QPDFOutlineDocumentHelper> outlines_; |
| 445 | std::unique_ptr<QPDFPageDocumentHelper> page_dh_; | 471 | std::unique_ptr<QPDFPageDocumentHelper> page_dh_; |
| @@ -1173,7 +1199,7 @@ class QPDF::Doc::Pages: Common | @@ -1173,7 +1199,7 @@ class QPDF::Doc::Pages: Common | ||
| 1173 | void flatten_annotations_for_page( | 1199 | void flatten_annotations_for_page( |
| 1174 | QPDFPageObjectHelper& page, | 1200 | QPDFPageObjectHelper& page, |
| 1175 | QPDFObjectHandle& resources, | 1201 | QPDFObjectHandle& resources, |
| 1176 | - QPDFAcroFormDocumentHelper& afdh, | 1202 | + impl::AcroForm& afdh, |
| 1177 | int required_flags, | 1203 | int required_flags, |
| 1178 | int forbidden_flags); | 1204 | int forbidden_flags); |
| 1179 | 1205 |
qpdf/test_driver.cc
| @@ -3567,7 +3567,6 @@ test_101(QPDF& pdf, char const* arg2) | @@ -3567,7 +3567,6 @@ test_101(QPDF& pdf, char const* arg2) | ||
| 3567 | std::cout << oh.unparseResolved() << '\n'; | 3567 | std::cout << oh.unparseResolved() << '\n'; |
| 3568 | } | 3568 | } |
| 3569 | 3569 | ||
| 3570 | - | ||
| 3571 | auto test_helper_throws = [&qpdf](auto helper_func) { | 3570 | auto test_helper_throws = [&qpdf](auto helper_func) { |
| 3572 | bool thrown = false; | 3571 | bool thrown = false; |
| 3573 | try { | 3572 | try { |