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,8 +226,7 @@ class QPDFAcroFormDocumentHelper: public QPDFDocumentHelper | ||
| 226 | 226 | ||
| 227 | private: | 227 | private: |
| 228 | void analyze(); | 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 | QPDFObjectHandle getOrCreateAcroForm(); | 230 | QPDFObjectHandle getOrCreateAcroForm(); |
| 232 | void adjustInheritedFields( | 231 | void adjustInheritedFields( |
| 233 | QPDFObjectHandle obj, | 232 | QPDFObjectHandle obj, |
libqpdf/QPDFAcroFormDocumentHelper.cc
| @@ -89,8 +89,7 @@ QPDFAcroFormDocumentHelper::addFormField(QPDFFormFieldObjectHelper ff) | @@ -89,8 +89,7 @@ QPDFAcroFormDocumentHelper::addFormField(QPDFFormFieldObjectHelper ff) | ||
| 89 | fields = acroform.replaceKeyAndGetNew("/Fields", QPDFObjectHandle::newArray()); | 89 | fields = acroform.replaceKeyAndGetNew("/Fields", QPDFObjectHandle::newArray()); |
| 90 | } | 90 | } |
| 91 | fields.appendItem(ff.getObjectHandle()); | 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 | void | 95 | void |
| @@ -188,9 +187,7 @@ void | @@ -188,9 +187,7 @@ void | ||
| 188 | QPDFAcroFormDocumentHelper::setFormFieldName(QPDFFormFieldObjectHelper ff, std::string const& name) | 187 | QPDFAcroFormDocumentHelper::setFormFieldName(QPDFFormFieldObjectHelper ff, std::string const& name) |
| 189 | { | 188 | { |
| 190 | ff.setFieldAttribute("/T", name); | 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 | std::vector<QPDFFormFieldObjectHelper> | 193 | std::vector<QPDFFormFieldObjectHelper> |
| @@ -199,7 +196,7 @@ QPDFAcroFormDocumentHelper::getFormFields() | @@ -199,7 +196,7 @@ QPDFAcroFormDocumentHelper::getFormFields() | ||
| 199 | analyze(); | 196 | analyze(); |
| 200 | std::vector<QPDFFormFieldObjectHelper> result; | 197 | std::vector<QPDFFormFieldObjectHelper> result; |
| 201 | for (auto const& [og, data]: m->field_to) { | 198 | for (auto const& [og, data]: m->field_to) { |
| 202 | - if (!(data.annotations.empty())) { | 199 | + if (!data.annotations.empty()) { |
| 203 | result.emplace_back(qpdf.getObject(og)); | 200 | result.emplace_back(qpdf.getObject(og)); |
| 204 | } | 201 | } |
| 205 | } | 202 | } |
| @@ -279,18 +276,14 @@ QPDFAcroFormDocumentHelper::analyze() | @@ -279,18 +276,14 @@ QPDFAcroFormDocumentHelper::analyze() | ||
| 279 | return; | 276 | return; |
| 280 | } | 277 | } |
| 281 | QPDFObjectHandle fields = acroform.getKey("/Fields"); | 278 | QPDFObjectHandle fields = acroform.getKey("/Fields"); |
| 282 | - if (auto fa = fields.as_array(strict)) { | 279 | + if (Array fa = fields) { |
| 283 | // Traverse /AcroForm to find annotations and map them bidirectionally to fields. | 280 | // Traverse /AcroForm to find annotations and map them bidirectionally to fields. |
| 284 | - | ||
| 285 | - QPDFObjGen::set visited; | ||
| 286 | - QPDFObjectHandle null(QPDFObjectHandle::newNull()); | ||
| 287 | for (auto const& field: fa) { | 281 | for (auto const& field: fa) { |
| 288 | - traverseField(field, null, 0, visited); | 282 | + traverseField(field, {}, 0); |
| 289 | } | 283 | } |
| 290 | } else { | 284 | } else { |
| 291 | - QTC::TC("qpdf", "QPDFAcroFormDocumentHelper fields not array"); | ||
| 292 | acroform.warn("/Fields key of /AcroForm dictionary is not an array; ignoring"); | 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 | // All Widget annotations should have been encountered by traversing /AcroForm, but in case any | 289 | // All Widget annotations should have been encountered by traversing /AcroForm, but in case any |
| @@ -305,7 +298,6 @@ QPDFAcroFormDocumentHelper::analyze() | @@ -305,7 +298,6 @@ QPDFAcroFormDocumentHelper::analyze() | ||
| 305 | QPDFObjectHandle annot(iter.getObjectHandle()); | 298 | QPDFObjectHandle annot(iter.getObjectHandle()); |
| 306 | QPDFObjGen og(annot.getObjGen()); | 299 | QPDFObjGen og(annot.getObjGen()); |
| 307 | if (!m->annotation_to_field.contains(og)) { | 300 | if (!m->annotation_to_field.contains(og)) { |
| 308 | - QTC::TC("qpdf", "QPDFAcroFormDocumentHelper orphaned widget"); | ||
| 309 | // This is not supposed to happen, but it's easy enough for us to handle this case. | 301 | // This is not supposed to happen, but it's easy enough for us to handle this case. |
| 310 | // Treat the annotation as its own field. This could allow qpdf to sensibly handle a | 302 | // Treat the annotation as its own field. This could allow qpdf to sensibly handle a |
| 311 | // case such as a PDF creator adding a self-contained annotation (merged with the | 303 | // case such as a PDF creator adding a self-contained annotation (merged with the |
| @@ -323,7 +315,7 @@ QPDFAcroFormDocumentHelper::analyze() | @@ -323,7 +315,7 @@ QPDFAcroFormDocumentHelper::analyze() | ||
| 323 | 315 | ||
| 324 | void | 316 | void |
| 325 | QPDFAcroFormDocumentHelper::traverseField( | 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 | if (depth > 100) { | 320 | if (depth > 100) { |
| 329 | // Arbitrarily cut off recursion at a fixed depth to avoid specially crafted files that | 321 | // Arbitrarily cut off recursion at a fixed depth to avoid specially crafted files that |
| @@ -331,22 +323,19 @@ QPDFAcroFormDocumentHelper::traverseField( | @@ -331,22 +323,19 @@ QPDFAcroFormDocumentHelper::traverseField( | ||
| 331 | return; | 323 | return; |
| 332 | } | 324 | } |
| 333 | if (!field.indirect()) { | 325 | if (!field.indirect()) { |
| 334 | - QTC::TC("qpdf", "QPDFAcroFormDocumentHelper direct field"); | ||
| 335 | field.warn( | 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 | return; | 329 | return; |
| 339 | } | 330 | } |
| 340 | if (!field.isDictionary()) { | 331 | if (!field.isDictionary()) { |
| 341 | - QTC::TC("qpdf", "QPDFAcroFormDocumentHelper non-dictionary field"); | ||
| 342 | field.warn( | 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 | return; | 335 | return; |
| 346 | } | 336 | } |
| 347 | QPDFObjGen og(field.getObjGen()); | 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 | field.warn("loop detected while traversing /AcroForm"); | 339 | field.warn("loop detected while traversing /AcroForm"); |
| 351 | return; | 340 | return; |
| 352 | } | 341 | } |
| @@ -357,21 +346,10 @@ QPDFAcroFormDocumentHelper::traverseField( | @@ -357,21 +346,10 @@ QPDFAcroFormDocumentHelper::traverseField( | ||
| 357 | // fields can be merged with terminal field dictionaries. Otherwise, the annotation fields might | 346 | // fields can be merged with terminal field dictionaries. Otherwise, the annotation fields might |
| 358 | // be there to be inherited by annotations below it. | 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 | QTC::TC("qpdf", "QPDFAcroFormDocumentHelper field found", (depth == 0) ? 0 : 1); | 354 | QTC::TC("qpdf", "QPDFAcroFormDocumentHelper field found", (depth == 0) ? 0 : 1); |
| 377 | QTC::TC("qpdf", "QPDFAcroFormDocumentHelper annotation found", (is_field ? 0 : 1)); | 355 | QTC::TC("qpdf", "QPDFAcroFormDocumentHelper annotation found", (is_field ? 0 : 1)); |
| @@ -384,15 +362,18 @@ QPDFAcroFormDocumentHelper::traverseField( | @@ -384,15 +362,18 @@ QPDFAcroFormDocumentHelper::traverseField( | ||
| 384 | 362 | ||
| 385 | if (is_field && (field.hasKey("/T"))) { | 363 | if (is_field && (field.hasKey("/T"))) { |
| 386 | QPDFFormFieldObjectHelper foh(field); | 364 | QPDFFormFieldObjectHelper foh(field); |
| 387 | - auto f_og = field.getObjGen(); | ||
| 388 | std::string name = foh.getFullyQualifiedName(); | 365 | std::string name = foh.getFullyQualifiedName(); |
| 389 | - auto old = m->field_to.find(f_og); | 366 | + auto old = m->field_to.find(og); |
| 390 | if (old != m->field_to.end() && !old->second.name.empty()) { | 367 | if (old != m->field_to.end() && !old->second.name.empty()) { |
| 391 | // We might be updating after a name change, so remove any old information | 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,11 +204,6 @@ QPDFAnnotationObjectHelper AP stream 0 | ||
| 204 | QPDFAnnotationObjectHelper AP dictionary 0 | 204 | QPDFAnnotationObjectHelper AP dictionary 0 |
| 205 | QPDFAnnotationObjectHelper AP sub stream 0 | 205 | QPDFAnnotationObjectHelper AP sub stream 0 |
| 206 | QPDFAnnotationObjectHelper AP null 0 | 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 | QPDFAcroFormDocumentHelper field found 1 | 207 | QPDFAcroFormDocumentHelper field found 1 |
| 213 | QPDFAcroFormDocumentHelper annotation found 1 | 208 | QPDFAcroFormDocumentHelper annotation found 1 |
| 214 | QPDFJob automatically set keep files open 1 | 209 | QPDFJob automatically set keep files open 1 |