hexapdf 0.12.0 → 0.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +126 -0
- data/examples/019-acro_form.rb +41 -4
- data/lib/hexapdf/cli/command.rb +4 -2
- data/lib/hexapdf/cli/image2pdf.rb +2 -1
- data/lib/hexapdf/cli/info.rb +51 -2
- data/lib/hexapdf/cli/inspect.rb +30 -8
- data/lib/hexapdf/cli/merge.rb +1 -1
- data/lib/hexapdf/cli/split.rb +74 -14
- data/lib/hexapdf/configuration.rb +15 -0
- data/lib/hexapdf/content/graphic_object/arc.rb +3 -3
- data/lib/hexapdf/content/parser.rb +1 -1
- data/lib/hexapdf/dictionary.rb +4 -4
- data/lib/hexapdf/dictionary_fields.rb +1 -9
- data/lib/hexapdf/document.rb +41 -16
- data/lib/hexapdf/document/files.rb +0 -1
- data/lib/hexapdf/encryption/fast_arc4.rb +1 -1
- data/lib/hexapdf/encryption/security_handler.rb +1 -0
- data/lib/hexapdf/encryption/standard_security_handler.rb +1 -0
- data/lib/hexapdf/font/cmap.rb +1 -4
- data/lib/hexapdf/font/encoding/base.rb +8 -0
- data/lib/hexapdf/font/encoding/difference_encoding.rb +6 -0
- data/lib/hexapdf/font/true_type/table/head.rb +1 -0
- data/lib/hexapdf/font/true_type/table/os2.rb +2 -0
- data/lib/hexapdf/font/type1_wrapper.rb +1 -1
- data/lib/hexapdf/image_loader/png.rb +3 -2
- data/lib/hexapdf/layout/line.rb +1 -1
- data/lib/hexapdf/layout/style.rb +23 -23
- data/lib/hexapdf/layout/text_layouter.rb +2 -2
- data/lib/hexapdf/layout/text_shaper.rb +3 -2
- data/lib/hexapdf/object.rb +52 -25
- data/lib/hexapdf/parser.rb +87 -3
- data/lib/hexapdf/pdf_array.rb +11 -4
- data/lib/hexapdf/revisions.rb +29 -21
- data/lib/hexapdf/serializer.rb +1 -1
- data/lib/hexapdf/task/optimize.rb +6 -4
- data/lib/hexapdf/tokenizer.rb +4 -3
- data/lib/hexapdf/type/acro_form/appearance_generator.rb +132 -28
- data/lib/hexapdf/type/acro_form/button_field.rb +21 -13
- data/lib/hexapdf/type/acro_form/choice_field.rb +68 -14
- data/lib/hexapdf/type/acro_form/field.rb +35 -5
- data/lib/hexapdf/type/acro_form/form.rb +139 -14
- data/lib/hexapdf/type/acro_form/text_field.rb +70 -4
- data/lib/hexapdf/type/actions/uri.rb +3 -2
- data/lib/hexapdf/type/annotations/widget.rb +3 -4
- data/lib/hexapdf/type/catalog.rb +2 -2
- data/lib/hexapdf/type/cid_font.rb +1 -1
- data/lib/hexapdf/type/file_specification.rb +1 -1
- data/lib/hexapdf/type/font.rb +1 -1
- data/lib/hexapdf/type/font_simple.rb +4 -2
- data/lib/hexapdf/type/font_true_type.rb +6 -2
- data/lib/hexapdf/type/font_type0.rb +4 -4
- data/lib/hexapdf/type/form.rb +15 -2
- data/lib/hexapdf/type/image.rb +2 -2
- data/lib/hexapdf/type/page.rb +37 -13
- data/lib/hexapdf/type/page_tree_node.rb +29 -5
- data/lib/hexapdf/type/resources.rb +1 -0
- data/lib/hexapdf/type/trailer.rb +2 -3
- data/lib/hexapdf/utils/object_hash.rb +0 -1
- data/lib/hexapdf/utils/sorted_tree_node.rb +18 -15
- data/lib/hexapdf/version.rb +1 -1
- data/test/hexapdf/common_tokenizer_tests.rb +6 -1
- data/test/hexapdf/content/graphic_object/test_arc.rb +4 -4
- data/test/hexapdf/content/test_canvas.rb +3 -3
- data/test/hexapdf/content/test_color_space.rb +1 -1
- data/test/hexapdf/encryption/test_aes.rb +4 -4
- data/test/hexapdf/encryption/test_standard_security_handler.rb +11 -11
- data/test/hexapdf/filter/test_ascii85_decode.rb +1 -1
- data/test/hexapdf/filter/test_ascii_hex_decode.rb +1 -1
- data/test/hexapdf/font/encoding/test_base.rb +10 -0
- data/test/hexapdf/font/encoding/test_difference_encoding.rb +8 -0
- data/test/hexapdf/font/test_type1_wrapper.rb +4 -3
- data/test/hexapdf/layout/test_style.rb +1 -1
- data/test/hexapdf/layout/test_text_layouter.rb +12 -5
- data/test/hexapdf/test_configuration.rb +2 -2
- data/test/hexapdf/test_dictionary.rb +3 -1
- data/test/hexapdf/test_dictionary_fields.rb +2 -2
- data/test/hexapdf/test_document.rb +18 -10
- data/test/hexapdf/test_object.rb +71 -26
- data/test/hexapdf/test_parser.rb +159 -53
- data/test/hexapdf/test_pdf_array.rb +8 -1
- data/test/hexapdf/test_revisions.rb +35 -0
- data/test/hexapdf/test_writer.rb +2 -2
- data/test/hexapdf/type/acro_form/test_appearance_generator.rb +296 -38
- data/test/hexapdf/type/acro_form/test_button_field.rb +22 -2
- data/test/hexapdf/type/acro_form/test_choice_field.rb +92 -9
- data/test/hexapdf/type/acro_form/test_field.rb +39 -0
- data/test/hexapdf/type/acro_form/test_form.rb +87 -15
- data/test/hexapdf/type/acro_form/test_text_field.rb +77 -1
- data/test/hexapdf/type/test_font_simple.rb +2 -1
- data/test/hexapdf/type/test_font_true_type.rb +6 -0
- data/test/hexapdf/type/test_form.rb +26 -1
- data/test/hexapdf/type/test_page.rb +45 -7
- data/test/hexapdf/type/test_page_tree_node.rb +42 -0
- data/test/hexapdf/utils/test_bit_field.rb +2 -0
- data/test/hexapdf/utils/test_object_hash.rb +5 -0
- data/test/hexapdf/utils/test_sorted_tree_node.rb +10 -9
- data/test/test_helper.rb +2 -0
- metadata +6 -11
|
@@ -134,8 +134,8 @@ module HexaPDF
|
|
|
134
134
|
# Check boxes:: For check boxes that are in the on state the value +true+ is returned.
|
|
135
135
|
# Otherwise +false+ is returned.
|
|
136
136
|
#
|
|
137
|
-
# Radio buttons:: If no radio button is selected, +nil+ is returned. Otherwise the
|
|
138
|
-
# the specific radio button that is selected is returned.
|
|
137
|
+
# Radio buttons:: If no radio button is selected, +nil+ is returned. Otherwise the value (a
|
|
138
|
+
# Symbol) of the specific radio button that is selected is returned.
|
|
139
139
|
def field_value
|
|
140
140
|
normalized_field_value(:V)
|
|
141
141
|
end
|
|
@@ -149,7 +149,8 @@ module HexaPDF
|
|
|
149
149
|
# +false+ for unchecking it.
|
|
150
150
|
#
|
|
151
151
|
# Radio buttons:: To turn all radio buttons off, provide +nil+ as value. Otherwise provide
|
|
152
|
-
# the
|
|
152
|
+
# the value (a Symbol or an object responding to +#to_sym+) of a radio
|
|
153
|
+
# button that should be turned on.
|
|
153
154
|
def field_value=(value)
|
|
154
155
|
normalized_field_value_set(:V, value)
|
|
155
156
|
end
|
|
@@ -179,7 +180,7 @@ module HexaPDF
|
|
|
179
180
|
end
|
|
180
181
|
end
|
|
181
182
|
|
|
182
|
-
# Returns the name used for setting the check box to the on state.
|
|
183
|
+
# Returns the name (a Symbol) used for setting the check box to the on state.
|
|
183
184
|
#
|
|
184
185
|
# Defaults to :Yes if no other name could be determined.
|
|
185
186
|
def check_box_on_name
|
|
@@ -187,7 +188,8 @@ module HexaPDF
|
|
|
187
188
|
find {|key| key != :Off } || :Yes
|
|
188
189
|
end
|
|
189
190
|
|
|
190
|
-
# Returns the array of values that can be used for the field value of the radio
|
|
191
|
+
# Returns the array of Symbol values that can be used for the field value of the radio
|
|
192
|
+
# button.
|
|
191
193
|
def radio_button_values
|
|
192
194
|
each_widget.map do |widget|
|
|
193
195
|
widget.appearance&.normal_appearance&.value&.each_key&.find {|key| key != :Off }
|
|
@@ -200,8 +202,8 @@ module HexaPDF
|
|
|
200
202
|
# default appearance.
|
|
201
203
|
#
|
|
202
204
|
# If the widget is created for a radio button field, the +value+ argument needs to set to
|
|
203
|
-
# the value (a
|
|
204
|
-
# specific widget of the radio button set to on.
|
|
205
|
+
# the value (a Symbol or an object responding to +#to_sym+) this widget represents. It can
|
|
206
|
+
# be used with #field_value= to set this specific widget of the radio button set to on.
|
|
205
207
|
#
|
|
206
208
|
# See: Field#create_widget, AppearanceGenerator button field methods
|
|
207
209
|
def create_widget(page, defaults: true, value: nil, **values)
|
|
@@ -209,8 +211,11 @@ module HexaPDF
|
|
|
209
211
|
if check_box?
|
|
210
212
|
widget[:AP] = {N: {Yes: nil, Off: nil}}
|
|
211
213
|
elsif radio_button?
|
|
212
|
-
|
|
213
|
-
|
|
214
|
+
unless value.respond_to?(:to_sym)
|
|
215
|
+
raise ArgumentError, "Argument 'value' has to be provided for radio buttons " \
|
|
216
|
+
"and needs to respond to #to_sym"
|
|
217
|
+
end
|
|
218
|
+
widget[:AP] = {N: {value.to_sym => nil, Off: nil}}
|
|
214
219
|
end
|
|
215
220
|
next unless defaults
|
|
216
221
|
widget.border_style(color: 0, width: 1, style: (push_button? ? :beveled : :solid))
|
|
@@ -223,10 +228,12 @@ module HexaPDF
|
|
|
223
228
|
#
|
|
224
229
|
# The created appearance streams depend on the actual type of the button field. See
|
|
225
230
|
# AppearanceGenerator for the details.
|
|
226
|
-
|
|
231
|
+
#
|
|
232
|
+
# By setting +force+ to +true+ the creation of the appearances can be forced.
|
|
233
|
+
def create_appearances(force: false)
|
|
227
234
|
appearance_generator_class = document.config.constantize('acro_form.appearance_generator')
|
|
228
235
|
each_widget do |widget|
|
|
229
|
-
next if widget.appearance?
|
|
236
|
+
next if !force && widget.appearance?
|
|
230
237
|
if check_box?
|
|
231
238
|
appearance_generator_class.new(widget).create_check_box_appearances
|
|
232
239
|
elsif radio_button?
|
|
@@ -240,6 +247,7 @@ module HexaPDF
|
|
|
240
247
|
# Updates the widgets so that they reflect the current field value.
|
|
241
248
|
def update_widgets
|
|
242
249
|
return if push_button?
|
|
250
|
+
create_appearances
|
|
243
251
|
value = self[:V]
|
|
244
252
|
each_widget do |widget|
|
|
245
253
|
widget[:AS] = (widget.appearance&.normal_appearance&.value&.key?(value) ? value : :Off)
|
|
@@ -271,8 +279,8 @@ module HexaPDF
|
|
|
271
279
|
value == true ? check_box_on_name : :Off
|
|
272
280
|
elsif value.nil?
|
|
273
281
|
:Off
|
|
274
|
-
elsif radio_button_values.include?(value)
|
|
275
|
-
value
|
|
282
|
+
elsif radio_button_values.include?(value.to_sym)
|
|
283
|
+
value.to_sym
|
|
276
284
|
else
|
|
277
285
|
@document.config['acro_form.on_invalid_value'].call(self, value)
|
|
278
286
|
end
|
|
@@ -122,17 +122,24 @@ module HexaPDF
|
|
|
122
122
|
end
|
|
123
123
|
|
|
124
124
|
# Sets the field value to the given string or array of strings.
|
|
125
|
+
#
|
|
126
|
+
# The dictionary field /I is also modified to correctly represent the selected item(s).
|
|
125
127
|
def field_value=(value)
|
|
126
128
|
items = option_items
|
|
127
|
-
|
|
129
|
+
array_value = [value].flatten
|
|
130
|
+
all_included = array_value.all? {|v| items.include?(v) }
|
|
128
131
|
self[:V] = if (combo_box? && value.kind_of?(String) &&
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
(value.kind_of?(String) || flagged?(:multi_select)))
|
|
132
|
+
(flagged?(:edit) || all_included))
|
|
133
|
+
delete(:I)
|
|
132
134
|
value
|
|
135
|
+
elsif list_box? && all_included &&
|
|
136
|
+
(value.kind_of?(String) || flagged?(:multi_select))
|
|
137
|
+
self[:I] = array_value.map {|val| items.index(val) }.sort!
|
|
138
|
+
array_value.length == 1 ? value : array_value
|
|
133
139
|
else
|
|
134
140
|
@document.config['acro_form.on_invalid_value'].call(self, value)
|
|
135
141
|
end
|
|
142
|
+
update_widgets
|
|
136
143
|
end
|
|
137
144
|
|
|
138
145
|
# Returns the default field value.
|
|
@@ -148,20 +155,54 @@ module HexaPDF
|
|
|
148
155
|
def default_field_value=(value)
|
|
149
156
|
items = option_items
|
|
150
157
|
self[:DV] = if [value].flatten.all? {|v| items.include?(v) }
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
158
|
+
value
|
|
159
|
+
else
|
|
160
|
+
@document.config['acro_form.on_invalid_value'].call(self, value)
|
|
161
|
+
end
|
|
155
162
|
end
|
|
156
163
|
|
|
157
164
|
# Returns the array with the available option items.
|
|
165
|
+
#
|
|
166
|
+
# Note that this *only* returns the option items themselves! For getting the export values,
|
|
167
|
+
# the #export_values method has to be used.
|
|
158
168
|
def option_items
|
|
159
|
-
key?(:Opt) ? process_value(self[:Opt])
|
|
169
|
+
key?(:Opt) ? process_value(self[:Opt].map {|i| i.kind_of?(Array) ? i[1] : i }) : []
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
# Returns the export values of the option items.
|
|
173
|
+
#
|
|
174
|
+
# If you need the display strings (as in most cases), use the #option_items method.
|
|
175
|
+
def export_values
|
|
176
|
+
key?(:Opt) ? process_value(self[:Opt].map {|i| i.kind_of?(Array) ? i[0] : i }) : []
|
|
160
177
|
end
|
|
161
178
|
|
|
162
179
|
# Sets the array with the available option items to the given value.
|
|
180
|
+
#
|
|
181
|
+
# Each entry in the array may either be a string representing the text to be displayed. Or
|
|
182
|
+
# an array of two strings where the first describes the export value (to be used when
|
|
183
|
+
# exporting form field data from the document) and the second is the display value.
|
|
184
|
+
#
|
|
185
|
+
# See: #option_items, #export_values
|
|
163
186
|
def option_items=(value)
|
|
164
|
-
self[:Opt] =
|
|
187
|
+
self[:Opt] = if flagged?(:sort)
|
|
188
|
+
value.sort_by {|i| process_value(i.kind_of?(Array) ? i[1] : i) }
|
|
189
|
+
else
|
|
190
|
+
value
|
|
191
|
+
end
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
# Returns the index of the first visible option item of a list box.
|
|
195
|
+
def list_box_top_index
|
|
196
|
+
self[:TI]
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
# Makes the option item referred to via the given +index+ the first visible option item of a
|
|
200
|
+
# list box.
|
|
201
|
+
def list_box_top_index=(index)
|
|
202
|
+
if index < 0 || !key?(:Opt) || index >= self[:Opt].length
|
|
203
|
+
raise ArgumentError, "Index out of range for the set option items"
|
|
204
|
+
end
|
|
205
|
+
self[:TI] = index
|
|
165
206
|
end
|
|
166
207
|
|
|
167
208
|
# Returns the concrete choice field type, either :list_box, :combo_box or
|
|
@@ -178,19 +219,32 @@ module HexaPDF
|
|
|
178
219
|
#
|
|
179
220
|
# For information on how this is done see AppearanceGenerator.
|
|
180
221
|
#
|
|
181
|
-
# Note that
|
|
182
|
-
#
|
|
183
|
-
|
|
222
|
+
# Note that no new appearances are created if the dictionary fields involved in the creation
|
|
223
|
+
# of the appearance stream have not been changed between invocations.
|
|
224
|
+
#
|
|
225
|
+
# By setting +force+ to +true+ the creation of the appearances can be forced.
|
|
226
|
+
def create_appearances(force: false)
|
|
227
|
+
current_appearance_state = [self[:V], self[:I], self[:Opt], self[:TI]]
|
|
228
|
+
|
|
184
229
|
appearance_generator_class = document.config.constantize('acro_form.appearance_generator')
|
|
185
230
|
each_widget do |widget|
|
|
231
|
+
next if !force && widget.cached?(:appearance_state) &&
|
|
232
|
+
widget.cache(:appearance_state) == current_appearance_state
|
|
233
|
+
|
|
234
|
+
widget.cache(:appearance_state, current_appearance_state, update: true)
|
|
186
235
|
if combo_box?
|
|
187
236
|
appearance_generator_class.new(widget).create_combo_box_appearances
|
|
188
237
|
else
|
|
189
|
-
|
|
238
|
+
appearance_generator_class.new(widget).create_list_box_appearances
|
|
190
239
|
end
|
|
191
240
|
end
|
|
192
241
|
end
|
|
193
242
|
|
|
243
|
+
# Updates the widgets so that they reflect the current field value.
|
|
244
|
+
def update_widgets
|
|
245
|
+
create_appearances
|
|
246
|
+
end
|
|
247
|
+
|
|
194
248
|
private
|
|
195
249
|
|
|
196
250
|
# Uses the HexaPDF::DictionaryFields::StringConverter to process the value (a string or an
|
|
@@ -236,6 +236,11 @@ module HexaPDF
|
|
|
236
236
|
kids.nil? || kids.empty? || kids.none? {|kid| kid.key?(:T) }
|
|
237
237
|
end
|
|
238
238
|
|
|
239
|
+
# Returns +true+ if the field contains an embedded widget.
|
|
240
|
+
def embedded_widget?
|
|
241
|
+
key?(:Subtype)
|
|
242
|
+
end
|
|
243
|
+
|
|
239
244
|
# :call-seq:
|
|
240
245
|
# field.each_widget {|widget| block} -> field
|
|
241
246
|
# field.each_widget -> Enumerator
|
|
@@ -245,7 +250,7 @@ module HexaPDF
|
|
|
245
250
|
# See: HexaPDF::Type::Annotations::Widget
|
|
246
251
|
def each_widget # :yields: widget
|
|
247
252
|
return to_enum(__method__) unless block_given?
|
|
248
|
-
if
|
|
253
|
+
if embedded_widget?
|
|
249
254
|
yield(document.wrap(self))
|
|
250
255
|
elsif terminal_field?
|
|
251
256
|
self[:Kids]&.each {|kid| yield(document.wrap(kid)) }
|
|
@@ -275,9 +280,9 @@ module HexaPDF
|
|
|
275
280
|
|
|
276
281
|
widget_data = {Type: :Annot, Subtype: :Widget, Rect: [0, 0, 0, 0], **values}
|
|
277
282
|
|
|
278
|
-
if !allow_embedded ||
|
|
283
|
+
if !allow_embedded || embedded_widget? || (key?(:Kids) && !self[:Kids].empty?)
|
|
279
284
|
kids = self[:Kids] ||= []
|
|
280
|
-
kids << extract_widget if
|
|
285
|
+
kids << extract_widget if embedded_widget?
|
|
281
286
|
widget = document.add(widget_data)
|
|
282
287
|
widget[:Parent] = self
|
|
283
288
|
self[:Kids] << widget
|
|
@@ -291,6 +296,31 @@ module HexaPDF
|
|
|
291
296
|
widget
|
|
292
297
|
end
|
|
293
298
|
|
|
299
|
+
# Deletes the given widget annotation object from this field, the page it appears on and the
|
|
300
|
+
# document.
|
|
301
|
+
#
|
|
302
|
+
# If the given widget is not a widget of this field, nothing is done.
|
|
303
|
+
def delete_widget(widget)
|
|
304
|
+
widget = if embedded_widget? && self == widget
|
|
305
|
+
widget
|
|
306
|
+
elsif terminal_field?
|
|
307
|
+
(widget_index = self[:Kids]&.index(widget)) && widget
|
|
308
|
+
end
|
|
309
|
+
|
|
310
|
+
return unless widget
|
|
311
|
+
|
|
312
|
+
document.pages.each do |page|
|
|
313
|
+
break if page[:Annots]&.delete(widget) # See comment in #extract_widget
|
|
314
|
+
end
|
|
315
|
+
|
|
316
|
+
if embedded_widget?
|
|
317
|
+
WIDGET_FIELDS.each {|key| delete(key) }
|
|
318
|
+
else
|
|
319
|
+
self[:Kids].delete_at(widget_index)
|
|
320
|
+
document.delete(widget)
|
|
321
|
+
end
|
|
322
|
+
end
|
|
323
|
+
|
|
294
324
|
private
|
|
295
325
|
|
|
296
326
|
# An array of all widget annotation field names.
|
|
@@ -300,14 +330,14 @@ module HexaPDF
|
|
|
300
330
|
# directly in the field and adjust the references accordingly. If the field doesn't have any
|
|
301
331
|
# widget data, +nil+ is returned.
|
|
302
332
|
def extract_widget
|
|
303
|
-
return unless
|
|
333
|
+
return unless embedded_widget?
|
|
304
334
|
data = WIDGET_FIELDS.each_with_object({}) do |key, hash|
|
|
305
335
|
hash[key] = delete(key) if key?(key)
|
|
306
336
|
end
|
|
307
337
|
widget = document.add(data, type: :Annot)
|
|
308
338
|
widget[:Parent] = self
|
|
309
339
|
document.pages.each do |page|
|
|
310
|
-
if page.key?(:Annots) && (index = page[:Annots].index
|
|
340
|
+
if page.key?(:Annots) && (index = page[:Annots].index(self))
|
|
311
341
|
page[:Annots][index] = widget
|
|
312
342
|
break # Each annotation dictionary may only appear on one page, see PDF1.7 12.5.2
|
|
313
343
|
end
|
|
@@ -153,8 +153,83 @@ module HexaPDF
|
|
|
153
153
|
#
|
|
154
154
|
# The +name+ may contain dots to signify a field hierarchy. If so, the referenced parent
|
|
155
155
|
# fields must already exist. If it doesn't contain dots, a top-level field is created.
|
|
156
|
-
|
|
157
|
-
|
|
156
|
+
#
|
|
157
|
+
# The optional keyword arguments allow setting often used properties of the field:
|
|
158
|
+
#
|
|
159
|
+
# +font+::
|
|
160
|
+
# The font that should be used for the text of the field. If +font_size+ is specified
|
|
161
|
+
# but +font+ isn't, the font Helvetica is used.
|
|
162
|
+
#
|
|
163
|
+
# +font_size+::
|
|
164
|
+
# The font size that should be used. If +font+ is specified but +font_size+ isn't, font
|
|
165
|
+
# size defaults to 0 (= auto-sizing).
|
|
166
|
+
#
|
|
167
|
+
# +align+::
|
|
168
|
+
# The alignment of the text, either :left, :center or :right.
|
|
169
|
+
def create_text_field(name, font: nil, font_size: nil, align: nil)
|
|
170
|
+
create_field(name, :Tx) do |field|
|
|
171
|
+
apply_variable_text_properties(field, font: font, font_size: font_size, align: align)
|
|
172
|
+
end
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
# Creates a new multiline text field with the given name and adds it to the form.
|
|
176
|
+
#
|
|
177
|
+
# The +name+ may contain dots to signify a field hierarchy. If so, the referenced parent
|
|
178
|
+
# fields must already exist. If it doesn't contain dots, a top-level field is created.
|
|
179
|
+
#
|
|
180
|
+
# The optional keyword arguments allow setting often used properties of the field, see
|
|
181
|
+
# #create_text_field for details.
|
|
182
|
+
def create_multiline_text_field(name, font: nil, font_size: nil, align: nil)
|
|
183
|
+
create_field(name, :Tx) do |field|
|
|
184
|
+
field.initialize_as_multiline_text_field
|
|
185
|
+
apply_variable_text_properties(field, font: font, font_size: font_size, align: align)
|
|
186
|
+
end
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
# Creates a new comb text field with the given name and adds it to the form.
|
|
190
|
+
#
|
|
191
|
+
# The +max_chars+ argument defines the maximum number of characters the comb text field can
|
|
192
|
+
# accommodate.
|
|
193
|
+
#
|
|
194
|
+
# The +name+ may contain dots to signify a field hierarchy. If so, the referenced parent
|
|
195
|
+
# fields must already exist. If it doesn't contain dots, a top-level field is created.
|
|
196
|
+
#
|
|
197
|
+
# The optional keyword arguments allow setting often used properties of the field, see
|
|
198
|
+
# #create_text_field for details.
|
|
199
|
+
def create_comb_text_field(name, max_chars:, font: nil, font_size: nil, align: nil)
|
|
200
|
+
create_field(name, :Tx) do |field|
|
|
201
|
+
field.initialize_as_comb_text_field
|
|
202
|
+
apply_variable_text_properties(field, font: font, font_size: font_size, align: align)
|
|
203
|
+
field[:MaxLen] = max_chars
|
|
204
|
+
end
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
# Creates a new file select field with the given name and adds it to the form.
|
|
208
|
+
#
|
|
209
|
+
# The +name+ may contain dots to signify a field hierarchy. If so, the referenced parent
|
|
210
|
+
# fields must already exist. If it doesn't contain dots, a top-level field is created.
|
|
211
|
+
#
|
|
212
|
+
# The optional keyword arguments allow setting often used properties of the field, see
|
|
213
|
+
# #create_text_field for details.
|
|
214
|
+
def create_file_select_field(name, font: nil, font_size: nil, align: nil)
|
|
215
|
+
create_field(name, :Tx) do |field|
|
|
216
|
+
field.initialize_as_file_select_field
|
|
217
|
+
apply_variable_text_properties(field, font: font, font_size: font_size, align: align)
|
|
218
|
+
end
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
# Creates a new password field with the given name and adds it to the form.
|
|
222
|
+
#
|
|
223
|
+
# The +name+ may contain dots to signify a field hierarchy. If so, the referenced parent
|
|
224
|
+
# fields must already exist. If it doesn't contain dots, a top-level field is created.
|
|
225
|
+
#
|
|
226
|
+
# The optional keyword arguments allow setting often used properties of the field, see
|
|
227
|
+
# #create_text_field for details.
|
|
228
|
+
def create_password_field(name, font: nil, font_size: nil, align: nil)
|
|
229
|
+
create_field(name, :Tx) do |field|
|
|
230
|
+
field.initialize_as_password_field
|
|
231
|
+
apply_variable_text_properties(field, font: font, font_size: font_size, align: align)
|
|
232
|
+
end
|
|
158
233
|
end
|
|
159
234
|
|
|
160
235
|
# Creates a new check box with the given name and adds it to the form.
|
|
@@ -162,7 +237,7 @@ module HexaPDF
|
|
|
162
237
|
# The +name+ may contain dots to signify a field hierarchy. If so, the referenced parent
|
|
163
238
|
# fields must already exist. If it doesn't contain dots, a top-level field is created.
|
|
164
239
|
def create_check_box(name)
|
|
165
|
-
create_field(name, :Btn
|
|
240
|
+
create_field(name, :Btn, &:initialize_as_check_box)
|
|
166
241
|
end
|
|
167
242
|
|
|
168
243
|
# Creates a radio button with the given name and adds it to the form.
|
|
@@ -170,28 +245,64 @@ module HexaPDF
|
|
|
170
245
|
# The +name+ may contain dots to signify a field hierarchy. If so, the referenced parent
|
|
171
246
|
# fields must already exist. If it doesn't contain dots, a top-level field is created.
|
|
172
247
|
def create_radio_button(name)
|
|
173
|
-
create_field(name, :Btn
|
|
248
|
+
create_field(name, :Btn, &:initialize_as_radio_button)
|
|
174
249
|
end
|
|
175
250
|
|
|
176
251
|
# Creates a combo box with the given name and adds it to the form.
|
|
177
252
|
#
|
|
178
253
|
# The +name+ may contain dots to signify a field hierarchy. If so, the referenced parent
|
|
179
254
|
# fields must already exist. If it doesn't contain dots, a top-level field is created.
|
|
180
|
-
|
|
181
|
-
|
|
255
|
+
#
|
|
256
|
+
# The optional keyword arguments allow setting often used properties of the field:
|
|
257
|
+
#
|
|
258
|
+
# +option_items+::
|
|
259
|
+
# Specifies the values of the list box.
|
|
260
|
+
#
|
|
261
|
+
# +editable+::
|
|
262
|
+
# If set to +true+, the combo box allows entering an arbitrary value in addition to
|
|
263
|
+
# selecting one of the provided option items.
|
|
264
|
+
#
|
|
265
|
+
# +font+, +font_size+ and +align+::
|
|
266
|
+
# See #create_text_field
|
|
267
|
+
def create_combo_box(name, option_items: nil, editable: nil, font: nil, font_size: nil,
|
|
268
|
+
align: nil)
|
|
269
|
+
create_field(name, :Ch) do |field|
|
|
270
|
+
field.initialize_as_combo_box
|
|
271
|
+
field.option_items = option_items if option_items
|
|
272
|
+
field.flag(:edit) if editable
|
|
273
|
+
apply_variable_text_properties(field, font: font, font_size: font_size, align: align)
|
|
274
|
+
end
|
|
182
275
|
end
|
|
183
276
|
|
|
184
277
|
# Creates a list box with the given name and adds it to the form.
|
|
185
278
|
#
|
|
186
279
|
# The +name+ may contain dots to signify a field hierarchy. If so, the referenced parent
|
|
187
280
|
# fields must already exist. If it doesn't contain dots, a top-level field is created.
|
|
188
|
-
|
|
189
|
-
|
|
281
|
+
#
|
|
282
|
+
# The optional keyword arguments allow setting often used properties of the field:
|
|
283
|
+
#
|
|
284
|
+
# +option_items+::
|
|
285
|
+
# Specifies the values of the list box.
|
|
286
|
+
#
|
|
287
|
+
# +multi_select+::
|
|
288
|
+
# If set to +true+, the list box allows selecting multiple items instead of only one.
|
|
289
|
+
#
|
|
290
|
+
# +font+, +font_size+ and +align+::
|
|
291
|
+
# See #create_text_field.
|
|
292
|
+
def create_list_box(name, option_items: nil, multi_select: nil, font: nil, font_size: nil,
|
|
293
|
+
align: nil)
|
|
294
|
+
create_field(name, :Ch) do |field|
|
|
295
|
+
field.initialize_as_list_box
|
|
296
|
+
field.option_items = option_items if option_items
|
|
297
|
+
field.flag(:multi_select) if multi_select
|
|
298
|
+
apply_variable_text_properties(field, font: font, font_size: font_size, align: align)
|
|
299
|
+
end
|
|
190
300
|
end
|
|
191
301
|
|
|
192
302
|
# Returns the dictionary containing the default resources for form field appearance streams.
|
|
193
303
|
def default_resources
|
|
194
|
-
self[:DR] ||= document.wrap({
|
|
304
|
+
self[:DR] ||= document.wrap({ProcSet: [:PDF, :Text, :ImageB, :ImageC, :ImageI]},
|
|
305
|
+
type: :XXResources)
|
|
195
306
|
end
|
|
196
307
|
|
|
197
308
|
# Sets the global default appearance string using the provided values.
|
|
@@ -212,9 +323,11 @@ module HexaPDF
|
|
|
212
323
|
end
|
|
213
324
|
|
|
214
325
|
# Creates the appearances for all widgets of all terminal fields if they don't exist.
|
|
215
|
-
|
|
326
|
+
#
|
|
327
|
+
# If +force+ is +true+, new appearances are created even if there are existing ones.
|
|
328
|
+
def create_appearances(force: false)
|
|
216
329
|
each_field do |field|
|
|
217
|
-
field.create_appearances if field.respond_to?(:create_appearances)
|
|
330
|
+
field.create_appearances(force: force) if field.respond_to?(:create_appearances)
|
|
218
331
|
end
|
|
219
332
|
end
|
|
220
333
|
|
|
@@ -245,18 +358,30 @@ module HexaPDF
|
|
|
245
358
|
else
|
|
246
359
|
(self[:Fields] ||= []) << field
|
|
247
360
|
end
|
|
361
|
+
|
|
362
|
+
yield(field)
|
|
363
|
+
|
|
248
364
|
field
|
|
249
365
|
end
|
|
250
366
|
|
|
367
|
+
# Applies the given variable field properties to the field.
|
|
368
|
+
def apply_variable_text_properties(field, font: nil, font_size: nil, align: nil)
|
|
369
|
+
if font || font_size
|
|
370
|
+
field.set_default_appearance_string(font: font || 'Helvetica', font_size: font_size || 0)
|
|
371
|
+
end
|
|
372
|
+
field.text_alignment(align) if align
|
|
373
|
+
end
|
|
374
|
+
|
|
251
375
|
def perform_validation # :nodoc:
|
|
376
|
+
super
|
|
377
|
+
|
|
252
378
|
if (da = self[:DA])
|
|
253
379
|
unless self[:DR]
|
|
254
380
|
yield("When the field /DA is present, the field /DR must also be present")
|
|
381
|
+
return
|
|
255
382
|
end
|
|
256
383
|
font_name = nil
|
|
257
|
-
HexaPDF::Content::Parser.parse(da)
|
|
258
|
-
font_name = params[0] if obj == :Tf
|
|
259
|
-
end
|
|
384
|
+
HexaPDF::Content::Parser.parse(da) {|obj, params| font_name = params[0] if obj == :Tf }
|
|
260
385
|
if font_name && !(self[:DR][:Font] && self[:DR][:Font][font_name])
|
|
261
386
|
yield("The font specified in /DA is not in the /DR resource dictionary")
|
|
262
387
|
end
|