Class: Pdfrb::Document::Form

Inherits:
Object
  • Object
show all
Defined in:
lib/pdfrb/document/form.rb

Overview

Facade for AcroForm interactive forms (ISO 32000-2 §12.7).

Creates the /AcroForm dictionary on the Catalog and manages form fields. Each field is a /Type /Annot /Subtype /Widget indirect object linked to a page via /P and positioned via /Rect.

Supported field types (FT):

:Btn — button (checkbox, radio, pushbutton)
:Tx  — text input
:Ch  — choice (list, combo)
:Sig — digital signature

Appearance streams are generated by the viewer when /NeedAppearances is true on the /AcroForm dict.

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(document) ⇒ Form

Returns a new instance of Form.



22
23
24
# File 'lib/pdfrb/document/form.rb', line 22

def initialize(document)
  @document = document
end

Instance Attribute Details

#documentObject (readonly)

Returns the value of attribute document.



20
21
22
# File 'lib/pdfrb/document/form.rb', line 20

def document
  @document
end

Instance Method Details

#add_checkbox(name, page:, rect:, checked: false) ⇒ Object

Add a checkbox field. @return [Pdfrb::Model::Cos::Dictionary] the field.



56
57
58
59
60
61
62
63
# File 'lib/pdfrb/document/form.rb', line 56

def add_checkbox(name, page:, rect:, checked: false)
  enable!
  field = build_widget(name, page: page, rect: rect)
  field.value[:FT] = :Btn
  field.value[:V] = checked ? :Yes : :Off
  register_field(field)
  field
end

#add_combo(name, page:, rect:, options:, value: nil) ⇒ Pdfrb::Model::Cos::Dictionary

Add a combo box (dropdown) field.

Returns:



67
68
69
70
71
72
73
74
75
76
# File 'lib/pdfrb/document/form.rb', line 67

def add_combo(name, page:, rect:, options:, value: nil)
  enable!
  field = build_widget(name, page: page, rect: rect)
  field.value[:FT] = :Ch
  field.value[:Ff] = 0x20000 # bit 18 = Combo
  field.value[:Opt] = options
  field.value[:V] = value if value
  register_field(field)
  field
end

#add_text_field(name, page:, rect:, value: nil, multiline: false) ⇒ Object

Add a text field. @return [Pdfrb::Model::Cos::Dictionary] the field.



42
43
44
45
46
47
48
49
50
51
52
53
# File 'lib/pdfrb/document/form.rb', line 42

def add_text_field(name, page:, rect:, value: nil, multiline: false)
  enable!
  flags = 0
  flags |= 0x1000 if multiline # bit 13 = multiline

  field = build_widget(name, page: page, rect: rect)
  field.value[:FT] = :Tx
  field.value[:Ff] = flags if flags.positive?
  field.value[:V] = value if value
  register_field(field)
  field
end

#countObject

Total field count.



79
80
81
82
83
84
85
# File 'lib/pdfrb/document/form.rb', line 79

def count
  acroform = document.catalog.value[:AcroForm]
  return 0 unless acroform

  fields = acroform[:Fields]
  fields ? fields.length : 0
end

#delete_acroform_key(acroform, key) ⇒ Object



149
150
151
152
153
154
155
156
157
# File 'lib/pdfrb/document/form.rb', line 149

def delete_acroform_key(acroform, key)
  return unless acroform

  if acroform.is_a?(Pdfrb::Model::Cos::Dictionary)
    acroform.value.delete(key)
  else
    acroform.delete(key)
  end
end

#each_field(&block) ⇒ Object

Enumerate all top-level fields.



93
94
95
96
97
98
99
100
101
102
103
104
# File 'lib/pdfrb/document/form.rb', line 93

def each_field(&block)
  acroform = document.catalog.value[:AcroForm]
  return enum_for(:each_field) unless block

  return unless acroform

  fields = acroform[:Fields] || []
  fields.each do |ref|
    obj = ref.is_a?(Pdfrb::Model::Reference) ? document.object(ref) : ref
    yield obj if obj
  end
end

#enable!Object

Enable AcroForm by creating the /AcroForm dict on the Catalog. Idempotent. @return [Hash] the AcroForm dict value.



28
29
30
31
32
33
34
35
36
37
38
39
# File 'lib/pdfrb/document/form.rb', line 28

def enable!
  catalog = document.catalog
  return catalog.value[:AcroForm] if catalog.value[:AcroForm]

  acroform = {
    Fields: [],
    NeedAppearances: true,
    DA: "/Helv 0 Tf 0 g",
  }
  catalog.value[:AcroForm] = acroform
  acroform
end

#field_namesObject

List of fully qualified field names (T values).



107
108
109
# File 'lib/pdfrb/document/form.rb', line 107

def field_names
  each_field.filter_map { |f| f.value[:T] }
end

#find(name) ⇒ Object

Find a field by fully qualified name.



88
89
90
# File 'lib/pdfrb/document/form.rb', line 88

def find(name)
  each_field.find { |f| f.value[:T] == name }
end

#flatten!Object

Flatten all form fields: stamp their appearance streams into page content, then remove the field from /AcroForm /Fields. After flattening, the form is read-only.



138
139
140
141
142
143
144
145
146
147
# File 'lib/pdfrb/document/form.rb', line 138

def flatten!
  each_field.to_a.each do |field|
    stamp_field_appearance(field)
    remove_field(field)
  end
  acroform = document.catalog.value[:AcroForm]
  delete_acroform_key(acroform, :Fields)
  delete_acroform_key(acroform, :NeedAppearances)
  document
end

#get_value(name) ⇒ Object?

Get a field value by name.

Parameters:

  • name (String)

    the field's /T value.

Returns:

  • (Object, nil)

    the /V value, or nil if not found.



128
129
130
131
132
133
# File 'lib/pdfrb/document/form.rb', line 128

def get_value(name)
  field = find(name)
  return nil unless field

  field.value[:V]
end

#remove_field(field) ⇒ Object

Remove a single field from /AcroForm /Fields and its widget from the page's /Annots array.



161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
# File 'lib/pdfrb/document/form.rb', line 161

def remove_field(field)
  acroform = document.catalog.value[:AcroForm]
  return nil unless acroform

  fields = acroform[:Fields]
  return nil unless fields

  field_ref = Pdfrb::Model::Reference.new(field.oid, field.gen)
  fields.delete_if { |r| r == field_ref || (r.is_a?(Pdfrb::Model::Reference) && r.oid == field.oid) }

  # Remove widget annotation from page /Annots
  page_ref = field.value[:P]
  if page_ref
    page = page_ref.is_a?(Pdfrb::Model::Reference) ? document.object(page_ref) : page_ref
    annots = page&.value&.[](:Annots)
    annots&.delete_if { |r| r == field_ref || (r.is_a?(Pdfrb::Model::Reference) && r.oid == field.oid) }
  end
  field
end

#set_value(name, value) ⇒ Pdfrb::Model::Cos::Dictionary?

Set a field value by name. Auto-regenerates the appearance stream for text/checkbox fields. For unsupported types, sets /V only.

Parameters:

  • name (String)

    the field's /T value.

  • value (Object)

    the new /V value.

Returns:



116
117
118
119
120
121
122
123
# File 'lib/pdfrb/document/form.rb', line 116

def set_value(name, value)
  field = find(name)
  return nil unless field

  field.value[:V] = normalize_value(field, value)
  regenerate_appearance(field)
  field
end