hexapdf 0.41.0 → 0.43.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|