AcroForm.hh 37.7 KB
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723
#ifndef ACRO_FORM_HH
#define ACRO_FORM_HH

#include <qpdf/QPDFObjectHandle_private.hh>
#include <qpdf/QPDFObjectHelper.hh>
#include <qpdf/QPDF_private.hh>

#include <vector>

class QPDFAnnotationObjectHelper;

namespace qpdf::impl
{
    /// @class  AcroForm
    /// @brief  Represents the interactive form dictionary and the interactive form tree within a
    ///         PDF document.
    /// @par
    ///         The AcroForm class deals with interactive forms defined in section 12.7 of the PDF
    ///         specification. This defines a tree structure consisting of an interactive form or
    ///         `/AcroForm` dictionary (section 12.7.3) at its root. The attributes of the
    ///         `/AcroForm` dictionary are defined in table 224 of the PDF 2.0 / table 220 of the
    ///         PDF 1.7 specification.
    /// @par
    ///         The nodes of the interactive forms tree are represented by the FormNode class.
    ///
    /// @since 12.3
    class AcroForm: public Doc::Common
    {
      public:
        AcroForm() = delete;
        AcroForm(AcroForm const&) = delete;
        AcroForm(AcroForm&&) = delete;
        AcroForm& operator=(AcroForm const&) = delete;
        AcroForm& operator=(AcroForm&&) = delete;
        ~AcroForm() = default;

        AcroForm(impl::Doc& doc) :
            Common(doc)
        {
            // We have to analyze up front. Otherwise, when we are adding annotations and fields, we
            // are in a temporarily unstable configuration where some widget annotations are not
            // reachable.
            validate();
        }

        // Re-validate the AcroForm structure. This is useful if you have modified the structure of
        // the AcroForm dictionary in a way that would invalidate the cache.
        //
        // If repair is true, the document will be repaired if possible if the validation encounters
        // errors.
        void validate(bool repair = true);

        // This class lazily creates an internal cache of the mapping among form fields,
        // annotations, and pages. Methods within this class preserve the validity of this cache.
        // However, if you modify pages' annotation dictionaries, the document's /AcroForm
        // dictionary, or any form fields manually in a way that alters the association between
        // forms, fields, annotations, and pages, it may cause this cache to become invalid. This
        // method marks the cache invalid and forces it to be regenerated the next time it is
        // needed.
        void invalidateCache();

        bool hasAcroForm();

        // Add a form field, initializing the document's AcroForm dictionary if needed, updating the
        // cache if necessary. Note that if you are adding fields that are copies of other fields,
        // this method may result in multiple fields existing with the same qualified name, which
        // can have unexpected side effects. In that case, you should use addAndRenameFormFields()
        // instead.
        void addFormField(QPDFFormFieldObjectHelper);

        // Add a collection of form fields making sure that their fully qualified names don't
        // conflict with already present form fields. Fields within the collection of new fields
        // that have the same name as each other will continue to do so.
        void addAndRenameFormFields(std::vector<QPDFObjectHandle> fields);

        // Remove fields from the fields array
        void removeFormFields(std::set<QPDFObjGen> const&);

        // Set the name of a field, updating internal records of field names. Name should be UTF-8
        // encoded.
        void setFormFieldName(QPDFFormFieldObjectHelper, std::string const& name);

        // Return a vector of all terminal fields in a document. Terminal fields are fields that
        // have no children that are also fields. Terminal fields may still have children that are
        // annotations. Intermediate nodes in the fields tree are not included in this list, but you
        // can still reach them through the getParent method of the field object helper.
        std::vector<QPDFFormFieldObjectHelper> getFormFields();

        // Return all the form fields that have the given fully-qualified name and also have an
        // explicit "/T" attribute. For this information to be accurate, any changes to field names
        // must be done through setFormFieldName() above.
        std::set<QPDFObjGen> getFieldsWithQualifiedName(std::string const& name);

        // Return the annotations associated with a terminal field. Note that in the case of a field
        // having a single annotation, the underlying object will typically be the same as the
        // underlying object for the field.
        std::vector<QPDFAnnotationObjectHelper> getAnnotationsForField(QPDFFormFieldObjectHelper);

        /// Retrieves a list of widget annotations for the specified page.
        ///
        /// A widget annotation represents the visual part of a form field in a PDF.
        /// This function filters annotations on the given page, returning only those
        /// annotations whose subtype is "/Widget".
        ///
        /// @param page A `QPDFPageObjectHelper` representing the page from which to
        ///             extract widget annotations.
        ///
        /// @return A vector of `QPDFAnnotationObjectHelper` objects corresponding to
        ///         the widget annotations found on the specified page.
        std::vector<QPDFAnnotationObjectHelper>
        getWidgetAnnotationsForPage(QPDFPageObjectHelper page);

        // Return top-level form fields for a page.
        std::vector<QPDFFormFieldObjectHelper> getFormFieldsForPage(QPDFPageObjectHelper);

        // Return the terminal field that is associated with this annotation. If the annotation
        // dictionary is merged with the field dictionary, the underlying object will be the same,
        // but this is not always the case. Note that if you call this method with an annotation
        // that is not a widget annotation, there will not be an associated field, and this method
        // will return a helper associated with a null object (isNull() == true).
        QPDFFormFieldObjectHelper getFieldForAnnotation(QPDFAnnotationObjectHelper);

        // Return the current value of /NeedAppearances. If /NeedAppearances is missing, return
        // false as that is how PDF viewers are supposed to interpret it.
        bool getNeedAppearances();

        // Indicate whether appearance streams must be regenerated. If you modify a field value, you
        // should call setNeedAppearances(true) unless you also generate an appearance stream for
        // the corresponding annotation at the same time. If you generate appearance streams for all
        // fields, you can call setNeedAppearances(false). If you use
        // QPDFFormFieldObjectHelper::setV, it will automatically call this method unless you tell
        // it not to.
        void setNeedAppearances(bool);

        // If /NeedAppearances is false, do nothing. Otherwise generate appearance streams for all
        // widget annotations that need them. See comments in QPDFFormFieldObjectHelper.hh for
        // generateAppearance for limitations. For checkbox and radio button fields, this code
        // ensures that appearance state is consistent with the field's value and uses any
        // pre-existing appearance streams.
        void generateAppearancesIfNeeded();

        // Disable Digital Signature Fields. Remove all digital signature fields from the document,
        // leaving any annotation showing the content of the field intact. This also calls
        // QPDF::removeSecurityRestrictions.
        void disableDigitalSignatures();

        // Note: this method works on all annotations, not just ones with associated fields. For
        // each annotation in old_annots, apply the given transformation matrix to create a new
        // annotation. New annotations are appended to new_annots. If the annotation is associated
        // with a form field, a new form field is created that points to the new annotation and is
        // appended to new_fields, and the old field is added to old_fields.
        //
        // old_annots may belong to a different QPDF object. In that case, you should pass in
        // from_qpdf, and copyForeignObject will be called automatically. If this is the case, for
        // efficiency, you may pass in a QPDFAcroFormDocumentHelper for the other file to avoid the
        // expensive process of creating one for each call to transformAnnotations. New fields and
        // annotations are not added to the document or pages. You have to do that yourself after
        // calling transformAnnotations. If this operation will leave orphaned fields behind, such
        // as if you are replacing the old annotations with the new ones on the same page and the
        // fields and annotations are not shared, you will also need to remove the old fields to
        // prevent them from hanging around unreferenced.
        void transformAnnotations(
            QPDFObjectHandle old_annots,
            std::vector<QPDFObjectHandle>& new_annots,
            std::vector<QPDFObjectHandle>& new_fields,
            std::set<QPDFObjGen>& old_fields,
            QPDFMatrix const& cm,
            QPDF* from_qpdf,
            AcroForm* from_afdh);

        // Copy form fields and annotations from one page to another, allowing the from page to be
        // in a different QPDF or in the same QPDF. This would typically be called after calling
        // addPage to add field/annotation awareness. When just copying the page by itself,
        // annotations end up being shared, and fields end up being omitted because there is no
        // reference to the field from the page. This method ensures that each separate copy of a
        // page has private annotations and that fields and annotations are properly updated to
        // resolve conflicts that may occur from common resource and field names across documents.
        // It is basically a wrapper around transformAnnotations that handles updating the receiving
        // page. If new_fields is non-null, any newly created fields are added to it.
        void fixCopiedAnnotations(
            QPDFObjectHandle to_page,
            QPDFObjectHandle from_page,
            AcroForm& from_afdh,
            std::set<QPDFObjGen>* new_fields = nullptr);

      private:
        struct FieldData
        {
            std::vector<QPDFAnnotationObjectHelper> annotations;
            std::string name;
        };

        /// Analyzes the AcroForm structure in the PDF document and updates the internal
        /// cache with the form fields and their corresponding widget annotations.
        ///
        /// The function performs the following steps:
        /// - Checks if the cache is valid. If it is, the function exits early.
        /// - Retrieves the `/AcroForm` dictionary from the PDF and checks if it contains
        ///   a `/Fields` key.
        /// - If `/Fields` exist and is an array, iterates through the fields and traverses
        ///   them to map annotations bidirectionally to form fields.
        /// - Logs a warning if the `/Fields` key is present but not an array, and initializes
        ///   it to an empty array.
        /// - Ensures that all widget annotations are processed, including any annotations
        ///   that might not be reachable from the `/AcroForm`. Treats such annotations as
        ///   their own fields.
        /// - Provides a workaround for PDF documents containing inconsistencies, such as
        ///   widget annotations on a page not being referenced in `/AcroForm`.
        ///
        /// This function allows precise navigation and manipulation of form fields and
        /// their related annotations, facilitating advanced PDF document processing.
        void analyze();

        /// Recursively traverses the structure of form fields and annotations in a PDF's /AcroForm.
        ///
        /// The method is designed to process form fields in a hierarchical /AcroForm structure.
        /// It captures field and annotation data, resolves parent-child relationships, detects
        /// loops, and avoids stack overflow from excessive recursion depth.
        ///
        /// @param field The current field or annotation to process.
        /// @param parent The parent field object. If the current field is a top-level field, parent
        ///               will be a null object.
        /// @param depth The current recursion depth to limit stack usage and avoid infinite loops.
        ///
        /// @return True if the field was processed successfully, false otherwise.
        ///
        /// - Recursion is limited to a depth of 100 to prevent stack overflow with maliciously
        ///   crafted files.
        /// - The function skips non-indirect and invalid objects (e.g., non-dictionaries or objects
        ///   with invalid parent references).
        /// - Detects and warns about loops in the /AcroForm hierarchy.
        /// - Differentiates between terminal fields, annotations, and composite fields based on
        ///   dictionary keys.
        /// - Tracks processed fields and annotations using internal maps to prevent reprocessing
        ///   and detect loops.
        /// - Updates name-to-field mappings for terminal fields with a valid fully qualified name.
        /// - Ensures the integrity of parent-child relationships within the field hierarchy.
        /// - Any invalid child objects are logged and skipped during traversal.
        bool traverseField(QPDFObjectHandle field, QPDFObjectHandle const& parent, int depth);

        /// Retrieves or creates the /AcroForm dictionary in the PDF document's root.
        ///
        /// - If the /AcroForm key exists in the document root and is a dictionary,
        ///   it is returned as is.
        /// - If the /AcroForm key does not exist or is not a dictionary, a new
        ///   dictionary is created, stored as the /AcroForm entry in the document root,
        ///   and then returned.
        ///
        /// @return A QPDFObjectHandle representing the /AcroForm dictionary.
        QPDFObjectHandle getOrCreateAcroForm();

        /// Adjusts inherited field properties for an AcroForm field object.
        ///
        /// This method ensures that the `/DA` (default appearance) and `/Q` (quadding) keys
        /// of the specified field object are overridden if necessary, based on the provided
        /// parameters. The overriding is performed only if the respective `override_da` or
        /// `override_q` flags are set to true, and when the original object's values differ from
        /// the provided defaults. No changes are made to fields that have explicit values for `/DA`
        /// or `/Q`.
        ///
        /// The function is primarily used for adjusting inherited form field properties in cases
        /// where the document structure or inherited values have changed (e.g., when working with
        /// fields in a PDF document).
        ///
        /// @param obj The `QPDFObjectHandle` instance representing the form field object to be
        ///            adjusted.
        /// @param override_da A boolean flag indicating whether to override the `/DA` key.
        /// @param from_default_da The default appearance string to apply if overriding the `/DA`
        ///                        key.
        /// @param override_q A boolean flag indicating whether to override the `/Q` key.
        /// @param from_default_q The default quadding value (alignment) to apply if overriding the
        ///                       `/Q` key.
        void adjustInheritedFields(
            QPDFObjectHandle obj,
            bool override_da,
            std::string const& from_default_da,
            bool override_q,
            int from_default_q);

        /// Adjusts the default appearances (/DA) of an AcroForm field object.
        ///
        /// This method ensures that form fields copied from another PDF document
        /// have their default appearances resource references updated to correctly
        /// point to the appropriate resources in the current document's resource
        /// dictionary (/DR). It resolves name conflicts between the dictionaries
        /// of the source and destination documents by using a mapping provided in
        /// `dr_map`.
        ///
        /// The method parses the /DA string, processes its resource references,
        /// and regenerates the /DA with updated references.
        ///
        /// @param obj The AcroForm field object whose /DA is being adjusted.
        /// @param dr_map A mapping between resource names in the source document's
        ///        resource dictionary and their corresponding names in the current
        ///        document's resource dictionary.
        void adjustDefaultAppearances(
            QPDFObjectHandle obj,
            std::map<std::string, std::map<std::string, std::string>> const& dr_map);

        /// Modifies the appearance stream of an AcroForm field to ensure its resources
        /// align with the resource dictionary and appearance settings. This method
        /// ensures proper resource handling to avoid any conflicts when regenerating
        /// the appearance stream.
        ///
        /// Adjustments include:
        /// - Creating a private resource dictionary for the stream if not already present.
        /// - Merging top-level resource keys into the stream's resource dictionary.
        /// - Resolving naming conflicts between existing and remapped resource keys.
        /// - Removing empty sub-dictionaries from the resource dictionary.
        /// - Attaching a token filter to rewrite resource references in the stream content.
        ///
        /// If conflicts between keys are encountered or the stream cannot be parsed successfully,
        /// appropriate warnings will be generated instead of halting execution.
        ///
        /// @param stream The QPDFObjectHandle representation of the PDF appearance stream to be
        ///               adjusted.
        /// @param dr_map A mapping of resource types and their corresponding name remappings
        ///               used for resolving resource conflicts and regenerating appearances.
        void adjustAppearanceStream(
            QPDFObjectHandle stream,
            std::map<std::string, std::map<std::string, std::string>> dr_map);

        std::map<QPDFObjGen, FieldData> fields_;
        std::map<QPDFObjGen, QPDFFormFieldObjectHelper> annotation_to_field_;
        std::map<std::string, std::set<QPDFObjGen>> name_to_fields_;
        std::set<QPDFObjGen> bad_fields_;
        bool cache_valid_{false};

    }; // class Acroform

    /// @class FormNode
    /// @brief Represents a node in the interactive forms tree of a PDF document.
    ///
    /// This class models nodes that may be either form field dictionaries or widget annotation
    /// dictionaries, as defined in the PDF specification (sections 12.7 and 12.5.6.19).
    ///
    /// For a detailed description of the attributes that this class can expose, refer to the
    /// corresponding tables in the PDF 2.0 (Table 226) or PDF 1.7 (Table 220) specifications.
    class FormNode: public qpdf::BaseDictionary
    {
      public:
        FormNode() = default;
        FormNode(FormNode const&) = default;
        FormNode& operator=(FormNode const&) = default;
        FormNode(FormNode&&) = default;
        FormNode& operator=(FormNode&&) = default;
        ~FormNode() = default;

        FormNode(QPDFObjectHandle const& oh) :
            BaseDictionary(oh)
        {
        }

        FormNode(QPDFObjectHandle&& oh) :
            BaseDictionary(std::move(oh))
        {
        }

        /// Retrieves the /Parent form field of the current field.
        ///
        /// This function accesses the parent field in the hierarchical structure of form fields, if
        /// it exists. The parent is determined based on the /Parent attribute in the field
        /// dictionary.
        ///
        /// @return A FormNode object representing the parent field. If the current field has no
        ///         parent, an empty FormNode object is returned.
        FormNode
        Parent()
        {
            return {get("/Parent")};
        }

        /// @brief Returns the top-level field associated with the current field.
        ///
        /// The function traverses the hierarchy of parent fields to identify the highest-level
        /// field in the tree. Typically, this will be the current field itself unless it has a
        /// parent field. Optionally, it can indicate whether the top-level field is different from
        /// the current field.
        ///
        /// @param is_different A pointer to a boolean that, if provided, will be set to true if the
        ///        top-level field differs from the current field; otherwise, it will be set to
        ///        false.
        ///
        /// @return The top-level field in the form field hierarchy.
        FormNode root_field(bool* is_different = nullptr);

        /// @brief Retrieves the inherited value of the specified attribute.
        ///
        /// @param name The name of the attribute to retrieve.
        /// @param acroform If true, checks the document's /AcroForm dictionary for the attribute
        ///                 if it is not found in the field hierarchy.
        ///
        /// @return A constant reference to the QPDFObjectHandle representing the value of the
        ///         specified attribute, if found. If the attribute is not found in the field
        ///         hierarchy or the /AcroForm dictionary (when `acroform` is true), returns a
        ///         reference to a static null object handle.
        QPDFObjectHandle const& inherited(std::string const& name, bool acroform = false) const;

        /// @brief Retrieves the value of a specified field, accounting for inheritance through the
        ///        hierarchy of ancestor nodes in the form field tree.
        ///
        /// This function attempts to retrieve the value of the specified field. If the `inherit`
        /// parameter is set to `true` and the field value is not found at the current level, the
        /// method traverses up the parent hierarchy to find the value. The traversal stops when a
        /// value is found, when the root node is reached, or when a loop detection mechanism
        /// prevents further traversal.
        ///
        /// @tparam T The return type of the field value.
        /// @param name The name of the field to retrieve the value for.
        /// @param inherit If set to `true`, the function will attempt to retrieve the value by
        ///        inheritance from the parent hierarchy of the form field. Defaults to `true`.
        /// @return Returns the field's value if found; otherwise, returns a default-constructed
        ///         value of type `T`.
        template <class T>
        T
        inheritable_value(std::string const& name, bool inherit = true, bool acroform = false) const
        {
            if (auto& v = get(name)) {
                return {v};
            }
            return {inherit ? inherited(name, acroform) : null_oh};
        }

        /// @brief Retrieves an inherited field string attribute as a string.
        ///
        /// @param name The name of the field for which the value is to be retrieved.
        /// @return The inherited field value as a UTF-8 encoded string, or an empty string if the
        ///         value does not exist or is not of String type.
        std::string inheritable_string(std::string const& name) const;

        /// @brief Retrieves the field type (/FT attribute).
        ///
        /// @param inherit If set to `true`, the function will attempt to retrieve the value by
        ///        inheritance from the parent hierarchy of the form field. Defaults to `true`.
        /// @return Returns the field type if found; otherwise, returns a default-constructed
        ///         `Name`.
        Name
        FT(bool inherit = true) const
        {
            return inheritable_value<Name>("/FT");
        }

        /// @brief Retrieves the partial field name (/T attribute).
        ///
        /// @return Returns the partial field name if found; otherwise, returns a
        ///         default-constructed `String`.
        String
        T() const
        {
            return {get("/T")};
        }

        /// @brief Retrieves the alternative name (/TU attribute).
        ///
        /// @return Returns the alternative name if found; otherwise, returns a default-constructed
        ///         `String`.
        String
        TU() const
        {
            return {get("/TU")};
        }

        /// @brief Retrieves the mapping name (/TM attribute).
        ///
        /// @return Returns the mapping name if found; otherwise, returns a default-constructed
        ///         `String`.
        String
        TM() const
        {
            return {get("/TM")};
        }

        /// @brief Retrieves the fully qualified name of the form field.
        ///
        /// This method constructs the fully qualified name of the form field by traversing through
        /// its parent hierarchy. The fully qualified name is constructed by concatenating the /T
        /// (field name) attribute of each parent node with periods as separators, starting from the
        /// root of the hierarchy.
        ///
        /// If the field has no parent hierarchy, the result will simply be the /T attribute of the
        /// current field. In cases of potential circular references, loop detection is applied.
        ///
        /// @return A string representing the fully qualified name of the field.
        std::string fully_qualified_name() const;

        /// @brief Retrieves the partial name (/T attribute) of the form field.
        ///
        /// This method returns the value of the field's /T attribute, which is the partial name
        /// used to identify the field within its parent hierarchy. If the attribute is not set, an
        /// empty string is returned.
        ///
        /// @return A string representing the partial name of the field in UTF-8 encoding, or an
        ///         empty string if the /T attribute is not present.
        std::string partial_name() const;

        /// @brief Retrieves the alternative name for the form field.
        ///
        /// This method attempts to return the alternative name (/TU) of the form field, which is
        /// the field name intended to be presented, to users as a UTF-8 string, if it exists. If
        /// the alternative name is not present, the method falls back to the fully qualified name
        /// of the form field.
        ///
        /// @return The alternative name of the form field as a string, or the
        /// fully qualified name if the alternative name is unavailable.
        std::string alternative_name() const;

        /// @brief Retrieves the mapping field name (/TM) for the form field.
        ///
        /// If the mapping name (/TM) is present, it is returned as a UTF-8 string. If not, it falls
        /// back to the 'alternative name', which is obtained using the `alternative_name()` method.
        ///
        /// @return The mapping field name (/TM) as a UTF-8 string or the alternative name if the
        ///         mapping name is absent.
        std::string mapping_name() const;

        /// @brief Retrieves the field value (`/V` attribute) of a specified field, accounting for
        ///        inheritance through the hierarchy of ancestor nodes in the form field tree.
        ///
        /// This function attempts to retrieve the `/V` attribute. If the `inherit`
        /// parameter is set to `true` and the `/V` is not found at the current level, the
        /// method traverses up the parent hierarchy to find the value. The traversal stops when
        /// `/V` is found, when the root node is reached, or when a loop detection mechanism
        /// prevents further traversal.
        ///
        /// @tparam T The return type.
        /// @param inherit If set to `true`, the function will attempt to retrieve `/V` by
        ///        inheritance from the parent hierarchy of the form field. Defaults to `true`.
        /// @return Returns the field's value if found; otherwise, returns a default-constructed
        /// value of type `T`.
        template <class T>
        T
        V(bool inherit = true) const
        {
            return inheritable_value<T>("/V", inherit);
        }

        /// @brief Retrieves the field value (`/V` attribute) of a specified field, accounting for
        ///        inheritance through the hierarchy of ancestor nodes in the form field tree.
        ///
        /// This function attempts to retrieve the `/V` attribute. If the `inherit`
        /// parameter is set to `true` and the `/V` is not found at the current level, the
        /// method traverses up the parent hierarchy to find the value. The traversal stops when
        /// `/V` is found, when the root node is reached, or when a loop detection mechanism
        /// prevents further traversal.
        ///
        /// @param inherit If set to `true`, the function will attempt to retrieve `/V` by
        ///        inheritance from the parent hierarchy of the form field. Defaults to `true`.
        /// @return Returns the field's value if found; otherwise, returns a default-constructed
        ///         object handle.
        QPDFObjectHandle const&
        V(bool inherit = true) const
        {
            if (auto& v = get("/V")) {
                return v;
            }
            return {inherit ? inherited("/V") : null_oh};
        }

        /// @brief Retrieves the field value `/V` attribute of the form field, considering
        ///        inheritance, if the value is a String.
        ///
        /// This function extracts the value of the form field, accounting for potential inheritance
        /// through the form hierarchy. It returns the value if it is a String, and an empty string
        /// otherwise.
        ///
        /// @return A string containing the actual or inherited `/V` attribute of the form field, or
        ///         an empty string if the value is not present or not a String.
        std::string value() const;

        /// @brief Retrieves the field default value (`/DV` attribute) of a specified field,
        ///        accounting for inheritance through the hierarchy of ancestor nodes in the form
        ///        field tree.
        ///
        /// This function attempts to retrieve the `/DV` attribute. If the `inherit` parameter is
        /// set to `true` and the `/DV` is not found at the current level, the method traverses up
        /// the parent hierarchy to find the value. The traversal stops when
        /// `/DV` is found, when the root node is reached, or when a loop detection mechanism
        /// prevents further traversal.
        ///
        /// @tparam T The return type.
        /// @param inherit If set to `true`, the function will attempt to retrieve `/DV` by
        ///        inheritance from the parent hierarchy of the form field. Defaults to `true`.
        /// @return Returns the field's default value if found; otherwise, returns a
        ///         default-constructed value of type `T`.
        QPDFObjectHandle const&
        DV(bool inherit = true) const
        {
            if (auto& v = get("/DV")) {
                return v;
            }
            return {inherit ? inherited("/DV") : null_oh};
        }

        /// @brief Retrieves the default value `/DV` attribute of the form field, considering
        ///        inheritance, if the default value is a String.
        ///
        /// This function extracts the default value of the form field, accounting for potential
        /// inheritance through the form hierarchy. It returns the value if it is a String, and an
        /// empty string otherwise.
        ///
        /// @return A string containing the actual or inherited `/DV` attribute of the form field,
        ///         or an empty string if the value is not present or not a String.
        std::string default_value() const;

        /// @brief Returns the default appearance string for the form field, considering inheritance
        ///        from the field tree hierarchy and the document's /AcroForm dictionary.
        ///
        /// This method retrieves the field's /DA (default appearance) attribute. If the attribute
        /// is not directly available, it checks the parent fields in the hierarchy for an inherited
        /// value. If no value is found in the field hierarchy, it attempts to retrieve the /DA
        /// attribute from the document's /AcroForm dictionary. The method returns an empty string
        /// if no default appearance string is available or applicable.
        ///
        /// @return A string representing the default appearance, or an empty string if
        ///         no value is found.
        std::string default_appearance() const;

        // Return the default resource dictionary for the field. This comes not from the field but
        // from the document-level /AcroForm dictionary. While several PDF generators put a /DR key
        // in the form field's dictionary, experimentation suggests that many popular readers,
        // including Adobe Acrobat and Acrobat Reader, ignore any /DR item on the field.
        QPDFObjectHandle getDefaultResources();

        // Return the quadding value, taking inheritance from the field tree into account. Returns 0
        // if quadding is not specified. Look in /AcroForm if not found in the field hierarchy.
        int getQuadding();

        // Return field flags from /Ff. The value is a logical or of pdf_form_field_flag_e as
        // defined in qpdf/Constants.h
        int getFlags();

        // Methods for testing for particular types of form fields

        // Returns true if field is of type /Tx
        bool isText();
        // Returns true if field is of type /Btn and flags do not indicate some other type of
        // button.
        bool isCheckbox();

        // Returns true if field is a checkbox and is checked.
        bool isChecked();

        // Returns true if field is of type /Btn and flags indicate that it is a radio button
        bool isRadioButton();

        // Returns true if field is of type /Btn and flags indicate that it is a pushbutton
        bool isPushbutton();

        // Returns true if field is of type /Ch
        bool isChoice();

        // Returns choices display values as UTF-8 strings
        std::vector<std::string> getChoices();

        // Set an attribute to the given value. If you have a QPDFAcroFormDocumentHelper and you
        // want to set the name of a field, use QPDFAcroFormDocumentHelper::setFormFieldName
        // instead.
        void setFieldAttribute(std::string const& key, QPDFObjectHandle value);

        // Set an attribute to the given value as a Unicode string (UTF-16 BE encoded). The input
        // string should be UTF-8 encoded. If you have a QPDFAcroFormDocumentHelper and you want to
        // set the name of a field, use QPDFAcroFormDocumentHelper::setFormFieldName instead.
        void setFieldAttribute(std::string const& key, std::string const& utf8_value);

        // Set /V (field value) to the given value. If need_appearances is true and the field type
        // is either /Tx (text) or /Ch (choice), set /NeedAppearances to true. You can explicitly
        // tell this method not to set /NeedAppearances if you are going to generate an appearance
        // stream yourself. Starting with qpdf 8.3.0, this method handles fields of type /Btn
        // (checkboxes, radio buttons, pushbuttons) specially. When setting a checkbox value, any
        // value other than /Off will be treated as on, and the actual value set will be based on
        // the appearance stream's /N dictionary, so the value that ends up in /V may not exactly
        // match the value you pass in.
        void setV(QPDFObjectHandle value, bool need_appearances = true);

        // Set /V (field value) to the given string value encoded as a Unicode string. The input
        // value should be UTF-8 encoded. See comments above about /NeedAppearances.
        void setV(std::string const& utf8_value, bool need_appearances = true);

        // Update the appearance stream for this field. Note that qpdf's ability to generate
        // appearance streams is limited. We only generate appearance streams for streams of type
        // text or choice. The appearance uses the default parameters provided in the file, and it
        // only supports ASCII characters. Quadding is currently ignored. While this functionality
        // is limited, it should do a decent job on properly constructed PDF files when field values
        // are restricted to ASCII characters.
        void generateAppearance(QPDFAnnotationObjectHelper&);

      private:
        /// @brief Retrieves an entry from the document's /AcroForm dictionary using the specified
        /// name.
        ///
        /// The method accesses the AcroForm dictionary within the root object of the PDF document.
        /// If the AcroForm dictionary contains the given field name, it retrieves the
        /// corresponding entry. Otherwise, it returns a default-constructed object handle.
        ///
        /// @param name The name of the form field to retrieve.
        /// @return An object handle corresponding to the specified name within the AcroForm
        /// dictionary.
        QPDFObjectHandle const&
        from_AcroForm(std::string const& name) const
        {
            return {qpdf() ? qpdf()->getRoot()["/AcroForm"][name] : null_oh};
        }

        void setRadioButtonValue(QPDFObjectHandle name);
        void setCheckBoxValue(bool value);
        void generateTextAppearance(QPDFAnnotationObjectHelper&);
        QPDFObjectHandle
        getFontFromResource(QPDFObjectHandle resources, std::string const& font_name);

        static const QPDFObjectHandle null_oh;
    }; // class FormNode
} // namespace qpdf::impl

class QPDFAcroFormDocumentHelper::Members: public qpdf::impl::AcroForm
{
  public:
    Members(QPDF& qpdf) :
        AcroForm(qpdf.doc())
    {
    }
};

#endif // ACRO_FORM_HH