Commit bf615b8c9c9100152f7dd9861fc3f19716f09405
Committed by
GitHub
Merge pull request #1535 from m-holger/afdh
Refactor `QPDFAcroFormDocumentHelper`: simplify `traverseField`, remo…
Showing
3 changed files
with
25 additions
and
50 deletions
include/qpdf/QPDFAcroFormDocumentHelper.hh
| ... | ... | @@ -226,8 +226,7 @@ class QPDFAcroFormDocumentHelper: public QPDFDocumentHelper |
| 226 | 226 | |
| 227 | 227 | private: |
| 228 | 228 | void analyze(); |
| 229 | - void traverseField( | |
| 230 | - QPDFObjectHandle field, QPDFObjectHandle parent, int depth, QPDFObjGen::set& visited); | |
| 229 | + void traverseField(QPDFObjectHandle const& field, QPDFObjectHandle const& parent, int depth); | |
| 231 | 230 | QPDFObjectHandle getOrCreateAcroForm(); |
| 232 | 231 | void adjustInheritedFields( |
| 233 | 232 | QPDFObjectHandle obj, | ... | ... |
libqpdf/QPDFAcroFormDocumentHelper.cc
| ... | ... | @@ -89,8 +89,7 @@ QPDFAcroFormDocumentHelper::addFormField(QPDFFormFieldObjectHelper ff) |
| 89 | 89 | fields = acroform.replaceKeyAndGetNew("/Fields", QPDFObjectHandle::newArray()); |
| 90 | 90 | } |
| 91 | 91 | fields.appendItem(ff.getObjectHandle()); |
| 92 | - QPDFObjGen::set visited; | |
| 93 | - traverseField(ff.getObjectHandle(), QPDFObjectHandle::newNull(), 0, visited); | |
| 92 | + traverseField(ff.getObjectHandle(), {}, 0); | |
| 94 | 93 | } |
| 95 | 94 | |
| 96 | 95 | void |
| ... | ... | @@ -188,9 +187,7 @@ void |
| 188 | 187 | QPDFAcroFormDocumentHelper::setFormFieldName(QPDFFormFieldObjectHelper ff, std::string const& name) |
| 189 | 188 | { |
| 190 | 189 | ff.setFieldAttribute("/T", name); |
| 191 | - QPDFObjGen::set visited; | |
| 192 | - auto ff_oh = ff.getObjectHandle(); | |
| 193 | - traverseField(ff_oh, ff_oh.getKey("/Parent"), 0, visited); | |
| 190 | + traverseField(ff, ff["/Parent"], 0); | |
| 194 | 191 | } |
| 195 | 192 | |
| 196 | 193 | std::vector<QPDFFormFieldObjectHelper> |
| ... | ... | @@ -199,7 +196,7 @@ QPDFAcroFormDocumentHelper::getFormFields() |
| 199 | 196 | analyze(); |
| 200 | 197 | std::vector<QPDFFormFieldObjectHelper> result; |
| 201 | 198 | for (auto const& [og, data]: m->field_to) { |
| 202 | - if (!(data.annotations.empty())) { | |
| 199 | + if (!data.annotations.empty()) { | |
| 203 | 200 | result.emplace_back(qpdf.getObject(og)); |
| 204 | 201 | } |
| 205 | 202 | } |
| ... | ... | @@ -279,18 +276,14 @@ QPDFAcroFormDocumentHelper::analyze() |
| 279 | 276 | return; |
| 280 | 277 | } |
| 281 | 278 | QPDFObjectHandle fields = acroform.getKey("/Fields"); |
| 282 | - if (auto fa = fields.as_array(strict)) { | |
| 279 | + if (Array fa = fields) { | |
| 283 | 280 | // Traverse /AcroForm to find annotations and map them bidirectionally to fields. |
| 284 | - | |
| 285 | - QPDFObjGen::set visited; | |
| 286 | - QPDFObjectHandle null(QPDFObjectHandle::newNull()); | |
| 287 | 281 | for (auto const& field: fa) { |
| 288 | - traverseField(field, null, 0, visited); | |
| 282 | + traverseField(field, {}, 0); | |
| 289 | 283 | } |
| 290 | 284 | } else { |
| 291 | - QTC::TC("qpdf", "QPDFAcroFormDocumentHelper fields not array"); | |
| 292 | 285 | acroform.warn("/Fields key of /AcroForm dictionary is not an array; ignoring"); |
| 293 | - fields = QPDFObjectHandle::newArray(); | |
| 286 | + fields = Array::empty(); | |
| 294 | 287 | } |
| 295 | 288 | |
| 296 | 289 | // All Widget annotations should have been encountered by traversing /AcroForm, but in case any |
| ... | ... | @@ -305,7 +298,6 @@ QPDFAcroFormDocumentHelper::analyze() |
| 305 | 298 | QPDFObjectHandle annot(iter.getObjectHandle()); |
| 306 | 299 | QPDFObjGen og(annot.getObjGen()); |
| 307 | 300 | if (!m->annotation_to_field.contains(og)) { |
| 308 | - QTC::TC("qpdf", "QPDFAcroFormDocumentHelper orphaned widget"); | |
| 309 | 301 | // This is not supposed to happen, but it's easy enough for us to handle this case. |
| 310 | 302 | // Treat the annotation as its own field. This could allow qpdf to sensibly handle a |
| 311 | 303 | // case such as a PDF creator adding a self-contained annotation (merged with the |
| ... | ... | @@ -323,7 +315,7 @@ QPDFAcroFormDocumentHelper::analyze() |
| 323 | 315 | |
| 324 | 316 | void |
| 325 | 317 | QPDFAcroFormDocumentHelper::traverseField( |
| 326 | - QPDFObjectHandle field, QPDFObjectHandle parent, int depth, QPDFObjGen::set& visited) | |
| 318 | + QPDFObjectHandle const& field, QPDFObjectHandle const& parent, int depth) | |
| 327 | 319 | { |
| 328 | 320 | if (depth > 100) { |
| 329 | 321 | // Arbitrarily cut off recursion at a fixed depth to avoid specially crafted files that |
| ... | ... | @@ -331,22 +323,19 @@ QPDFAcroFormDocumentHelper::traverseField( |
| 331 | 323 | return; |
| 332 | 324 | } |
| 333 | 325 | if (!field.indirect()) { |
| 334 | - QTC::TC("qpdf", "QPDFAcroFormDocumentHelper direct field"); | |
| 335 | 326 | field.warn( |
| 336 | - "encountered a direct object as a field or annotation while " | |
| 337 | - "traversing /AcroForm; ignoring field or annotation"); | |
| 327 | + "encountered a direct object as a field or annotation while traversing /AcroForm; " | |
| 328 | + "ignoring field or annotation"); | |
| 338 | 329 | return; |
| 339 | 330 | } |
| 340 | 331 | if (!field.isDictionary()) { |
| 341 | - QTC::TC("qpdf", "QPDFAcroFormDocumentHelper non-dictionary field"); | |
| 342 | 332 | field.warn( |
| 343 | - "encountered a non-dictionary as a field or annotation while" | |
| 344 | - " traversing /AcroForm; ignoring field or annotation"); | |
| 333 | + "encountered a non-dictionary as a field or annotation while traversing /AcroForm; " | |
| 334 | + "ignoring field or annotation"); | |
| 345 | 335 | return; |
| 346 | 336 | } |
| 347 | 337 | QPDFObjGen og(field.getObjGen()); |
| 348 | - if (!visited.add(og)) { | |
| 349 | - QTC::TC("qpdf", "QPDFAcroFormDocumentHelper loop"); | |
| 338 | + if (m->field_to.contains(og) || m->annotation_to_field.contains(og)) { | |
| 350 | 339 | field.warn("loop detected while traversing /AcroForm"); |
| 351 | 340 | return; |
| 352 | 341 | } |
| ... | ... | @@ -357,21 +346,10 @@ QPDFAcroFormDocumentHelper::traverseField( |
| 357 | 346 | // fields can be merged with terminal field dictionaries. Otherwise, the annotation fields might |
| 358 | 347 | // be there to be inherited by annotations below it. |
| 359 | 348 | |
| 360 | - bool is_annotation = false; | |
| 361 | - bool is_field = (0 == depth); | |
| 362 | - if (auto a = field.getKey("/Kids").as_array(strict)) { | |
| 363 | - is_field = true; | |
| 364 | - for (auto const& item: a) { | |
| 365 | - traverseField(item, field, 1 + depth, visited); | |
| 366 | - } | |
| 367 | - } else { | |
| 368 | - if (field.hasKey("/Parent")) { | |
| 369 | - is_field = true; | |
| 370 | - } | |
| 371 | - if (field.hasKey("/Subtype") || field.hasKey("/Rect") || field.hasKey("/AP")) { | |
| 372 | - is_annotation = true; | |
| 373 | - } | |
| 374 | - } | |
| 349 | + Array Kids = field["/Kids"]; | |
| 350 | + const bool is_field = depth == 0 || Kids || field.hasKey("/Parent"); | |
| 351 | + const bool is_annotation = | |
| 352 | + !Kids && (field.hasKey("/Subtype") || field.hasKey("/Rect") || field.hasKey("/AP")); | |
| 375 | 353 | |
| 376 | 354 | QTC::TC("qpdf", "QPDFAcroFormDocumentHelper field found", (depth == 0) ? 0 : 1); |
| 377 | 355 | QTC::TC("qpdf", "QPDFAcroFormDocumentHelper annotation found", (is_field ? 0 : 1)); |
| ... | ... | @@ -384,15 +362,18 @@ QPDFAcroFormDocumentHelper::traverseField( |
| 384 | 362 | |
| 385 | 363 | if (is_field && (field.hasKey("/T"))) { |
| 386 | 364 | QPDFFormFieldObjectHelper foh(field); |
| 387 | - auto f_og = field.getObjGen(); | |
| 388 | 365 | std::string name = foh.getFullyQualifiedName(); |
| 389 | - auto old = m->field_to.find(f_og); | |
| 366 | + auto old = m->field_to.find(og); | |
| 390 | 367 | if (old != m->field_to.end() && !old->second.name.empty()) { |
| 391 | 368 | // We might be updating after a name change, so remove any old information |
| 392 | - m->name_to_fields[old->second.name].erase(f_og); | |
| 369 | + m->name_to_fields[old->second.name].erase(og); | |
| 393 | 370 | } |
| 394 | - m->field_to[f_og].name = name; | |
| 395 | - m->name_to_fields[name].insert(f_og); | |
| 371 | + m->field_to[og].name = name; | |
| 372 | + m->name_to_fields[name].insert(og); | |
| 373 | + } | |
| 374 | + | |
| 375 | + for (auto const& kid: Kids) { | |
| 376 | + traverseField(kid, field, 1 + depth); | |
| 396 | 377 | } |
| 397 | 378 | } |
| 398 | 379 | ... | ... |
qpdf/qpdf.testcov
| ... | ... | @@ -204,11 +204,6 @@ QPDFAnnotationObjectHelper AP stream 0 |
| 204 | 204 | QPDFAnnotationObjectHelper AP dictionary 0 |
| 205 | 205 | QPDFAnnotationObjectHelper AP sub stream 0 |
| 206 | 206 | QPDFAnnotationObjectHelper AP null 0 |
| 207 | -QPDFAcroFormDocumentHelper fields not array 0 | |
| 208 | -QPDFAcroFormDocumentHelper orphaned widget 0 | |
| 209 | -QPDFAcroFormDocumentHelper direct field 0 | |
| 210 | -QPDFAcroFormDocumentHelper non-dictionary field 0 | |
| 211 | -QPDFAcroFormDocumentHelper loop 0 | |
| 212 | 207 | QPDFAcroFormDocumentHelper field found 1 |
| 213 | 208 | QPDFAcroFormDocumentHelper annotation found 1 |
| 214 | 209 | QPDFJob automatically set keep files open 1 | ... | ... |