class HexaPDF::Type::AcroForm::Field

AcroForm field dictionaries are used to define the properties of form fields of AcroForm objects.

Fields can be organized in a hierarchy using the /Kids and /Parent keys, for namespacing purposes and to set default values. Those fields that have other fields as children are called non-terminal fields, otherwise they are called terminal fields.

While field objects can be created manually, it is best to use the various create_ methods of the main Form object to create them so that all necessary things are set up correctly.

Field Types

Subclasses are used to implement the specific AcroForm field types:

Field Flags

Various characteristics of a field can be changed by setting a certain flag. Some flags are defined for all types of field, some are specific to a certain type.

The following flags apply to all fields:

:read_only

The field is read only which means the user can't change the value or interact with associated widget annotations.

:required

The field is required if the form is exported by a submit-form action.

:no_export

The field should not be exported by a submit-form action.

Field Type Implementation Notes

If an AcroForm field type adds additional inheritable dictionary fields, it has to set the constant INHERITABLE_FIELDS to all inheritable dictionary fields, including those from the superclass.

Similarily, if additional flags are provided, the constant FLAGS_BIT_MAPPING has to be set to combination of the superclass value of the constant and the mapping of flag names to bit indices.

See: PDF1.7 s12.7.3.1

Constants

INHERITABLE_FIELDS

The inheritable dictionary fields common to all AcroForm field types.

WIDGET_FIELDS

An array of all widget annotation field names.

Public Class Methods

inherited_value(field, name) click to toggle source

Treats name as an inheritable dictionary field and resolves its value for the AcroForm field field.

# File lib/hexapdf/type/acro_form/field.rb, line 150
def self.inherited_value(field, name)
  while field.value[name].nil? && (parent = field[:Parent])
    field = parent
  end
  field.value[name].nil? ? nil : field[name]
end

Public Instance Methods

[](name) click to toggle source

Returns the value for the entry name.

If name is an inheritable field and the value has not been set on this field object, its value is retrieved from the parent fields.

See: Dictionary#[]

Calls superclass method HexaPDF::Dictionary#[]
# File lib/hexapdf/type/acro_form/field.rb, line 168
def [](name)
  if value[name].nil? && self.class::INHERITABLE_FIELDS.include?(name)
    self.class.inherited_value(self, name) || super
  else
    super
  end
end
alternate_field_name() click to toggle source

Returns the alternate field name that should be used for display purposes (e.g. Acrobat shows this as tool tip).

# File lib/hexapdf/type/acro_form/field.rb, line 220
def alternate_field_name
  self[:TU]
end
alternate_field_name=(value) click to toggle source

Sets the alternate field name.

See alternate_field_name

# File lib/hexapdf/type/acro_form/field.rb, line 227
def alternate_field_name=(value)
  self[:TU] = value
end
concrete_field_type() click to toggle source

Returns the concrete field type (:button_field, :text_field, :choice_field or :signature_field) or nil is no field type is set.

In constrast to field_type this method also considers the field flags and not just the field type. This means that subclasses can return a more concrete name for the field type.

Also see field_type

# File lib/hexapdf/type/acro_form/field.rb, line 191
def concrete_field_type
  case self[:FT]
  when :Btn then :button_field
  when :Tx  then :text_field
  when :Ch  then :choice_field
  when :Sig then :signature_field
  else nil
  end
end
create_widget(page, allow_embedded: true, **values) click to toggle source

Creates a new widget annotation for this form field (must be a terminal field!) on the given page, adding the values to the created widget annotation oject.

If allow_embedded is false, embedding the first widget in the field itself is not allowed.

The values argument should at least include :Rect for setting the visible area of the widget.

If the field already has an embedded widget, i.e. field and widget are the same PDF object, its widget data is extracted to a new PDF object and stored in the /Kids field, together with the new widget annotation. Note that this means that a possible reference to the formerly embedded widget (=this field) is not valid anymore!

See: HexaPDF::Type::Annotations::Widget

# File lib/hexapdf/type/acro_form/field.rb, line 275
def create_widget(page, allow_embedded: true, **values)
  unless terminal_field?
    raise HexaPDF::Error, "Widgets can only be added to terminal fields"
  end

  widget_data = {Type: :Annot, Subtype: :Widget, Rect: [0, 0, 0, 0], **values}

  if !allow_embedded || embedded_widget? || (key?(:Kids) && !self[:Kids].empty?)
    kids = self[:Kids] ||= []
    kids << extract_widget if embedded_widget?
    widget = document.add(widget_data)
    widget[:Parent] = self
    self[:Kids] << widget
  else
    value.update(widget_data)
    widget = document.wrap(self)
  end

  (page[:Annots] ||= []) << widget

  widget
end
delete_widget(widget) click to toggle source

Deletes the given widget annotation object from this field, the page it appears on and the document.

If the given widget is not a widget of this field, nothing is done.

# File lib/hexapdf/type/acro_form/field.rb, line 302
def delete_widget(widget)
  widget = if embedded_widget? && self == widget
             widget
           elsif terminal_field?
             (widget_index = self[:Kids]&.index(widget)) && widget
           end

  return unless widget

  document.pages.each do |page|
    break if page[:Annots]&.delete(widget) # See comment in #extract_widget
  end

  if embedded_widget?
    WIDGET_FIELDS.each {|key| delete(key) }
    document.revisions.each {|revision| break if revision.update(self)}
  else
    self[:Kids].delete_at(widget_index)
    document.delete(widget)
  end
end
each_widget {|widget| block} → field click to toggle source
each_widget → Enumerator

Yields each widget, i.e. visual representation, of this field.

See: HexaPDF::Type::Annotations::Widget

# File lib/hexapdf/type/acro_form/field.rb, line 250
def each_widget # :yields: widget
  return to_enum(__method__) unless block_given?
  if embedded_widget?
    yield(document.wrap(self))
  elsif terminal_field?
    self[:Kids]&.each {|kid| yield(document.wrap(kid)) }
  end
  self
end
embedded_widget?() click to toggle source

Returns true if the field contains an embedded widget.

# File lib/hexapdf/type/acro_form/field.rb, line 239
def embedded_widget?
  key?(:Subtype)
end
field_name() click to toggle source

Returns the name of the field or nil if no name is set.

# File lib/hexapdf/type/acro_form/field.rb, line 202
def field_name
  self[:T]
end
field_type() click to toggle source

Returns the type of the field, either :Btn (pushbuttons, check boxes, radio buttons), :Tx (text fields), :Ch (scrollable list boxes, combo boxes) or :Sig (signature fields).

Also see concrete_field_type

# File lib/hexapdf/type/acro_form/field.rb, line 180
def field_type
  self[:FT]
end
flag(*flags, clear_existing: false) click to toggle source

Sets the given flags, given as flag names or bit indices. If clear_existing is true, all prior flags will be cleared.

# File lib/hexapdf/type/acro_form/field.rb, line 144
bit_field(:flags, {read_only: 0, required: 1, no_export: 2},
          lister: "flags", getter: "flagged?", setter: "flag", unsetter: "unflag",
          value_getter: "self[:Ff]", value_setter: "self[:Ff]")
flagged?(flag) click to toggle source

Returns true if the given flag is set. The argument can either be the flag name or the bit index.

# File lib/hexapdf/type/acro_form/field.rb, line 128
        
flags() click to toggle source

Returns an array of flag names representing the set bit flags.

# File lib/hexapdf/type/acro_form/field.rb, line 122
        
full_field_name() click to toggle source

Returns the full name of the field or nil if no name is set.

The full name of a field is constructed using the full name of the parent field, a period and the field name of the field.

# File lib/hexapdf/type/acro_form/field.rb, line 210
def full_field_name
  if key?(:Parent)
    [self[:Parent].full_field_name, field_name].compact.join('.')
  else
    field_name
  end
end
must_be_indirect?() click to toggle source

Form fields must always be indirect objects.

# File lib/hexapdf/type/acro_form/field.rb, line 158
def must_be_indirect?
  true
end
terminal_field?() click to toggle source

Returns true if this is a terminal field.

# File lib/hexapdf/type/acro_form/field.rb, line 232
def terminal_field?
  kids = self[:Kids]
  # PDF 2.0 s12.7.4.2 clarifies how to do check for fields since PDF 1.7 isn't clear
  kids.nil? || kids.empty? || kids.none? {|kid| kid.key?(:T) }
end

Private Instance Methods

extract_widget() click to toggle source

Returns a new dictionary object with all the widget annotation data that is stored directly in the field and adjust the references accordingly. If the field doesn't have any widget data, nil is returned.

# File lib/hexapdf/type/acro_form/field.rb, line 332
def extract_widget
  return unless embedded_widget?
  data = WIDGET_FIELDS.each_with_object({}) do |key, hash|
    hash[key] = delete(key) if key?(key)
  end
  widget = document.add(data, type: :Annot)
  widget[:Parent] = self
  document.pages.each do |page|
    if page.key?(:Annots) && (index = page[:Annots].index(self))
      page[:Annots][index] = widget
      break # Each annotation dictionary may only appear on one page, see PDF1.7 12.5.2
    end
  end
  widget
end