Class: HexaPDF::Type::AcroForm::Field

Inherits:
Dictionary show all
Extended by:
Utils::BitField
Defined in:
lib/hexapdf/type/acro_form/field.rb

Overview

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:

  • ButtonField implements the button fields (pushbuttons, check boxes and radio buttons)

  • TextField implements single or multiline text fields.

  • ChoiceField implements scrollable list boxes or (editable) combo boxes.

  • SignatureField implements signature fields.

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.

Also see the class description of the subclasses for additional, type specific field flags.

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: PDF2.0 s12.7.4.1

Direct Known Subclasses

ButtonField, SignatureField, VariableTextField

Defined Under Namespace

Modules: HashRefinement

Constant Summary collapse

INHERITABLE_FIELDS =

The inheritable dictionary fields common to all AcroForm field types.

[:FT, :Ff, :V, :DV].freeze

Constants included from DictionaryFields

DictionaryFields::Boolean, DictionaryFields::PDFByteString, DictionaryFields::PDFDate

Instance Attribute Summary

Attributes inherited from Object

#data, #document, #must_be_indirect

Class Method Summary collapse

Instance Method Summary collapse

Methods included from Utils::BitField

bit_field

Methods inherited from Dictionary

#[]=, define_field, define_type, #delete, #each, each_field, #empty?, field, #key?, #to_hash, type, #type

Methods inherited from Object

#<=>, #==, #cache, #cached?, #clear_cache, deep_copy, #deep_copy, #document?, #eql?, field, #gen, #gen=, #hash, #indirect?, #initialize, #inspect, make_direct, #null?, #oid, #oid=, #type, #validate, #value, #value=

Constructor Details

This class inherits a constructor from HexaPDF::Object

Class Method Details

.inherited_value(field, name) ⇒ Object

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



159
160
161
162
163
164
# File 'lib/hexapdf/type/acro_form/field.rb', line 159

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

.wrap(document, field) ⇒ Object

Wraps the given field object inside the correct field class and returns the wrapped object.



168
169
170
# File 'lib/hexapdf/type/acro_form/field.rb', line 168

def self.wrap(document, field)
  document.wrap(field, type: :XXAcroFormField, subtype: inherited_value(field, :FT))
end

Instance Method Details

#[](name) ⇒ Object

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#[]



183
184
185
186
187
188
189
# File 'lib/hexapdf/type/acro_form/field.rb', line 183

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_nameObject

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



235
236
237
# File 'lib/hexapdf/type/acro_form/field.rb', line 235

def alternate_field_name
  self[:TU]
end

#alternate_field_name=(value) ⇒ Object

Sets the alternate field name.

See #alternate_field_name



242
243
244
# File 'lib/hexapdf/type/acro_form/field.rb', line 242

def alternate_field_name=(value)
  self[:TU] = value
end

#concrete_field_typeObject

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



206
207
208
209
210
211
212
213
214
# File 'lib/hexapdf/type/acro_form/field.rb', line 206

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) ⇒ Object

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 object.

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



323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
# File 'lib/hexapdf/type/acro_form/field.rb', line 323

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

#delete_widget(widget) ⇒ Object

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.



352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
# File 'lib/hexapdf/type/acro_form/field.rb', line 352

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(direct_only: true, &block) ⇒ Object

:call-seq:

field.each_widget(direct_only: true) {|widget| block}    -> field
field.each_widget(direct_only: true)                     -> Enumerator

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

Widgets can be associated to the field in three ways:

  1. The widget can be embedded in the field itself.

  2. One or more widgets are defined as children of this field.

  3. Widgets of *another field instance with the same full field name*.

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

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

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



288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
# File 'lib/hexapdf/type/acro_form/field.rb', line 288

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

#embedded_widget?Boolean

Returns true if the field contains an embedded widget.

Returns:



262
263
264
# File 'lib/hexapdf/type/acro_form/field.rb', line 262

def embedded_widget?
  key?(:Subtype)
end

#field_nameObject

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



217
218
219
# File 'lib/hexapdf/type/acro_form/field.rb', line 217

def field_name
  self[:T]
end

#field_typeObject

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



195
196
197
# File 'lib/hexapdf/type/acro_form/field.rb', line 195

def field_type
  self[:FT]
end

#flagsObject

:method: flag :call-seq:

flag(*flags, clear_existing: false)

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

See the class description for a list of available flags.



153
154
155
# File 'lib/hexapdf/type/acro_form/field.rb', line 153

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]")

#form_fieldObject

Returns self.

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



257
258
259
# File 'lib/hexapdf/type/acro_form/field.rb', line 257

def form_field
  self
end

#full_field_nameObject

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.



225
226
227
228
229
230
231
# File 'lib/hexapdf/type/acro_form/field.rb', line 225

def full_field_name
  if key?(:Parent)
    [self[:Parent].full_field_name, field_name].compact.join('.')
  else
    field_name
  end
end

#must_be_indirect?Boolean

Form fields must always be indirect objects.

Returns:



173
174
175
# File 'lib/hexapdf/type/acro_form/field.rb', line 173

def must_be_indirect?
  true
end

#terminal_field?Boolean

Returns true if this is a terminal field.

Returns:



247
248
249
250
251
# File 'lib/hexapdf/type/acro_form/field.rb', line 247

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