class HexaPDF::Type::AcroForm::Field

See: PDF2.0 s12.7.4.1
indices.
to combination of the superclass value of the constant and the mapping of flag names to bit
Similarily, if additional flags are provided, the constant FLAGS_BIT_MAPPING has to be set
superclass.
constant INHERITABLE_FIELDS to all inheritable dictionary fields, including those from the
If an AcroForm field type adds additional inheritable dictionary fields, it has to set the
== Field Type Implementation Notes
Also see the class description of the subclasses for additional, type specific field flags.
:no_export
The field should not be exported by a submit-form action.
:required
The field is required if the form is exported by a submit-form action.
with associated widget annotations.
:read_only

The field is read only which means the user can’t change the value or interact
The following flags apply to all fields:
defined for all types of field, some are specific to a certain type.
Various characteristics of a field can be changed by setting a certain flag. Some flags are
== Field Flags
* SignatureField implements signature fields.
* ChoiceField implements scrollable list boxes or (editable) combo boxes.
* TextField implements single or multiline text fields.
* ButtonField implements the button fields (pushbuttons, check boxes and radio buttons)
Subclasses are used to implement the specific AcroForm field types:
== Field Types
of the main Form object to create them so that all necessary things are set up correctly.
While field objects can be created manually, it is best to use the various create_ methods
called non-terminal fields, otherwise they are called terminal fields.
purposes and to set default values. Those fields that have other fields as children are
Fields can be organized in a hierarchy using the /Kids and /Parent keys, for namespacing
objects.
AcroForm field dictionaries are used to define the properties of form fields of AcroForm

def self.inherited_value(field, name)

field +field+.
Treats +name+ as an inheritable dictionary field and resolves its value for the AcroForm
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

def self.wrap(document, field)

object.
Wraps the given +field+ object inside the correct field class and returns the wrapped
def self.wrap(document, field)
  document.wrap(field, type: :XXAcroFormField, subtype: inherited_value(field, :FT))
end

def [](name)

See: Dictionary#[]

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

Returns the value for the entry +name+.
def [](name)
  if value[name].nil? && self.class::INHERITABLE_FIELDS.include?(name)
    self.class.inherited_value(self, name) || super
  else
    super
  end
end

def alternate_field_name

shows this as tool tip).
Returns the alternate field name that should be used for display purposes (e.g. Acrobat
def alternate_field_name
  self[:TU]
end

def alternate_field_name=(value)

See #alternate_field_name

Sets the alternate field name.
def alternate_field_name=(value)
  self[:TU] = value
end

def concrete_field_type

Also see #field_type

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

:signature_field) or +nil+ is no field type is set.
Returns the concrete field type (:button_field, :text_field, :choice_field or
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

def create_widget(page, allow_embedded: true, **values)

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

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

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

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

given +page+, adding the +values+ to the created widget annotation object.
Creates a new widget annotation for this form field (must be a terminal field!) on the
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
  widget.flag(:print)
  widget[:P] = page
  (page[:Annots] ||= []) << widget
  widget
end

def delete_widget(widget)

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

document.
Deletes the given widget annotation object from this field, the page it appears on and the
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

def each_widget(direct_only: true, &block) # :yields: widget

:yields: widget
See: HexaPDF::Type::Annotations::Widget

same full field name.
fields of the form have to be searched to check whether there is another field with the
*Note*: Setting +direct_only+ to +false+ will have a severe performance impact since all

reduce case 3 to case 2).
the main AcroForm object (HexaPDF::Document#acro_form) before using this method (this will
If case 3 also needs to be handled, set +direct_only+ to +false+ or run the validation on
With the default of +direct_only+ being +true+, only the usual cases 1 and 2 are handled/

3. Widgets of *another field instance with the same full field name*.
2. One or more widgets are defined as children of this field.
1. The widget can be embedded in the field itself.

Widgets can be associated to the field in three ways:

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

field.each_widget(direct_only: true) -> Enumerator
field.each_widget(direct_only: true) {|widget| block} -> field
:call-seq:
def each_widget(direct_only: true, &block) # :yields: widget
  return to_enum(__method__, direct_only: direct_only) unless block_given?
  if embedded_widget?
    yield(document.wrap(self))
  elsif terminal_field?
    self[:Kids]&.each {|kid| yield(document.wrap(kid)) }
  end
  unless direct_only
    my_name = full_field_name
    document.acro_form&.each_field do |field|
      next if field.full_field_name != my_name || field == self
      field.each_widget(direct_only: true, &block)
    end
  end
  self
end

def embedded_widget?

Returns +true+ if the field contains an embedded widget.
def embedded_widget?
  key?(:Subtype)
end

def extract_widget

widget data, +nil+ is returned.
directly in the field and adjust the references accordingly. If the field doesn't have any
Returns a new dictionary object with all the widget annotation data that is stored
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 PDF2.0 12.5.2
    end
  end
  document.revisions.current.update(self)
  widget
end

def field_name

Returns the name of the field or +nil+ if no name is set.
def field_name
  self[:T]
end

def field_type

Also see #concrete_field_type

(text fields), :Ch (scrollable list boxes, combo boxes) or :Sig (signature fields).
Returns the type of the field, either :Btn (pushbuttons, check boxes, radio buttons), :Tx
def field_type
  self[:FT]
end

def form_field

either be a form field or a field widget.
This method is only here to make it easier to get the form field when the object may

Returns self.
def form_field
  self
end

def full_field_name

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

Returns the full name of the field or +nil+ if no name is set.
def full_field_name
  if key?(:Parent)
    [self[:Parent].full_field_name, field_name].compact.join('.')
  else
    field_name
  end
end

def must_be_indirect?

Form fields must always be indirect objects.
def must_be_indirect?
  true
end

def perform_validation #:nodoc:

:nodoc:
def perform_validation #:nodoc:
  super
  if terminal_field? && field_type.nil?
    yield("/FT is required for terminal fields")
  end
  if key?(:T) && self[:T].include?('.')
    yield("/T shall not contain a period")
  end
end

def terminal_field?

Returns +true+ if this is a terminal field.
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