Commit 3b6b32130627e337ba58e4b6dbfde21aebbac2fc

Authored by m-holger
Committed by GitHub
2 parents e51eee74 263afd97

Merge pull request #1586 from m-holger/ffoh

Refactor QPDFFormFieldObjectHelper
libqpdf/QPDFFormFieldObjectHelper.cc
@@ -18,6 +18,8 @@ using namespace qpdf; @@ -18,6 +18,8 @@ using namespace qpdf;
18 18
19 using FormField = qpdf::impl::FormField; 19 using FormField = qpdf::impl::FormField;
20 20
  21 +const QPDFObjectHandle FormField::null_oh;
  22 +
21 class QPDFFormFieldObjectHelper::Members: public FormField 23 class QPDFFormFieldObjectHelper::Members: public FormField
22 { 24 {
23 public: 25 public:
@@ -48,93 +50,71 @@ QPDFFormFieldObjectHelper::isNull() @@ -48,93 +50,71 @@ QPDFFormFieldObjectHelper::isNull()
48 QPDFFormFieldObjectHelper 50 QPDFFormFieldObjectHelper
49 QPDFFormFieldObjectHelper::getParent() 51 QPDFFormFieldObjectHelper::getParent()
50 { 52 {
51 - return {Null::if_null(m->getParent().oh())};  
52 -}  
53 -  
54 -FormField  
55 -FormField::getParent()  
56 -{  
57 - return {oh()["/Parent"]}; // maybe null 53 + return {Null::if_null(m->Parent().oh())};
58 } 54 }
59 55
60 QPDFFormFieldObjectHelper 56 QPDFFormFieldObjectHelper
61 QPDFFormFieldObjectHelper::getTopLevelField(bool* is_different) 57 QPDFFormFieldObjectHelper::getTopLevelField(bool* is_different)
62 { 58 {
63 - return Null::if_null(m->getTopLevelField(is_different).oh()); 59 + return Null::if_null(m->root_field(is_different).oh());
64 } 60 }
65 61
66 FormField 62 FormField
67 -FormField::getTopLevelField(bool* is_different) 63 +FormField::root_field(bool* is_different)
68 { 64 {
  65 + if (is_different) {
  66 + *is_different = false;
  67 + }
69 if (!obj) { 68 if (!obj) {
70 return {}; 69 return {};
71 } 70 }
72 - auto top_field = oh(); 71 + auto rf = *this;
  72 + size_t depth = 0; // Don't bother with loop detection until depth becomes suspicious
73 QPDFObjGen::set seen; 73 QPDFObjGen::set seen;
74 - while (seen.add(top_field) && !top_field.getKeyIfDict("/Parent").null()) {  
75 - top_field = top_field.getKey("/Parent"); 74 + while (rf.Parent() && (++depth < 10 || seen.add(rf))) {
  75 + rf = rf.Parent();
76 if (is_different) { 76 if (is_different) {
77 *is_different = true; 77 *is_different = true;
78 } 78 }
79 } 79 }
80 - return {top_field};  
81 -}  
82 -  
83 -QPDFObjectHandle  
84 -FormField::getFieldFromAcroForm(std::string const& name)  
85 -{  
86 - QPDFObjectHandle result = QPDFObjectHandle::newNull();  
87 - // Fields are supposed to be indirect, so this should work.  
88 - QPDF* q = oh().getOwningQPDF();  
89 - if (!q) {  
90 - return result;  
91 - }  
92 - auto acroform = q->getRoot().getKey("/AcroForm");  
93 - if (!acroform.isDictionary()) {  
94 - return result;  
95 - }  
96 - return acroform.getKey(name); 80 + return rf;
97 } 81 }
98 82
99 QPDFObjectHandle 83 QPDFObjectHandle
100 QPDFFormFieldObjectHelper::getInheritableFieldValue(std::string const& name) 84 QPDFFormFieldObjectHelper::getInheritableFieldValue(std::string const& name)
101 { 85 {
102 - return m->getInheritableFieldValue(name); 86 + return Null::if_null(m->inheritable_value<QPDFObjectHandle>(name));
103 } 87 }
104 88
105 -QPDFObjectHandle  
106 -FormField::getInheritableFieldValue(std::string const& name) 89 +QPDFObjectHandle const&
  90 +FormField::inherited(std::string const& name, bool acroform) const
107 { 91 {
108 - QPDFObjectHandle node = oh();  
109 - if (!node.isDictionary()) {  
110 - return QPDFObjectHandle::newNull(); 92 + if (!obj) {
  93 + return null_oh;
111 } 94 }
112 - QPDFObjectHandle result(node.getKey(name));  
113 - if (result.null()) {  
114 - QPDFObjGen::set seen;  
115 - while (seen.add(node) && node.hasKey("/Parent")) {  
116 - node = node.getKey("/Parent");  
117 - result = node.getKey(name);  
118 - if (!result.null()) {  
119 - return result;  
120 - } 95 + auto node = *this;
  96 + QPDFObjGen::set seen;
  97 + size_t depth = 0; // Don't bother with loop detection until depth becomes suspicious
  98 + while (node.Parent() && (++depth < 10 || seen.add(node))) {
  99 + node = node.Parent();
  100 + if (auto const& result = node[name]) {
  101 + return {result};
121 } 102 }
122 } 103 }
123 - return result; 104 + return acroform ? from_AcroForm(name) : null_oh;
124 } 105 }
125 106
126 std::string 107 std::string
127 QPDFFormFieldObjectHelper::getInheritableFieldValueAsString(std::string const& name) 108 QPDFFormFieldObjectHelper::getInheritableFieldValueAsString(std::string const& name)
128 { 109 {
129 - return m->getInheritableFieldValueAsString(name); 110 + return m->inheritable_string(name);
130 } 111 }
131 112
132 std::string 113 std::string
133 -FormField::getInheritableFieldValueAsString(std::string const& name) 114 +FormField::inheritable_string(std::string const& name) const
134 { 115 {
135 - auto fv = getInheritableFieldValue(name);  
136 - if (fv.isString()) {  
137 - return fv.getUTF8Value(); 116 + if (auto fv = inheritable_value<String>(name)) {
  117 + return fv.utf8_value();
138 } 118 }
139 return {}; 119 return {};
140 } 120 }
@@ -142,13 +122,7 @@ FormField::getInheritableFieldValueAsString(std::string const&amp; name) @@ -142,13 +122,7 @@ FormField::getInheritableFieldValueAsString(std::string const&amp; name)
142 std::string 122 std::string
143 QPDFFormFieldObjectHelper::getInheritableFieldValueAsName(std::string const& name) 123 QPDFFormFieldObjectHelper::getInheritableFieldValueAsName(std::string const& name)
144 { 124 {
145 - return m->getInheritableFieldValueAsName(name);  
146 -}  
147 -  
148 -std::string  
149 -FormField::getInheritableFieldValueAsName(std::string const& name)  
150 -{  
151 - if (Name fv = getInheritableFieldValue(name)) { 125 + if (auto fv = m->inheritable_value<Name>(name)) {
152 return fv; 126 return fv;
153 } 127 }
154 return {}; 128 return {};
@@ -157,35 +131,33 @@ FormField::getInheritableFieldValueAsName(std::string const&amp; name) @@ -157,35 +131,33 @@ FormField::getInheritableFieldValueAsName(std::string const&amp; name)
157 std::string 131 std::string
158 QPDFFormFieldObjectHelper::getFieldType() 132 QPDFFormFieldObjectHelper::getFieldType()
159 { 133 {
160 - return m->getFieldType();  
161 -}  
162 -  
163 -std::string  
164 -FormField::getFieldType()  
165 -{  
166 - return getInheritableFieldValueAsName("/FT"); 134 + if (auto ft = m->FT()) {
  135 + return ft;
  136 + }
  137 + return {};
167 } 138 }
168 139
169 std::string 140 std::string
170 QPDFFormFieldObjectHelper::getFullyQualifiedName() 141 QPDFFormFieldObjectHelper::getFullyQualifiedName()
171 { 142 {
172 - return m->getFullyQualifiedName(); 143 + return m->fully_qualified_name();
173 } 144 }
174 145
175 std::string 146 std::string
176 -FormField::getFullyQualifiedName() 147 +FormField::fully_qualified_name() const
177 { 148 {
178 std::string result; 149 std::string result;
179 - QPDFObjectHandle node = oh(); 150 + auto node = *this;
180 QPDFObjGen::set seen; 151 QPDFObjGen::set seen;
181 - while (!node.null() && seen.add(node)) {  
182 - if (node.getKey("/T").isString()) { 152 + size_t depth = 0; // Don't bother with loop detection until depth becomes suspicious
  153 + while (node && (++depth < 10 || seen.add(node))) {
  154 + if (auto T = node.T()) {
183 if (!result.empty()) { 155 if (!result.empty()) {
184 - result = "." + result; 156 + result.insert(0, 1, '.');
185 } 157 }
186 - result = node.getKey("/T").getUTF8Value() + result; 158 + result.insert(0, T.utf8_value());
187 } 159 }
188 - node = node.getKey("/Parent"); 160 + node = node.Parent();
189 } 161 }
190 return result; 162 return result;
191 } 163 }
@@ -193,131 +165,110 @@ FormField::getFullyQualifiedName() @@ -193,131 +165,110 @@ FormField::getFullyQualifiedName()
193 std::string 165 std::string
194 QPDFFormFieldObjectHelper::getPartialName() 166 QPDFFormFieldObjectHelper::getPartialName()
195 { 167 {
196 - return m->getPartialName(); 168 + return m->partial_name();
197 } 169 }
198 170
199 std::string 171 std::string
200 -FormField::getPartialName() 172 +FormField::partial_name() const
201 { 173 {
202 - std::string result;  
203 - if (oh().getKey("/T").isString()) {  
204 - result = oh().getKey("/T").getUTF8Value(); 174 + if (auto pn = T()) {
  175 + return pn.utf8_value();
205 } 176 }
206 - return result; 177 + return {};
207 } 178 }
208 179
209 std::string 180 std::string
210 QPDFFormFieldObjectHelper::getAlternativeName() 181 QPDFFormFieldObjectHelper::getAlternativeName()
211 { 182 {
212 - return m->getAlternativeName(); 183 + return m->alternative_name();
213 } 184 }
214 185
215 std::string 186 std::string
216 -FormField::getAlternativeName() 187 +FormField::alternative_name() const
217 { 188 {
218 - if (oh().getKey("/TU").isString()) {  
219 - QTC::TC("qpdf", "QPDFFormFieldObjectHelper TU present");  
220 - return oh().getKey("/TU").getUTF8Value(); 189 + if (auto an = TU()) {
  190 + return an.utf8_value();
221 } 191 }
222 - QTC::TC("qpdf", "QPDFFormFieldObjectHelper TU absent");  
223 - return getFullyQualifiedName(); 192 + return fully_qualified_name();
224 } 193 }
225 194
226 std::string 195 std::string
227 QPDFFormFieldObjectHelper::getMappingName() 196 QPDFFormFieldObjectHelper::getMappingName()
228 { 197 {
229 - return m->getMappingName(); 198 + return m->mapping_name();
230 } 199 }
231 200
232 std::string 201 std::string
233 -FormField::getMappingName() 202 +FormField::mapping_name() const
234 { 203 {
235 - if (oh().getKey("/TM").isString()) {  
236 - QTC::TC("qpdf", "QPDFFormFieldObjectHelper TM present");  
237 - return oh().getKey("/TM").getUTF8Value(); 204 + if (auto mn = TM()) {
  205 + return mn.utf8_value();
238 } 206 }
239 - QTC::TC("qpdf", "QPDFFormFieldObjectHelper TM absent");  
240 - return getAlternativeName(); 207 + return alternative_name();
241 } 208 }
242 209
243 QPDFObjectHandle 210 QPDFObjectHandle
244 QPDFFormFieldObjectHelper::getValue() 211 QPDFFormFieldObjectHelper::getValue()
245 { 212 {
246 - return m->getValue();  
247 -}  
248 -  
249 -QPDFObjectHandle  
250 -FormField::getValue()  
251 -{  
252 - return getInheritableFieldValue("/V"); 213 + return Null::if_null(m->V<QPDFObjectHandle>());
253 } 214 }
254 215
255 std::string 216 std::string
256 QPDFFormFieldObjectHelper::getValueAsString() 217 QPDFFormFieldObjectHelper::getValueAsString()
257 { 218 {
258 - return getInheritableFieldValueAsString("/V"); 219 + return m->value();
259 } 220 }
260 221
261 std::string 222 std::string
262 -FormField::getValueAsString() 223 +FormField::value() const
263 { 224 {
264 - return getInheritableFieldValueAsString("/V"); 225 + return inheritable_string("/V");
265 } 226 }
266 227
267 QPDFObjectHandle 228 QPDFObjectHandle
268 QPDFFormFieldObjectHelper::getDefaultValue() 229 QPDFFormFieldObjectHelper::getDefaultValue()
269 { 230 {
270 - return m->getDefaultValue();  
271 -}  
272 -  
273 -QPDFObjectHandle  
274 -FormField::getDefaultValue()  
275 -{  
276 - return getInheritableFieldValue("/DV"); 231 + return Null::if_null(m->DV());
277 } 232 }
278 233
279 std::string 234 std::string
280 QPDFFormFieldObjectHelper::getDefaultValueAsString() 235 QPDFFormFieldObjectHelper::getDefaultValueAsString()
281 { 236 {
282 - return m->getDefaultValueAsString(); 237 + return m->default_value();
283 } 238 }
284 239
285 std::string 240 std::string
286 -FormField::getDefaultValueAsString() 241 +FormField::default_value() const
287 { 242 {
288 - return getInheritableFieldValueAsString("/DV"); 243 + return inheritable_string("/DV");
289 } 244 }
290 245
291 QPDFObjectHandle 246 QPDFObjectHandle
292 QPDFFormFieldObjectHelper::getDefaultResources() 247 QPDFFormFieldObjectHelper::getDefaultResources()
293 { 248 {
294 - return m->getDefaultResources(); 249 + return Null::if_null(m->getDefaultResources());
295 } 250 }
296 251
297 QPDFObjectHandle 252 QPDFObjectHandle
298 FormField::getDefaultResources() 253 FormField::getDefaultResources()
299 { 254 {
300 - return getFieldFromAcroForm("/DR"); 255 + return from_AcroForm("/DR");
301 } 256 }
302 257
303 std::string 258 std::string
304 QPDFFormFieldObjectHelper::getDefaultAppearance() 259 QPDFFormFieldObjectHelper::getDefaultAppearance()
305 { 260 {
306 - return m->getDefaultAppearance(); 261 + return m->default_appearance();
307 } 262 }
308 263
309 std::string 264 std::string
310 -FormField::getDefaultAppearance() 265 +FormField::default_appearance() const
311 { 266 {
312 - auto value = getInheritableFieldValue("/DA");  
313 - bool looked_in_acroform = false;  
314 - if (!value.isString()) {  
315 - value = getFieldFromAcroForm("/DA");  
316 - looked_in_acroform = true; 267 + if (auto DA = inheritable_value<String>("/DA")) {
  268 + return DA.utf8_value();
317 } 269 }
318 - if (value.isString()) {  
319 - QTC::TC("qpdf", "QPDFFormFieldObjectHelper DA present", looked_in_acroform ? 0 : 1);  
320 - return value.getUTF8Value(); 270 + if (String DA = from_AcroForm("/DA")) {
  271 + return DA.utf8_value();
321 } 272 }
322 return {}; 273 return {};
323 } 274 }
@@ -331,10 +282,10 @@ QPDFFormFieldObjectHelper::getQuadding() @@ -331,10 +282,10 @@ QPDFFormFieldObjectHelper::getQuadding()
331 int 282 int
332 FormField::getQuadding() 283 FormField::getQuadding()
333 { 284 {
334 - QPDFObjectHandle fv = getInheritableFieldValue("/Q"); 285 + auto fv = inheritable_value<QPDFObjectHandle>("/Q");
335 bool looked_in_acroform = false; 286 bool looked_in_acroform = false;
336 if (!fv.isInteger()) { 287 if (!fv.isInteger()) {
337 - fv = getFieldFromAcroForm("/Q"); 288 + fv = from_AcroForm("/Q");
338 looked_in_acroform = true; 289 looked_in_acroform = true;
339 } 290 }
340 if (fv.isInteger()) { 291 if (fv.isInteger()) {
@@ -353,7 +304,7 @@ QPDFFormFieldObjectHelper::getFlags() @@ -353,7 +304,7 @@ QPDFFormFieldObjectHelper::getFlags()
353 int 304 int
354 FormField::getFlags() 305 FormField::getFlags()
355 { 306 {
356 - QPDFObjectHandle f = getInheritableFieldValue("/Ff"); 307 + auto f = inheritable_value<QPDFObjectHandle>("/Ff");
357 return f.isInteger() ? f.getIntValueAsInt() : 0; 308 return f.isInteger() ? f.getIntValueAsInt() : 0;
358 } 309 }
359 310
@@ -366,7 +317,7 @@ QPDFFormFieldObjectHelper::isText() @@ -366,7 +317,7 @@ QPDFFormFieldObjectHelper::isText()
366 bool 317 bool
367 FormField::isText() 318 FormField::isText()
368 { 319 {
369 - return getFieldType() == "/Tx"; 320 + return FT() == "/Tx";
370 } 321 }
371 322
372 bool 323 bool
@@ -378,7 +329,7 @@ QPDFFormFieldObjectHelper::isCheckbox() @@ -378,7 +329,7 @@ QPDFFormFieldObjectHelper::isCheckbox()
378 bool 329 bool
379 FormField::isCheckbox() 330 FormField::isCheckbox()
380 { 331 {
381 - return getFieldType() == "/Btn" && (getFlags() & (ff_btn_radio | ff_btn_pushbutton)) == 0; 332 + return FT() == "/Btn" && (getFlags() & (ff_btn_radio | ff_btn_pushbutton)) == 0;
382 } 333 }
383 334
384 bool 335 bool
@@ -390,7 +341,7 @@ QPDFFormFieldObjectHelper::isChecked() @@ -390,7 +341,7 @@ QPDFFormFieldObjectHelper::isChecked()
390 bool 341 bool
391 FormField::isChecked() 342 FormField::isChecked()
392 { 343 {
393 - return isCheckbox() && Name(getValue()) != "/Off"; 344 + return isCheckbox() && V<Name>() != "/Off";
394 } 345 }
395 346
396 bool 347 bool
@@ -402,7 +353,7 @@ QPDFFormFieldObjectHelper::isRadioButton() @@ -402,7 +353,7 @@ QPDFFormFieldObjectHelper::isRadioButton()
402 bool 353 bool
403 FormField::isRadioButton() 354 FormField::isRadioButton()
404 { 355 {
405 - return getFieldType() == "/Btn" && (getFlags() & ff_btn_radio) == ff_btn_radio; 356 + return FT() == "/Btn" && (getFlags() & ff_btn_radio) == ff_btn_radio;
406 } 357 }
407 358
408 bool 359 bool
@@ -414,7 +365,7 @@ QPDFFormFieldObjectHelper::isPushbutton() @@ -414,7 +365,7 @@ QPDFFormFieldObjectHelper::isPushbutton()
414 bool 365 bool
415 FormField::isPushbutton() 366 FormField::isPushbutton()
416 { 367 {
417 - return getFieldType() == "/Btn" && (getFlags() & ff_btn_pushbutton) == ff_btn_pushbutton; 368 + return FT() == "/Btn" && (getFlags() & ff_btn_pushbutton) == ff_btn_pushbutton;
418 } 369 }
419 370
420 bool 371 bool
@@ -426,7 +377,7 @@ QPDFFormFieldObjectHelper::isChoice() @@ -426,7 +377,7 @@ QPDFFormFieldObjectHelper::isChoice()
426 bool 377 bool
427 FormField::isChoice() 378 FormField::isChoice()
428 { 379 {
429 - return getFieldType() == "/Ch"; 380 + return FT() == "/Ch";
430 } 381 }
431 382
432 std::vector<std::string> 383 std::vector<std::string>
@@ -442,7 +393,7 @@ FormField::getChoices() @@ -442,7 +393,7 @@ FormField::getChoices()
442 return {}; 393 return {};
443 } 394 }
444 std::vector<std::string> result; 395 std::vector<std::string> result;
445 - for (auto const& item: getInheritableFieldValue("/Opt").as_array()) { 396 + for (auto const& item: inheritable_value<Array>("/Opt")) {
446 if (item.isString()) { 397 if (item.isString()) {
447 result.emplace_back(item.getUTF8Value()); 398 result.emplace_back(item.getUTF8Value());
448 } else if (item.size() == 2) { 399 } else if (item.size() == 2) {
@@ -489,7 +440,7 @@ void @@ -489,7 +440,7 @@ void
489 FormField::setV(QPDFObjectHandle value, bool need_appearances) 440 FormField::setV(QPDFObjectHandle value, bool need_appearances)
490 { 441 {
491 Name name = value; 442 Name name = value;
492 - if (getFieldType() == "/Btn") { 443 + if (FT() == "/Btn") {
493 if (isCheckbox()) { 444 if (isCheckbox()) {
494 if (!name) { 445 if (!name) {
495 warn("ignoring attempt to set a checkbox field to a value whose type is not name"); 446 warn("ignoring attempt to set a checkbox field to a value whose type is not name");
@@ -652,10 +603,9 @@ QPDFFormFieldObjectHelper::generateAppearance(QPDFAnnotationObjectHelper&amp; aoh) @@ -652,10 +603,9 @@ QPDFFormFieldObjectHelper::generateAppearance(QPDFAnnotationObjectHelper&amp; aoh)
652 void 603 void
653 FormField::generateAppearance(QPDFAnnotationObjectHelper& aoh) 604 FormField::generateAppearance(QPDFAnnotationObjectHelper& aoh)
654 { 605 {
655 - std::string ft = getFieldType();  
656 // 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
657 // need them -- see code in QPDFAcroFormDocumentHelper::generateAppearancesIfNeeded. 607 // need them -- see code in QPDFAcroFormDocumentHelper::generateAppearancesIfNeeded.
658 - if ((ft == "/Tx") || (ft == "/Ch")) { 608 + if (FT() == "/Tx" || FT() == "/Ch") {
659 generateTextAppearance(aoh); 609 generateTextAppearance(aoh);
660 } 610 }
661 } 611 }
@@ -973,8 +923,8 @@ FormField::generateTextAppearance(QPDFAnnotationObjectHelper&amp; aoh) @@ -973,8 +923,8 @@ FormField::generateTextAppearance(QPDFAnnotationObjectHelper&amp; aoh)
973 return; 923 return;
974 } 924 }
975 QPDFObjectHandle::Rectangle bbox = bbox_obj.getArrayAsRectangle(); 925 QPDFObjectHandle::Rectangle bbox = bbox_obj.getArrayAsRectangle();
976 - std::string DA = getDefaultAppearance();  
977 - std::string V = getValueAsString(); 926 + std::string DA = default_appearance();
  927 + std::string V = value();
978 std::vector<std::string> opt; 928 std::vector<std::string> opt;
979 if (isChoice() && (getFlags() & ff_ch_combo) == 0) { 929 if (isChoice() && (getFlags() & ff_ch_combo) == 0) {
980 opt = getChoices(); 930 opt = getChoices();
libqpdf/qpdf/FormField.hh
@@ -32,57 +32,264 @@ namespace qpdf::impl @@ -32,57 +32,264 @@ namespace qpdf::impl
32 { 32 {
33 } 33 }
34 34
35 - // Return the field's parent. A form field object helper whose underlying object is null is  
36 - // returned if there is no parent. This condition may be tested by calling isNull().  
37 - FormField getParent();  
38 -  
39 - // Return the top-level field for this field. Typically this will be the field itself or its  
40 - // parent. If is_different is provided, it is set to true if the top-level field is  
41 - // different from the field itself; otherwise it is set to false.  
42 - FormField getTopLevelField(bool* is_different = nullptr);  
43 -  
44 - // Get a field value, possibly inheriting the value from an ancestor node.  
45 - QPDFObjectHandle getInheritableFieldValue(std::string const& name);  
46 -  
47 - // Get an inherited field value as a string. If it is not a string, silently return the  
48 - // empty string.  
49 - std::string getInheritableFieldValueAsString(std::string const& name);  
50 -  
51 - // Get an inherited field value of type name as a string representing the name. If it is not  
52 - // a name, silently return the empty string.  
53 - std::string getInheritableFieldValueAsName(std::string const& name);  
54 -  
55 - // Returns the value of /FT if present, otherwise returns the empty string.  
56 - std::string getFieldType(); 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 + }
57 48
58 - std::string getFullyQualifiedName(); 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 + }
59 99
60 - std::string getPartialName(); 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 + }
61 118
62 - // Return the alternative field name (/TU), which is the field name intended to be presented  
63 - // to users. If not present, fall back to the fully qualified name.  
64 - std::string getAlternativeName(); 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 + }
65 128
66 - // Return the mapping field name (/TM). If not present, fall back to the alternative name,  
67 - // then to the partial name.  
68 - std::string getMappingName(); 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 + }
69 138
70 - QPDFObjectHandle getValue(); 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 + }
71 148
72 - // Return the field's value as a string. If this is called with a field whose value is not a  
73 - std::string getValueAsString(); 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 + }
74 212
75 - QPDFObjectHandle getDefaultValue(); 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 + }
76 234
77 - // Return the field's default value as a string. If this is called with a field whose value  
78 - // is not a string, the empty string will be silently returned.  
79 - std::string getDefaultValueAsString(); 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 + }
80 269
81 - // Return the default appearance string, taking inheritance from the field tree into  
82 - // account. Returns the empty string if the default appearance string is not available  
83 - // (because it's erroneously absent or because this is not a variable text field). If not  
84 - // found in the field hierarchy, look in /AcroForm.  
85 - std::string getDefaultAppearance(); 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;
86 293
87 // Return the default resource dictionary for the field. This comes not from the field but 294 // Return the default resource dictionary for the field. This comes not from the field but
88 // from the document-level /AcroForm dictionary. While several PDF generates put a /DR key 295 // from the document-level /AcroForm dictionary. While several PDF generates put a /DR key
@@ -154,12 +361,29 @@ namespace qpdf::impl @@ -154,12 +361,29 @@ namespace qpdf::impl
154 void generateAppearance(QPDFAnnotationObjectHelper&); 361 void generateAppearance(QPDFAnnotationObjectHelper&);
155 362
156 private: 363 private:
157 - QPDFObjectHandle getFieldFromAcroForm(std::string const& name); 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 +
158 void setRadioButtonValue(QPDFObjectHandle name); 380 void setRadioButtonValue(QPDFObjectHandle name);
159 void setCheckBoxValue(bool value); 381 void setCheckBoxValue(bool value);
160 void generateTextAppearance(QPDFAnnotationObjectHelper&); 382 void generateTextAppearance(QPDFAnnotationObjectHelper&);
161 QPDFObjectHandle 383 QPDFObjectHandle
162 getFontFromResource(QPDFObjectHandle resources, std::string const& font_name); 384 getFontFromResource(QPDFObjectHandle resources, std::string const& font_name);
  385 +
  386 + static const QPDFObjectHandle null_oh;
163 }; 387 };
164 } // namespace qpdf::impl 388 } // namespace qpdf::impl
165 389
libtests/objects.cc
@@ -90,7 +90,6 @@ test_0(QPDF&amp; pdf, char const* arg2) @@ -90,7 +90,6 @@ test_0(QPDF&amp; pdf, char const* arg2)
90 assert_compare_numbers(UINT_MAX, t.getKey("/Q3").getUIntValueAsUInt()); 90 assert_compare_numbers(UINT_MAX, t.getKey("/Q3").getUIntValueAsUInt());
91 } 91 }
92 92
93 -  
94 static void 93 static void
95 test_1(QPDF& pdf, char const* arg2) 94 test_1(QPDF& pdf, char const* arg2)
96 { 95 {
@@ -121,7 +120,7 @@ test_1(QPDF&amp; pdf, char const* arg2) @@ -121,7 +120,7 @@ test_1(QPDF&amp; pdf, char const* arg2)
121 120
122 bool thrown = false; 121 bool thrown = false;
123 try { 122 try {
124 - i.at("/A"); 123 + i.at("/A");
125 } catch (std::runtime_error const&) { 124 } catch (std::runtime_error const&) {
126 thrown = true; 125 thrown = true;
127 } 126 }
@@ -161,7 +160,9 @@ runtest(int n, char const* filename1, char const* arg2) @@ -161,7 +160,9 @@ runtest(int n, char const* filename1, char const* arg2)
161 // the test suite to see how the test is invoked to find the file 160 // the test suite to see how the test is invoked to find the file
162 // that the test is supposed to operate on. 161 // that the test is supposed to operate on.
163 162
164 - std::set<int> ignore_filename = {1,}; 163 + std::set<int> ignore_filename = {
  164 + 1,
  165 + };
165 166
166 QPDF pdf; 167 QPDF pdf;
167 std::shared_ptr<char> file_buf; 168 std::shared_ptr<char> file_buf;
@@ -175,7 +176,8 @@ runtest(int n, char const* filename1, char const* arg2) @@ -175,7 +176,8 @@ runtest(int n, char const* filename1, char const* arg2)
175 } 176 }
176 177
177 std::map<int, void (*)(QPDF&, char const*)> test_functions = { 178 std::map<int, void (*)(QPDF&, char const*)> test_functions = {
178 - {0, test_0}, {1, test_1}, 179 + {0, test_0},
  180 + {1, test_1},
179 }; 181 };
180 182
181 auto fn = test_functions.find(n); 183 auto fn = test_functions.find(n);
manual/release-notes.rst
@@ -23,6 +23,12 @@ more detail. @@ -23,6 +23,12 @@ more detail.
23 not work on some older Linux distributions. If you need support 23 not work on some older Linux distributions. If you need support
24 for an older distribution, please use version 12.2.0 or below. 24 for an older distribution, please use version 12.2.0 or below.
25 25
  26 + - Bug fixes
  27 +
  28 + - Set `is_different` flag in `QPDFFormFieldObjectHelper::getTopLevelField` to
  29 + false if the field is a top-level field. Previously the flag was only set
  30 + if the field is a top-level field.
  31 +
26 - Library Enhancements 32 - Library Enhancements
27 33
28 - Add ``QPDFNameTreeObjectHelper`` and ``QPDFNumberTreeObjectHelper`` 34 - Add ``QPDFNameTreeObjectHelper`` and ``QPDFNumberTreeObjectHelper``
qpdf/qpdf.testcov
@@ -174,12 +174,7 @@ QPDFObjectHandle dictionary empty map for asMap 0 @@ -174,12 +174,7 @@ QPDFObjectHandle dictionary empty map for asMap 0
174 QPDFObjectHandle numeric non-numeric 0 174 QPDFObjectHandle numeric non-numeric 0
175 QPDFObjectHandle erase array bounds 0 175 QPDFObjectHandle erase array bounds 0
176 qpdf-c called qpdf_check_pdf 0 176 qpdf-c called qpdf_check_pdf 0
177 -QPDFFormFieldObjectHelper TU present 0  
178 -QPDFFormFieldObjectHelper TM present 0  
179 -QPDFFormFieldObjectHelper TU absent 0  
180 -QPDFFormFieldObjectHelper TM absent 0  
181 QPDFFormFieldObjectHelper Q present 1 177 QPDFFormFieldObjectHelper Q present 1
182 -QPDFFormFieldObjectHelper DA present 1  
183 QPDFAcroFormDocumentHelper field found 1 178 QPDFAcroFormDocumentHelper field found 1
184 QPDFAcroFormDocumentHelper annotation found 1 179 QPDFAcroFormDocumentHelper annotation found 1
185 QPDFJob automatically set keep files open 1 180 QPDFJob automatically set keep files open 1