hexapdf 0.41.0 → 0.43.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 +55 -0
- data/Rakefile +1 -1
- data/examples/031-acro_form_java_script.rb +36 -24
- data/lib/hexapdf/cli/command.rb +14 -11
- data/lib/hexapdf/cli/files.rb +31 -7
- data/lib/hexapdf/cli/form.rb +10 -31
- data/lib/hexapdf/cli/inspect.rb +1 -1
- data/lib/hexapdf/cli/usage.rb +215 -0
- data/lib/hexapdf/cli.rb +2 -0
- data/lib/hexapdf/configuration.rb +1 -1
- data/lib/hexapdf/dictionary.rb +3 -3
- data/lib/hexapdf/document.rb +14 -1
- data/lib/hexapdf/encryption.rb +17 -0
- data/lib/hexapdf/layout/box.rb +1 -0
- data/lib/hexapdf/layout/box_fitter.rb +3 -3
- data/lib/hexapdf/layout/column_box.rb +2 -2
- data/lib/hexapdf/layout/container_box.rb +1 -1
- data/lib/hexapdf/layout/line.rb +4 -0
- data/lib/hexapdf/layout/list_box.rb +2 -2
- data/lib/hexapdf/layout/table_box.rb +1 -1
- data/lib/hexapdf/layout/text_box.rb +16 -2
- data/lib/hexapdf/parser.rb +20 -17
- data/lib/hexapdf/type/acro_form/button_field.rb +7 -5
- data/lib/hexapdf/type/acro_form/form.rb +123 -27
- data/lib/hexapdf/type/acro_form/java_script_actions.rb +165 -14
- data/lib/hexapdf/type/acro_form/text_field.rb +13 -1
- data/lib/hexapdf/type/resources.rb +2 -1
- data/lib/hexapdf/utils.rb +19 -0
- data/lib/hexapdf/version.rb +1 -1
- data/test/hexapdf/layout/test_box_fitter.rb +3 -3
- data/test/hexapdf/layout/test_text_box.rb +27 -1
- data/test/hexapdf/test_dictionary.rb +6 -4
- data/test/hexapdf/test_parser.rb +12 -0
- data/test/hexapdf/test_utils.rb +16 -0
- data/test/hexapdf/type/acro_form/test_button_field.rb +5 -0
- data/test/hexapdf/type/acro_form/test_form.rb +110 -2
- data/test/hexapdf/type/acro_form/test_java_script_actions.rb +102 -1
- data/test/hexapdf/type/acro_form/test_text_field.rb +22 -4
- data/test/hexapdf/type/test_resources.rb +5 -0
- metadata +3 -2
data/lib/hexapdf/document.rb
CHANGED
@@ -278,6 +278,14 @@ module HexaPDF
|
|
278
278
|
# If the same argument is provided in multiple invocations, the import is done only once and
|
279
279
|
# the previously imported object is returned.
|
280
280
|
#
|
281
|
+
# Note: If you first create a PDF document from scratch and then want to import objects from it
|
282
|
+
# into another PDF document, you need to run the following on the source document:
|
283
|
+
#
|
284
|
+
# doc.dispatch_message(:complete_objects)
|
285
|
+
# doc.validate
|
286
|
+
#
|
287
|
+
# This ensures that the source document has all the necessary PDF structures set-up correctly.
|
288
|
+
#
|
281
289
|
# See: Importer
|
282
290
|
def import(obj)
|
283
291
|
source = (obj.kind_of?(HexaPDF::Object) ? obj.document : nil)
|
@@ -617,13 +625,18 @@ module HexaPDF
|
|
617
625
|
# writing the document.
|
618
626
|
#
|
619
627
|
# The security handler used for encrypting is selected via the +name+ argument. All other
|
620
|
-
# arguments are passed on the security handler.
|
628
|
+
# arguments are passed on to the security handler.
|
621
629
|
#
|
622
630
|
# If the document should not be encrypted, the +name+ argument has to be set to +nil+. This
|
623
631
|
# removes the security handler and deletes the trailer's Encrypt dictionary.
|
624
632
|
#
|
625
633
|
# See: Encryption::SecurityHandler#set_up_encryption and
|
626
634
|
# Encryption::StandardSecurityHandler::EncryptionOptions for possible encryption options.
|
635
|
+
#
|
636
|
+
# Examples:
|
637
|
+
#
|
638
|
+
# document.encrypt(name: nil) # remove the existing encryption
|
639
|
+
# document.encrypt(algorithm: :aes, key_length: 256, permissions: [:print, :extract_content]
|
627
640
|
def encrypt(name: :Standard, **options)
|
628
641
|
if name.nil?
|
629
642
|
trailer.delete(:Encrypt)
|
data/lib/hexapdf/encryption.rb
CHANGED
@@ -46,6 +46,23 @@ module HexaPDF
|
|
46
46
|
#
|
47
47
|
# This module contains all encryption and security related code to facilitate PDF encryption.
|
48
48
|
#
|
49
|
+
# === Working With Encrypted Documents
|
50
|
+
#
|
51
|
+
# When a PDF document is opened, an encryption password can be specified. This is necessary if a
|
52
|
+
# user password is set on the file and optional otherwise (because the default password is
|
53
|
+
# automatically tried):
|
54
|
+
#
|
55
|
+
# HexaPDF::Document.open(filename, decryption_opts: {password: 'somepassword'}) do |doc|
|
56
|
+
# end
|
57
|
+
#
|
58
|
+
# To remove the encryption from a PDF document, use the following:
|
59
|
+
#
|
60
|
+
# document.encrypt(name: nil)
|
61
|
+
#
|
62
|
+
# To encrypt a PDF document, use the same method but specify the required encryption options:
|
63
|
+
#
|
64
|
+
# document.encrypt(algorithm: :aes, key_length: 256)
|
65
|
+
#
|
49
66
|
#
|
50
67
|
# === Security Handlers
|
51
68
|
#
|
data/lib/hexapdf/layout/box.rb
CHANGED
@@ -47,8 +47,8 @@ module HexaPDF
|
|
47
47
|
#
|
48
48
|
# * Then use the #fit method to fit boxes one after the other. No drawing is done.
|
49
49
|
#
|
50
|
-
# * Once all boxes have been fitted, the #fit_results, #remaining_boxes and #
|
51
|
-
#
|
50
|
+
# * Once all boxes have been fitted, the #fit_results, #remaining_boxes and #success? methods
|
51
|
+
# can be used to get the result:
|
52
52
|
#
|
53
53
|
# - If there are no remaining boxes, all boxes were successfully fitted into the frames.
|
54
54
|
# - If there are remaining boxes but no fit results, the first box could not be fitted.
|
@@ -126,7 +126,7 @@ module HexaPDF
|
|
126
126
|
end
|
127
127
|
|
128
128
|
# Returns +true+ if all boxes were successfully fitted.
|
129
|
-
def
|
129
|
+
def success?
|
130
130
|
@remaining_boxes.empty?
|
131
131
|
end
|
132
132
|
|
@@ -186,7 +186,7 @@ module HexaPDF
|
|
186
186
|
|
187
187
|
children.each {|box| @box_fitter.fit(box) }
|
188
188
|
|
189
|
-
fit_successful = @box_fitter.
|
189
|
+
fit_successful = @box_fitter.success?
|
190
190
|
initial_fit_successful = fit_successful if initial_fit_successful.nil?
|
191
191
|
|
192
192
|
if fit_successful
|
@@ -211,7 +211,7 @@ module HexaPDF
|
|
211
211
|
@draw_pos_x = frame.x + reserved_width_left
|
212
212
|
@draw_pos_y = frame.y - @height + reserved_height_bottom
|
213
213
|
|
214
|
-
@box_fitter.
|
214
|
+
@box_fitter.success?
|
215
215
|
end
|
216
216
|
|
217
217
|
private
|
@@ -137,7 +137,7 @@ module HexaPDF
|
|
137
137
|
@box_fitter = BoxFitter.new([my_frame])
|
138
138
|
children.each {|box| @box_fitter.fit(box) }
|
139
139
|
|
140
|
-
if @box_fitter.
|
140
|
+
if @box_fitter.success?
|
141
141
|
update_content_width do
|
142
142
|
result = @box_fitter.fit_results.max_by {|r| r.mask.x + r.mask.width }
|
143
143
|
children.empty? ? 0 : result.mask.x + result.mask.width - my_frame.left
|
data/lib/hexapdf/layout/line.rb
CHANGED
@@ -173,6 +173,10 @@ module HexaPDF
|
|
173
173
|
attr_accessor :items
|
174
174
|
|
175
175
|
# An optional horizontal offset that should be taken into account when positioning the line.
|
176
|
+
#
|
177
|
+
# This offset always describes the offset from the left side (and not, for example, the offset
|
178
|
+
# from the right side of another line even if those two lines are actually on the same
|
179
|
+
# horizontal level).
|
176
180
|
attr_accessor :x_offset
|
177
181
|
|
178
182
|
# An optional vertical offset that should be taken into account when positioning the line.
|
@@ -248,14 +248,14 @@ module HexaPDF
|
|
248
248
|
top -= item_result.height + item_spacing
|
249
249
|
height -= item_result.height + item_spacing
|
250
250
|
|
251
|
-
break if !box_fitter.
|
251
|
+
break if !box_fitter.success? || height <= 0
|
252
252
|
end
|
253
253
|
|
254
254
|
@height = @results.sum(&:height) + (@results.count - 1) * item_spacing + reserved_height
|
255
255
|
|
256
256
|
@draw_pos_x = frame.x + reserved_width_left
|
257
257
|
@draw_pos_y = frame.y - @height + reserved_height_bottom
|
258
|
-
@all_items_fitted = @results.all? {|r| r.box_fitter.
|
258
|
+
@all_items_fitted = @results.all? {|r| r.box_fitter.success? } &&
|
259
259
|
@results.size == @children.size
|
260
260
|
@fit_successful = @all_items_fitted || (@initial_height > 0 && style.overflow == :truncate)
|
261
261
|
end
|
@@ -233,7 +233,7 @@ module HexaPDF
|
|
233
233
|
@preferred_width = max_x_result.x + max_x_result.box.width + reserved_width
|
234
234
|
@height = @preferred_height = box_fitter.content_heights[0] + reserved_height
|
235
235
|
@fit_results = box_fitter.fit_results
|
236
|
-
@fit_successful = box_fitter.
|
236
|
+
@fit_successful = box_fitter.success?
|
237
237
|
else
|
238
238
|
@preferred_width = reserved_width
|
239
239
|
@height = @preferred_height = reserved_height
|
@@ -52,6 +52,7 @@ module HexaPDF
|
|
52
52
|
@tl = TextLayouter.new(style)
|
53
53
|
@items = items
|
54
54
|
@result = nil
|
55
|
+
@x_offset = 0
|
55
56
|
end
|
56
57
|
|
57
58
|
# Returns the text that will be drawn.
|
@@ -80,7 +81,7 @@ module HexaPDF
|
|
80
81
|
(@initial_height > 0 && @initial_height > available_height)
|
81
82
|
|
82
83
|
frame = frame.child_frame(box: self)
|
83
|
-
@width = @height = 0
|
84
|
+
@width = @x_offset = @height = 0
|
84
85
|
@result = if style.position == :flow
|
85
86
|
@tl.fit(@items, frame.width_specification, frame.shape.bbox.height,
|
86
87
|
apply_first_text_indent: !split_box?, frame: frame)
|
@@ -93,6 +94,14 @@ module HexaPDF
|
|
93
94
|
end
|
94
95
|
@width += if @initial_width > 0 || style.text_align == :center || style.text_align == :right
|
95
96
|
width
|
97
|
+
elsif style.position == :flow
|
98
|
+
min_x = +Float::INFINITY
|
99
|
+
max_x = -Float::INFINITY
|
100
|
+
@result.lines.each do |line|
|
101
|
+
min_x = [min_x, line.x_offset].min
|
102
|
+
max_x = [max_x, line.x_offset + line.width].max
|
103
|
+
end
|
104
|
+
min_x.finite? ? (@x_offset = min_x; max_x - min_x) : 0
|
96
105
|
else
|
97
106
|
@result.lines.max_by(&:width)&.width || 0
|
98
107
|
end
|
@@ -125,6 +134,11 @@ module HexaPDF
|
|
125
134
|
end
|
126
135
|
end
|
127
136
|
|
137
|
+
# :nodoc:
|
138
|
+
def draw(canvas, x, y)
|
139
|
+
super(canvas, x + @x_offset, y)
|
140
|
+
end
|
141
|
+
|
128
142
|
# :nodoc:
|
129
143
|
def empty?
|
130
144
|
super && (!@result || @result.lines.empty?)
|
@@ -142,7 +156,7 @@ module HexaPDF
|
|
142
156
|
end
|
143
157
|
|
144
158
|
return if @result.lines.empty?
|
145
|
-
@result.draw(canvas, x, y + content_height)
|
159
|
+
@result.draw(canvas, x - @x_offset, y + content_height)
|
146
160
|
end
|
147
161
|
|
148
162
|
# Creates a new TextBox instance for the items remaining after fitting the box.
|
data/lib/hexapdf/parser.rb
CHANGED
@@ -362,29 +362,32 @@ module HexaPDF
|
|
362
362
|
pos = @io.pos
|
363
363
|
lines = @io.read(step_size + 40).split(/[\r\n]+/)
|
364
364
|
|
365
|
-
|
366
|
-
|
367
|
-
|
368
|
-
|
369
|
-
|
370
|
-
|
371
|
-
|
372
|
-
|
373
|
-
|
374
|
-
|
375
|
-
|
376
|
-
|
365
|
+
# Need to iterate through the whole lines array in case there are multiple %%EOF to try
|
366
|
+
eof_index = 0
|
367
|
+
while (eof_index = lines[0..(eof_index - 1)].rindex {|l| l.strip == '%%EOF' })
|
368
|
+
if lines[eof_index - 1].strip =~ /\Astartxref\s(\d+)\z/
|
369
|
+
startxref_offset = $1.to_i
|
370
|
+
startxref_mangled = true
|
371
|
+
break # we found it even if it the syntax is not entirely correct
|
372
|
+
elsif eof_index < 2 || lines[eof_index - 2].strip != "startxref"
|
373
|
+
startxref_missing = true
|
374
|
+
else
|
375
|
+
startxref_offset = lines[eof_index - 1].to_i
|
376
|
+
break # we found it
|
377
|
+
end
|
377
378
|
end
|
379
|
+
eof_not_found ||= !eof_index
|
380
|
+
break if startxref_offset
|
378
381
|
end
|
379
382
|
|
380
|
-
if
|
381
|
-
maybe_raise("PDF file trailer with end-of-file marker not found", pos: pos,
|
382
|
-
force: !eof_index)
|
383
|
-
elsif startxref_mangled
|
383
|
+
if startxref_mangled
|
384
384
|
maybe_raise("PDF file trailer keyword startxref on same line as value", pos: pos)
|
385
385
|
elsif startxref_missing
|
386
386
|
maybe_raise("PDF file trailer is missing startxref keyword", pos: pos,
|
387
|
-
force:
|
387
|
+
force: !startxref_offset)
|
388
|
+
elsif eof_not_found
|
389
|
+
maybe_raise("PDF file trailer with end-of-file marker not found", pos: pos,
|
390
|
+
force: !startxref_offset)
|
388
391
|
end
|
389
392
|
|
390
393
|
@startxref_offset = startxref_offset
|
@@ -165,14 +165,16 @@ module HexaPDF
|
|
165
165
|
# nothing is stored for them (e.g a no-op).
|
166
166
|
#
|
167
167
|
# Check boxes:: Provide +nil+ or +false+ as value to toggle all check box widgets off. If
|
168
|
-
#
|
169
|
-
#
|
170
|
-
#
|
171
|
-
# toggled on.
|
168
|
+
# +true+ is provided, all check box widgets with the same name as the first
|
169
|
+
# one are toggled on. Otherwise provide the value (a Symbol or an object
|
170
|
+
# responding to +#to_sym+) of the check box widget that should be toggled on.
|
172
171
|
#
|
173
172
|
# Radio buttons:: To turn all radio buttons off, provide +nil+ as value. Otherwise provide
|
174
173
|
# the value (a Symbol or an object responding to +#to_sym+) of a radio
|
175
174
|
# button that should be turned on.
|
175
|
+
#
|
176
|
+
# Note that in most cases the field needs to already have widgets because the value is
|
177
|
+
# checked against the possibly allowed values which depend on the existing widgets.
|
176
178
|
def field_value=(value)
|
177
179
|
normalized_field_value_set(:V, value)
|
178
180
|
end
|
@@ -303,7 +305,7 @@ module HexaPDF
|
|
303
305
|
elsif check_box?
|
304
306
|
if value == false
|
305
307
|
:Off
|
306
|
-
elsif value == true && av.size
|
308
|
+
elsif value == true && av.size >= 1
|
307
309
|
av[0]
|
308
310
|
elsif av.include?(value.to_sym)
|
309
311
|
value.to_sym
|
@@ -163,10 +163,21 @@ module HexaPDF
|
|
163
163
|
field
|
164
164
|
end
|
165
165
|
|
166
|
+
# Creates an untyped namespace field for creating hierarchies.
|
167
|
+
#
|
168
|
+
# Example:
|
169
|
+
#
|
170
|
+
# form.create_namespace_field('text')
|
171
|
+
# form.create_text_field('text.a1')
|
172
|
+
def create_namespace_field(name)
|
173
|
+
create_field(name)
|
174
|
+
end
|
175
|
+
|
166
176
|
# Creates a new text field with the given name and adds it to the form.
|
167
177
|
#
|
168
|
-
# The +name+ may contain dots to signify a field hierarchy. If
|
169
|
-
#
|
178
|
+
# The +name+ may contain dots to signify a field hierarchy. If the parent fields don't
|
179
|
+
# already exist, they are created as pure namespace fields (see #create_namespace_field). If
|
180
|
+
# the +name+ doesn't contain dots, a top-level field is created.
|
170
181
|
#
|
171
182
|
# The optional keyword arguments allow setting often used properties of the field:
|
172
183
|
#
|
@@ -202,8 +213,9 @@ module HexaPDF
|
|
202
213
|
|
203
214
|
# Creates a new multiline text field with the given name and adds it to the form.
|
204
215
|
#
|
205
|
-
# The +name+ may contain dots to signify a field hierarchy. If
|
206
|
-
#
|
216
|
+
# The +name+ may contain dots to signify a field hierarchy. If the parent fields don't
|
217
|
+
# already exist, they are created as pure namespace fields (see #create_namespace_field). If
|
218
|
+
# the +name+ doesn't contain dots, a top-level field is created.
|
207
219
|
#
|
208
220
|
# The optional keyword arguments allow setting often used properties of the field, see
|
209
221
|
# #create_text_field for details.
|
@@ -221,8 +233,9 @@ module HexaPDF
|
|
221
233
|
# The +max_chars+ argument defines the maximum number of characters the comb text field can
|
222
234
|
# accommodate.
|
223
235
|
#
|
224
|
-
# The +name+ may contain dots to signify a field hierarchy. If
|
225
|
-
#
|
236
|
+
# The +name+ may contain dots to signify a field hierarchy. If the parent fields don't
|
237
|
+
# already exist, they are created as pure namespace fields (see #create_namespace_field). If
|
238
|
+
# the +name+ doesn't contain dots, a top-level field is created.
|
226
239
|
#
|
227
240
|
# The optional keyword arguments allow setting often used properties of the field, see
|
228
241
|
# #create_text_field for details.
|
@@ -238,8 +251,9 @@ module HexaPDF
|
|
238
251
|
|
239
252
|
# Creates a new file select field with the given name and adds it to the form.
|
240
253
|
#
|
241
|
-
# The +name+ may contain dots to signify a field hierarchy. If
|
242
|
-
#
|
254
|
+
# The +name+ may contain dots to signify a field hierarchy. If the parent fields don't
|
255
|
+
# already exist, they are created as pure namespace fields (see #create_namespace_field). If
|
256
|
+
# the +name+ doesn't contain dots, a top-level field is created.
|
243
257
|
#
|
244
258
|
# The optional keyword arguments allow setting often used properties of the field, see
|
245
259
|
# #create_text_field for details.
|
@@ -254,8 +268,9 @@ module HexaPDF
|
|
254
268
|
|
255
269
|
# Creates a new password field with the given name and adds it to the form.
|
256
270
|
#
|
257
|
-
# The +name+ may contain dots to signify a field hierarchy. If
|
258
|
-
#
|
271
|
+
# The +name+ may contain dots to signify a field hierarchy. If the parent fields don't
|
272
|
+
# already exist, they are created as pure namespace fields (see #create_namespace_field). If
|
273
|
+
# the +name+ doesn't contain dots, a top-level field is created.
|
259
274
|
#
|
260
275
|
# The optional keyword arguments allow setting often used properties of the field, see
|
261
276
|
# #create_text_field for details.
|
@@ -270,24 +285,33 @@ module HexaPDF
|
|
270
285
|
|
271
286
|
# Creates a new check box with the given name and adds it to the form.
|
272
287
|
#
|
273
|
-
# The +name+ may contain dots to signify a field hierarchy. If
|
274
|
-
#
|
288
|
+
# The +name+ may contain dots to signify a field hierarchy. If the parent fields don't
|
289
|
+
# already exist, they are created as pure namespace fields (see #create_namespace_field). If
|
290
|
+
# the +name+ doesn't contain dots, a top-level field is created.
|
291
|
+
#
|
292
|
+
# Before a field value other than +false+ can be assigned to the check box, a widget needs
|
293
|
+
# to be created.
|
275
294
|
def create_check_box(name)
|
276
295
|
create_field(name, :Btn, &:initialize_as_check_box)
|
277
296
|
end
|
278
297
|
|
279
298
|
# Creates a radio button with the given name and adds it to the form.
|
280
299
|
#
|
281
|
-
# The +name+ may contain dots to signify a field hierarchy. If
|
282
|
-
#
|
300
|
+
# The +name+ may contain dots to signify a field hierarchy. If the parent fields don't
|
301
|
+
# already exist, they are created as pure namespace fields (see #create_namespace_field). If
|
302
|
+
# the +name+ doesn't contain dots, a top-level field is created.
|
303
|
+
#
|
304
|
+
# Before a field value other than +nil+ can be assigned to the radio button, at least one
|
305
|
+
# widget needs to be created.
|
283
306
|
def create_radio_button(name)
|
284
307
|
create_field(name, :Btn, &:initialize_as_radio_button)
|
285
308
|
end
|
286
309
|
|
287
310
|
# Creates a combo box with the given name and adds it to the form.
|
288
311
|
#
|
289
|
-
# The +name+ may contain dots to signify a field hierarchy. If
|
290
|
-
#
|
312
|
+
# The +name+ may contain dots to signify a field hierarchy. If the parent fields don't
|
313
|
+
# already exist, they are created as pure namespace fields (see #create_namespace_field). If
|
314
|
+
# the +name+ doesn't contain dots, a top-level field is created.
|
291
315
|
#
|
292
316
|
# The optional keyword arguments allow setting often used properties of the field:
|
293
317
|
#
|
@@ -313,8 +337,9 @@ module HexaPDF
|
|
313
337
|
|
314
338
|
# Creates a list box with the given name and adds it to the form.
|
315
339
|
#
|
316
|
-
# The +name+ may contain dots to signify a field hierarchy. If
|
317
|
-
#
|
340
|
+
# The +name+ may contain dots to signify a field hierarchy. If the parent fields don't
|
341
|
+
# already exist, they are created as pure namespace fields (see #create_namespace_field). If
|
342
|
+
# the +name+ doesn't contain dots, a top-level field is created.
|
318
343
|
#
|
319
344
|
# The optional keyword arguments allow setting often used properties of the field:
|
320
345
|
#
|
@@ -339,10 +364,77 @@ module HexaPDF
|
|
339
364
|
|
340
365
|
# Creates a signature field with the given name and adds it to the form.
|
341
366
|
#
|
342
|
-
# The +name+ may contain dots to signify a field hierarchy. If
|
343
|
-
#
|
367
|
+
# The +name+ may contain dots to signify a field hierarchy. If the parent fields don't
|
368
|
+
# already exist, they are created as pure namespace fields (see #create_namespace_field). If
|
369
|
+
# the +name+ doesn't contain dots, a top-level field is created.
|
344
370
|
def create_signature_field(name)
|
345
|
-
create_field(name, :Sig)
|
371
|
+
create_field(name, :Sig)
|
372
|
+
end
|
373
|
+
|
374
|
+
# :call-seq:
|
375
|
+
# form.delete_field(name)
|
376
|
+
# form.delete_field(field)
|
377
|
+
#
|
378
|
+
# Deletes the field specified by the given name or via the given field object.
|
379
|
+
#
|
380
|
+
# If the field is a signature field, the associated signature dictionary is also deleted.
|
381
|
+
def delete_field(name_or_field)
|
382
|
+
field = (name_or_field.kind_of?(String) ? field_by_name(name_or_field) : name_or_field)
|
383
|
+
document.delete(field[:V]) if field.field_type == :Sig
|
384
|
+
|
385
|
+
to_delete = field.each_widget(direct_only: false).to_a
|
386
|
+
document.pages.each do |page|
|
387
|
+
next unless page.key?(:Annots)
|
388
|
+
page_annots = page[:Annots].to_a - to_delete
|
389
|
+
page[:Annots].value.replace(page_annots)
|
390
|
+
end
|
391
|
+
to_delete.each {|widget| document.delete(widget) }
|
392
|
+
|
393
|
+
if field[:Parent]
|
394
|
+
field[:Parent][:Kids].delete(field)
|
395
|
+
else
|
396
|
+
self[:Fields].delete(field)
|
397
|
+
end
|
398
|
+
document.delete(field)
|
399
|
+
end
|
400
|
+
|
401
|
+
# Fills form fields with the values from the given +data+ hash.
|
402
|
+
#
|
403
|
+
# The keys of the +data+ hash need to be full field names and the values are the respective
|
404
|
+
# values, usually in string form. It is possible to specify only some of the fields of the
|
405
|
+
# form.
|
406
|
+
#
|
407
|
+
# What kind of values are supported for a field depends on the field type:
|
408
|
+
#
|
409
|
+
# * For fields containing text (single/multiline/comb text fields, file select fields, combo
|
410
|
+
# boxes and list boxes) the value needs to be a string and it is assigned as is.
|
411
|
+
#
|
412
|
+
# * For check boxes, the values "y"/"yes"/"t"/"true" are handled as assigning +true+ to the
|
413
|
+
# field, the values "n"/"no"/"f"/"false" are handled as assigning +false+ to the field,
|
414
|
+
# and every other string value is assigned as is. See ButtonField#field_value= for
|
415
|
+
# details.
|
416
|
+
#
|
417
|
+
# * For radio buttons the value needs to be a String or a Symbol representing the name of
|
418
|
+
# the radio button widget to select.
|
419
|
+
def fill(data)
|
420
|
+
data.each do |field_name, value|
|
421
|
+
field = field_by_name(field_name)
|
422
|
+
raise HexaPDF::Error, "AcroForm field named '#{field_name}' not found" unless field
|
423
|
+
|
424
|
+
case field.concrete_field_type
|
425
|
+
when :single_line_text_field, :multiline_text_field, :comb_text_field, :file_select_field,
|
426
|
+
:combo_box, :list_box, :editable_combo_box, :radio_button
|
427
|
+
field.field_value = value
|
428
|
+
when :check_box
|
429
|
+
field.field_value = case value
|
430
|
+
when /\A(?:y(es)?|t(rue)?)\z/ then true
|
431
|
+
when /\A(?:n(o)?|f(alse)?)\z/ then false
|
432
|
+
else value
|
433
|
+
end
|
434
|
+
else
|
435
|
+
raise HexaPDF::Error, "AcroForm field type #{field.concrete_field_type} not yet supported"
|
436
|
+
end
|
437
|
+
end
|
346
438
|
end
|
347
439
|
|
348
440
|
# Returns the dictionary containing the default resources for form field appearance streams.
|
@@ -440,23 +532,27 @@ module HexaPDF
|
|
440
532
|
|
441
533
|
private
|
442
534
|
|
443
|
-
# Creates a new field with the full name +name+ and the field type +type+.
|
444
|
-
def create_field(name, type)
|
535
|
+
# Creates a new field with the full name +name+ and the optional field type +type+.
|
536
|
+
def create_field(name, type = nil)
|
445
537
|
parent_name, _, name = name.rpartition('.')
|
446
538
|
parent_field = parent_name.empty? ? nil : field_by_name(parent_name)
|
447
539
|
if !parent_name.empty? && !parent_field
|
448
|
-
|
540
|
+
parent_field = create_namespace_field(parent_name)
|
449
541
|
end
|
450
542
|
|
451
|
-
field =
|
452
|
-
|
543
|
+
field = if type
|
544
|
+
document.add({FT: type, T: name, Parent: parent_field},
|
545
|
+
type: :XXAcroFormField, subtype: type)
|
546
|
+
else
|
547
|
+
document.add({T: name, Parent: parent_field}, type: :XXAcroFormField)
|
548
|
+
end
|
453
549
|
if parent_field
|
454
550
|
(parent_field[:Kids] ||= []) << field
|
455
551
|
else
|
456
552
|
(self[:Fields] ||= []) << field
|
457
553
|
end
|
458
554
|
|
459
|
-
yield(field)
|
555
|
+
yield(field) if block_given?
|
460
556
|
|
461
557
|
field
|
462
558
|
end
|