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 18  
19 19 using FormField = qpdf::impl::FormField;
20 20  
  21 +const QPDFObjectHandle FormField::null_oh;
  22 +
21 23 class QPDFFormFieldObjectHelper::Members: public FormField
22 24 {
23 25 public:
... ... @@ -48,93 +50,71 @@ QPDFFormFieldObjectHelper::isNull()
48 50 QPDFFormFieldObjectHelper
49 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 56 QPDFFormFieldObjectHelper
61 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 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 68 if (!obj) {
70 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 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 76 if (is_different) {
77 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 83 QPDFObjectHandle
100 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 107 std::string
127 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 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 119 return {};
140 120 }
... ... @@ -142,13 +122,7 @@ FormField::getInheritableFieldValueAsString(std::string const&amp; name)
142 122 std::string
143 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 126 return fv;
153 127 }
154 128 return {};
... ... @@ -157,35 +131,33 @@ FormField::getInheritableFieldValueAsName(std::string const&amp; name)
157 131 std::string
158 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 140 std::string
170 141 QPDFFormFieldObjectHelper::getFullyQualifiedName()
171 142 {
172   - return m->getFullyQualifiedName();
  143 + return m->fully_qualified_name();
173 144 }
174 145  
175 146 std::string
176   -FormField::getFullyQualifiedName()
  147 +FormField::fully_qualified_name() const
177 148 {
178 149 std::string result;
179   - QPDFObjectHandle node = oh();
  150 + auto node = *this;
180 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 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 162 return result;
191 163 }
... ... @@ -193,131 +165,110 @@ FormField::getFullyQualifiedName()
193 165 std::string
194 166 QPDFFormFieldObjectHelper::getPartialName()
195 167 {
196   - return m->getPartialName();
  168 + return m->partial_name();
197 169 }
198 170  
199 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 180 std::string
210 181 QPDFFormFieldObjectHelper::getAlternativeName()
211 182 {
212   - return m->getAlternativeName();
  183 + return m->alternative_name();
213 184 }
214 185  
215 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 195 std::string
227 196 QPDFFormFieldObjectHelper::getMappingName()
228 197 {
229   - return m->getMappingName();
  198 + return m->mapping_name();
230 199 }
231 200  
232 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 210 QPDFObjectHandle
244 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 216 std::string
256 217 QPDFFormFieldObjectHelper::getValueAsString()
257 218 {
258   - return getInheritableFieldValueAsString("/V");
  219 + return m->value();
259 220 }
260 221  
261 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 228 QPDFObjectHandle
268 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 234 std::string
280 235 QPDFFormFieldObjectHelper::getDefaultValueAsString()
281 236 {
282   - return m->getDefaultValueAsString();
  237 + return m->default_value();
283 238 }
284 239  
285 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 246 QPDFObjectHandle
292 247 QPDFFormFieldObjectHelper::getDefaultResources()
293 248 {
294   - return m->getDefaultResources();
  249 + return Null::if_null(m->getDefaultResources());
295 250 }
296 251  
297 252 QPDFObjectHandle
298 253 FormField::getDefaultResources()
299 254 {
300   - return getFieldFromAcroForm("/DR");
  255 + return from_AcroForm("/DR");
301 256 }
302 257  
303 258 std::string
304 259 QPDFFormFieldObjectHelper::getDefaultAppearance()
305 260 {
306   - return m->getDefaultAppearance();
  261 + return m->default_appearance();
307 262 }
308 263  
309 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 273 return {};
323 274 }
... ... @@ -331,10 +282,10 @@ QPDFFormFieldObjectHelper::getQuadding()
331 282 int
332 283 FormField::getQuadding()
333 284 {
334   - QPDFObjectHandle fv = getInheritableFieldValue("/Q");
  285 + auto fv = inheritable_value<QPDFObjectHandle>("/Q");
335 286 bool looked_in_acroform = false;
336 287 if (!fv.isInteger()) {
337   - fv = getFieldFromAcroForm("/Q");
  288 + fv = from_AcroForm("/Q");
338 289 looked_in_acroform = true;
339 290 }
340 291 if (fv.isInteger()) {
... ... @@ -353,7 +304,7 @@ QPDFFormFieldObjectHelper::getFlags()
353 304 int
354 305 FormField::getFlags()
355 306 {
356   - QPDFObjectHandle f = getInheritableFieldValue("/Ff");
  307 + auto f = inheritable_value<QPDFObjectHandle>("/Ff");
357 308 return f.isInteger() ? f.getIntValueAsInt() : 0;
358 309 }
359 310  
... ... @@ -366,7 +317,7 @@ QPDFFormFieldObjectHelper::isText()
366 317 bool
367 318 FormField::isText()
368 319 {
369   - return getFieldType() == "/Tx";
  320 + return FT() == "/Tx";
370 321 }
371 322  
372 323 bool
... ... @@ -378,7 +329,7 @@ QPDFFormFieldObjectHelper::isCheckbox()
378 329 bool
379 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 335 bool
... ... @@ -390,7 +341,7 @@ QPDFFormFieldObjectHelper::isChecked()
390 341 bool
391 342 FormField::isChecked()
392 343 {
393   - return isCheckbox() && Name(getValue()) != "/Off";
  344 + return isCheckbox() && V<Name>() != "/Off";
394 345 }
395 346  
396 347 bool
... ... @@ -402,7 +353,7 @@ QPDFFormFieldObjectHelper::isRadioButton()
402 353 bool
403 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 359 bool
... ... @@ -414,7 +365,7 @@ QPDFFormFieldObjectHelper::isPushbutton()
414 365 bool
415 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 371 bool
... ... @@ -426,7 +377,7 @@ QPDFFormFieldObjectHelper::isChoice()
426 377 bool
427 378 FormField::isChoice()
428 379 {
429   - return getFieldType() == "/Ch";
  380 + return FT() == "/Ch";
430 381 }
431 382  
432 383 std::vector<std::string>
... ... @@ -442,7 +393,7 @@ FormField::getChoices()
442 393 return {};
443 394 }
444 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 397 if (item.isString()) {
447 398 result.emplace_back(item.getUTF8Value());
448 399 } else if (item.size() == 2) {
... ... @@ -489,7 +440,7 @@ void
489 440 FormField::setV(QPDFObjectHandle value, bool need_appearances)
490 441 {
491 442 Name name = value;
492   - if (getFieldType() == "/Btn") {
  443 + if (FT() == "/Btn") {
493 444 if (isCheckbox()) {
494 445 if (!name) {
495 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 603 void
653 604 FormField::generateAppearance(QPDFAnnotationObjectHelper& aoh)
654 605 {
655   - std::string ft = getFieldType();
656 606 // Ignore field types we don't know how to generate appearances for. Button fields don't really
657 607 // need them -- see code in QPDFAcroFormDocumentHelper::generateAppearancesIfNeeded.
658   - if ((ft == "/Tx") || (ft == "/Ch")) {
  608 + if (FT() == "/Tx" || FT() == "/Ch") {
659 609 generateTextAppearance(aoh);
660 610 }
661 611 }
... ... @@ -973,8 +923,8 @@ FormField::generateTextAppearance(QPDFAnnotationObjectHelper&amp; aoh)
973 923 return;
974 924 }
975 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 928 std::vector<std::string> opt;
979 929 if (isChoice() && (getFlags() & ff_ch_combo) == 0) {
980 930 opt = getChoices();
... ...
libqpdf/qpdf/FormField.hh
... ... @@ -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 294 // Return the default resource dictionary for the field. This comes not from the field but
88 295 // from the document-level /AcroForm dictionary. While several PDF generates put a /DR key
... ... @@ -154,12 +361,29 @@ namespace qpdf::impl
154 361 void generateAppearance(QPDFAnnotationObjectHelper&);
155 362  
156 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 380 void setRadioButtonValue(QPDFObjectHandle name);
159 381 void setCheckBoxValue(bool value);
160 382 void generateTextAppearance(QPDFAnnotationObjectHelper&);
161 383 QPDFObjectHandle
162 384 getFontFromResource(QPDFObjectHandle resources, std::string const& font_name);
  385 +
  386 + static const QPDFObjectHandle null_oh;
163 387 };
164 388 } // namespace qpdf::impl
165 389  
... ...
libtests/objects.cc
... ... @@ -90,7 +90,6 @@ test_0(QPDF&amp; pdf, char const* arg2)
90 90 assert_compare_numbers(UINT_MAX, t.getKey("/Q3").getUIntValueAsUInt());
91 91 }
92 92  
93   -
94 93 static void
95 94 test_1(QPDF& pdf, char const* arg2)
96 95 {
... ... @@ -121,7 +120,7 @@ test_1(QPDF&amp; pdf, char const* arg2)
121 120  
122 121 bool thrown = false;
123 122 try {
124   - i.at("/A");
  123 + i.at("/A");
125 124 } catch (std::runtime_error const&) {
126 125 thrown = true;
127 126 }
... ... @@ -161,7 +160,9 @@ runtest(int n, char const* filename1, char const* arg2)
161 160 // the test suite to see how the test is invoked to find the file
162 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 167 QPDF pdf;
167 168 std::shared_ptr<char> file_buf;
... ... @@ -175,7 +176,8 @@ runtest(int n, char const* filename1, char const* arg2)
175 176 }
176 177  
177 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 183 auto fn = test_functions.find(n);
... ...
manual/release-notes.rst
... ... @@ -23,6 +23,12 @@ more detail.
23 23 not work on some older Linux distributions. If you need support
24 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 32 - Library Enhancements
27 33  
28 34 - Add ``QPDFNameTreeObjectHelper`` and ``QPDFNumberTreeObjectHelper``
... ...
qpdf/qpdf.testcov
... ... @@ -174,12 +174,7 @@ QPDFObjectHandle dictionary empty map for asMap 0
174 174 QPDFObjectHandle numeric non-numeric 0
175 175 QPDFObjectHandle erase array bounds 0
176 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 177 QPDFFormFieldObjectHelper Q present 1
182   -QPDFFormFieldObjectHelper DA present 1
183 178 QPDFAcroFormDocumentHelper field found 1
184 179 QPDFAcroFormDocumentHelper annotation found 1
185 180 QPDFJob automatically set keep files open 1
... ...