class HexaPDF::Type::AcroForm::Form
Represents the PDF's interactive form dictionary. It is linked from the catalog dictionary via the /AcroForm entry.
Overview¶ ↑
An interactive form consists of fields which can be structured hierarchically and shown on pages by using Annotations::Widget
annotations. This means one field can have zero, one or more visual representations on one or more pages. The fields at the bottom of the hierarchy which have no parent are called “root fields” and are stored in /Fields.
Each field in a form has a certain type which determines how it should be displayed and what a user can do with it. The most common type is “text field” which allows the user to enter one or more lines of text. There are also check boxes, radio buttons, list boxes and combo boxes.
Visual Appearance¶ ↑
The visual appearance of a field is normally provided by the application creating the PDF. This is done by generating the so called appearances for all widgets of a field. However, it is also possible to instruct the PDF reader application to generate the appearances on the fly using the /NeedAppearances key, see need_appearances!
.
HexaPDF
uses the configuration option acro_form.create_appearance_streams
to determine whether appearances should automatically be generated.
See: PDF1.7 s12.7.2, Field
, HexaPDF::Type::Annotations::Widget
Public Instance Methods
Creates the appearances for all widgets of all terminal fields if they don't exist.
If force
is true
, new appearances are created even if there are existing ones.
# File lib/hexapdf/type/acro_form/form.rb, line 327 def create_appearances(force: false) each_field do |field| field.create_appearances(force: force) if field.respond_to?(:create_appearances) end end
Creates a new check box with the given name and adds it to the form.
The name
may contain dots to signify a field hierarchy. If so, the referenced parent fields must already exist. If it doesn't contain dots, a top-level field is created.
# File lib/hexapdf/type/acro_form/form.rb, line 238 def create_check_box(name) create_field(name, :Btn, &:initialize_as_check_box) end
Creates a new comb text field with the given name and adds it to the form.
The max_chars
argument defines the maximum number of characters the comb text field can accommodate.
The name
may contain dots to signify a field hierarchy. If so, the referenced parent fields must already exist. If it doesn't contain dots, a top-level field is created.
The optional keyword arguments allow setting often used properties of the field, see create_text_field
for details.
# File lib/hexapdf/type/acro_form/form.rb, line 198 def create_comb_text_field(name, max_chars:, font: nil, font_size: nil, align: nil) create_field(name, :Tx) do |field| field.initialize_as_comb_text_field apply_variable_text_properties(field, font: font, font_size: font_size, align: align) field[:MaxLen] = max_chars end end
Creates a combo box with the given name and adds it to the form.
The name
may contain dots to signify a field hierarchy. If so, the referenced parent fields must already exist. If it doesn't contain dots, a top-level field is created.
The optional keyword arguments allow setting often used properties of the field:
option_items
-
Specifies the values of the list box.
editable
-
If set to
true
, the combo box allows entering an arbitrary value in addition to selecting one of the provided option items. font
,font_size
andalign
# File lib/hexapdf/type/acro_form/form.rb, line 266 def create_combo_box(name, option_items: nil, editable: nil, font: nil, font_size: nil, align: nil) create_field(name, :Ch) do |field| field.initialize_as_combo_box field.option_items = option_items if option_items field.flag(:edit) if editable apply_variable_text_properties(field, font: font, font_size: font_size, align: align) end end
Creates a new file select field with the given name and adds it to the form.
The name
may contain dots to signify a field hierarchy. If so, the referenced parent fields must already exist. If it doesn't contain dots, a top-level field is created.
The optional keyword arguments allow setting often used properties of the field, see create_text_field
for details.
# File lib/hexapdf/type/acro_form/form.rb, line 213 def create_file_select_field(name, font: nil, font_size: nil, align: nil) create_field(name, :Tx) do |field| field.initialize_as_file_select_field apply_variable_text_properties(field, font: font, font_size: font_size, align: align) end end
Creates a list box with the given name and adds it to the form.
The name
may contain dots to signify a field hierarchy. If so, the referenced parent fields must already exist. If it doesn't contain dots, a top-level field is created.
The optional keyword arguments allow setting often used properties of the field:
option_items
-
Specifies the values of the list box.
multi_select
-
If set to
true
, the list box allows selecting multiple items instead of only one. font
,font_size
andalign
-
See
create_text_field
.
# File lib/hexapdf/type/acro_form/form.rb, line 291 def create_list_box(name, option_items: nil, multi_select: nil, font: nil, font_size: nil, align: nil) create_field(name, :Ch) do |field| field.initialize_as_list_box field.option_items = option_items if option_items field.flag(:multi_select) if multi_select apply_variable_text_properties(field, font: font, font_size: font_size, align: align) end end
Creates a new multiline text field with the given name and adds it to the form.
The name
may contain dots to signify a field hierarchy. If so, the referenced parent fields must already exist. If it doesn't contain dots, a top-level field is created.
The optional keyword arguments allow setting often used properties of the field, see create_text_field
for details.
# File lib/hexapdf/type/acro_form/form.rb, line 181 def create_multiline_text_field(name, font: nil, font_size: nil, align: nil) create_field(name, :Tx) do |field| field.initialize_as_multiline_text_field apply_variable_text_properties(field, font: font, font_size: font_size, align: align) end end
Creates a new password field with the given name and adds it to the form.
The name
may contain dots to signify a field hierarchy. If so, the referenced parent fields must already exist. If it doesn't contain dots, a top-level field is created.
The optional keyword arguments allow setting often used properties of the field, see create_text_field
for details.
# File lib/hexapdf/type/acro_form/form.rb, line 227 def create_password_field(name, font: nil, font_size: nil, align: nil) create_field(name, :Tx) do |field| field.initialize_as_password_field apply_variable_text_properties(field, font: font, font_size: font_size, align: align) end end
Creates a new text field with the given name and adds it to the form.
The name
may contain dots to signify a field hierarchy. If so, the referenced parent fields must already exist. If it doesn't contain dots, a top-level field is created.
The optional keyword arguments allow setting often used properties of the field:
font
-
The font that should be used for the text of the field. If
font_size
is specified butfont
isn't, the font Helvetica is used. font_size
-
The font size that should be used. If
font
is specified butfont_size
isn't, font size defaults to 0 (= auto-sizing). align
-
The alignment of the text, either :left, :center or :right.
# File lib/hexapdf/type/acro_form/form.rb, line 168 def create_text_field(name, font: nil, font_size: nil, align: nil) create_field(name, :Tx) do |field| apply_variable_text_properties(field, font: font, font_size: font_size, align: align) end end
Returns the dictionary containing the default resources for form field appearance streams.
# File lib/hexapdf/type/acro_form/form.rb, line 302 def default_resources self[:DR] ||= document.wrap({ProcSet: [:PDF, :Text, :ImageB, :ImageC, :ImageI]}, type: :XXResources) end
Yields all terminal fields or all fields, depending on the terminal_only
argument.
# File lib/hexapdf/type/acro_form/form.rb, line 123 def each_field(terminal_only: true) return to_enum(__method__, terminal_only: terminal_only) unless block_given? process_field = lambda do |field| field = document.wrap(field, type: :XXAcroFormField, subtype: Field.inherited_value(field, :FT)) yield(field) if field.terminal_field? || !terminal_only field[:Kids].each(&process_field) unless field.terminal_field? end root_fields.each(&process_field) self end
Returns the field with the given name
or nil
if no such field exists.
# File lib/hexapdf/type/acro_form/form.rb, line 138 def field_by_name(name) fields = root_fields field = nil name.split('.').each do |part| field = fields&.find {|f| f[:T] == part } break unless field field = document.wrap(field, type: :XXAcroFormField, subtype: Field.inherited_value(field, :FT)) fields = field[:Kids] unless field.terminal_field? end field end
Returns an array with all root fields that were found in the PDF document.
# File lib/hexapdf/type/acro_form/form.rb, line 95 def find_root_fields result = [] document.pages.each do |page| page[:Annots]&.each do |annot| if !annot.key?(:Parent) && annot.key?(:FT) result << document.wrap(annot, type: :XXAcroFormField, subtype: annot[:FT]) elsif annot.key?(:Parent) field = annot[:Parent] field = field[:Parent] while field[:Parent] result << document.wrap(field, type: :XXAcroFormField) end end end result end
Finds all root fields and sets /Fields appropriately.
See: find_root_fields
# File lib/hexapdf/type/acro_form/form.rb, line 114 def find_root_fields! self[:Fields] = find_root_fields end
Flattens the whole interactive form or only the given fields, and returns the fields that couldn't be flattened.
Flattening means making the appearance streams of the field widgets part of the respective page's content stream and removing the fields themselves.
If the whole interactive form is flattened, the form object itself is also removed if all fields were flattened.
The create_appearances
argument controls whether missing appearances should automatically be created.
See: HexaPDF::Type::Page#flatten_annotations
# File lib/hexapdf/type/acro_form/form.rb, line 346 def flatten(fields: nil, create_appearances: true) remove_form = fields.nil? fields ||= each_field.to_a if create_appearances fields.each {|field| field.create_appearances if field.respond_to?(:create_appearances) } end not_flattened = fields.map {|field| field.each_widget.to_a }.flatten document.pages.each {|page| not_flattened = page.flatten_annotations(not_flattened) } fields -= not_flattened.map(&:form_field) fields.each do |field| (field[:Parent]&.[](:Kids) || self[:Fields]).delete(field) document.delete(field) end if remove_form && not_flattened.empty? document.catalog.delete(:AcroForm) document.delete(self) end not_flattened end
Sets the /NeedAppearances field to true
.
This will make PDF reader applications generate appropriate appearance streams based on the information stored in the fields and associated widgets.
# File lib/hexapdf/type/acro_form/form.rb, line 320 def need_appearances! self[:NeedAppearances] = true end
Returns the PDFArray
containing the root fields.
# File lib/hexapdf/type/acro_form/form.rb, line 90 def root_fields self[:Fields] ||= document.wrap([]) end
Sets the global default appearance string using the provided values.
The default argument values are a sane default. If font_size
is set to 0, the font size is calculated using the height/width of the field.
# File lib/hexapdf/type/acro_form/form.rb, line 311 def set_default_appearance_string(font: 'Helvetica', font_size: 0) name = default_resources.add_font(document.fonts.add(font).pdf_object) self[:DA] = "0 g /#{name} #{font_size} Tf" end
Private Instance Methods
Applies the given variable field properties to the field.
# File lib/hexapdf/type/acro_form/form.rb, line 404 def apply_variable_text_properties(field, font: nil, font_size: nil, align: nil) if font || font_size field.set_default_appearance_string(font: font || 'Helvetica', font_size: font_size || 0) end field.text_alignment(align) if align end
Creates a new field with the full name name
and the field type type
.
# File lib/hexapdf/type/acro_form/form.rb, line 383 def create_field(name, type) parent_name, _, name = name.rpartition('.') parent_field = parent_name.empty? ? nil : field_by_name(parent_name) if !parent_name.empty? && !parent_field raise HexaPDF::Error, "Parent field '#{parent_name}' not found" end field = document.add({FT: type, T: name, Parent: parent_field}, type: :XXAcroFormField, subtype: type) if parent_field (parent_field[:Kids] ||= []) << field else (self[:Fields] ||= []) << field end yield(field) field end
Helper method for bit field getter access.
# File lib/hexapdf/type/acro_form/form.rb, line 373 def raw_signature_flags self[:SigFlags] end
Helper method for bit field setter access.
# File lib/hexapdf/type/acro_form/form.rb, line 378 def raw_signature_flags=(value) self[:SigFlags] = value end