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

create_appearances(force: false) click to toggle source

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
create_check_box(name) click to toggle source

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
create_comb_text_field(name, max_chars:, font: nil, font_size: nil, align: nil) click to toggle source

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
create_combo_box(name, option_items: nil, editable: nil, font: nil, font_size: nil, align: nil) click to toggle source

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 and align

See create_text_field

# 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
create_file_select_field(name, font: nil, font_size: nil, align: nil) click to toggle source

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
create_list_box(name, option_items: nil, multi_select: nil, font: nil, font_size: nil, align: nil) click to toggle source

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 and align

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
create_multiline_text_field(name, font: nil, font_size: nil, align: nil) click to toggle source

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
create_password_field(name, font: nil, font_size: nil, align: nil) click to toggle source

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
create_radio_button(name) click to toggle source

Creates a radio button 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 246
def create_radio_button(name)
  create_field(name, :Btn, &:initialize_as_radio_button)
end
create_text_field(name, font: nil, font_size: nil, align: nil) click to toggle source

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 but font isn't, the font Helvetica is used.

font_size

The font size that should be used. If font is specified but font_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
default_resources() click to toggle source

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
each_field(terminal_only: true) {|field| block} → acroform click to toggle source
each_field(terminal_only: true) → Enumerator

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
field_by_name(name) click to toggle source

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
find_root_fields() click to toggle source

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
find_root_fields!() click to toggle source

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
flatten(fields: nil, create_appearances: true) click to toggle source

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
need_appearances!() click to toggle source

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
root_fields() click to toggle source

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
set_default_appearance_string(font: 'Helvetica', font_size: 0) click to toggle source

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

apply_variable_text_properties(field, font: nil, font_size: nil, align: nil) click to toggle source

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
create_field(name, type) { |field| ... } click to toggle source

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
raw_signature_flags() click to toggle source

Helper method for bit field getter access.

# File lib/hexapdf/type/acro_form/form.rb, line 373
def raw_signature_flags
  self[:SigFlags]
end
raw_signature_flags=(value) click to toggle source

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