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
@@ -35,6 +35,7 @@
|
|
35
35
|
#++
|
36
36
|
|
37
37
|
require 'json'
|
38
|
+
require 'time'
|
38
39
|
require 'hexapdf/error'
|
39
40
|
require 'hexapdf/layout/style'
|
40
41
|
require 'hexapdf/layout/text_fragment'
|
@@ -56,6 +57,7 @@ module HexaPDF
|
|
56
57
|
# JavaScript actions are:
|
57
58
|
#
|
58
59
|
# * +AFNumber_Format+: See #af_number_format_action and #apply_af_number_format
|
60
|
+
# * +AFPercent_Format+: See #af_percent_format_action and #apply_af_percent_format
|
59
61
|
#
|
60
62
|
# Calculating a field's value::
|
61
63
|
#
|
@@ -184,6 +186,10 @@ module HexaPDF
|
|
184
186
|
return [value, nil] unless (action_string = action_string(format_action))
|
185
187
|
if action_string.start_with?('AFNumber_Format(')
|
186
188
|
apply_af_number_format(value, action_string)
|
189
|
+
elsif action_string.start_with?('AFPercent_Format(')
|
190
|
+
apply_af_percent_format(value, action_string)
|
191
|
+
elsif action_string.start_with?('AFTime_Format(')
|
192
|
+
apply_af_time_format(value, action_string)
|
187
193
|
else
|
188
194
|
[value, nil]
|
189
195
|
end
|
@@ -235,8 +241,15 @@ module HexaPDF
|
|
235
241
|
# See: #apply_af_number_format
|
236
242
|
def af_number_format_action(decimals: 2, separator_style: :point, negative_style: :minus_black,
|
237
243
|
currency_string: "", prepend_currency: true)
|
238
|
-
|
239
|
-
|
244
|
+
separator_style = AF_NUMBER_FORMAT_MAPPINGS[:separator].fetch(separator_style) do
|
245
|
+
raise ArgumentError, "Unsupported value for separator_style argument: #{separator_style}"
|
246
|
+
end
|
247
|
+
negative_style = AF_NUMBER_FORMAT_MAPPINGS[:negative].fetch(negative_style) do
|
248
|
+
raise ArgumentError, "Unsupported value for negative_style argument: #{negative_style}"
|
249
|
+
end
|
250
|
+
|
251
|
+
"AFNumber_Format(#{decimals}, #{separator_style}, " \
|
252
|
+
"#{negative_style}, 0, \"#{currency_string}\", " \
|
240
253
|
"#{prepend_currency});"
|
241
254
|
end
|
242
255
|
|
@@ -323,21 +336,141 @@ module HexaPDF
|
|
323
336
|
end
|
324
337
|
end
|
325
338
|
|
326
|
-
|
339
|
+
[af_format_number(value, format, match[:sep_style]), text_color]
|
340
|
+
end
|
327
341
|
|
328
|
-
|
329
|
-
|
330
|
-
|
331
|
-
|
342
|
+
# Returns the appropriate JavaScript action string for the AFPercent_Format function.
|
343
|
+
#
|
344
|
+
# +decimals+::
|
345
|
+
# The number of decimal digits to use. Default 2.
|
346
|
+
#
|
347
|
+
# +separator_style+::
|
348
|
+
# Specifies the character for the decimal and thousands separator, one of:
|
349
|
+
#
|
350
|
+
# :point:: (Default) Use point as decimal separator and comma as thousands separator.
|
351
|
+
# :point_no_thousands:: Use point as decimal separator and no thousands separator.
|
352
|
+
# :comma:: Use comma as decimal separator and point as thousands separator.
|
353
|
+
# :comma_no_thousands:: Use comma as decimal separator and no thousands separator.
|
354
|
+
#
|
355
|
+
# See: #apply_af_percent_format
|
356
|
+
def af_percent_format_action(decimals: 2, separator_style: :point)
|
357
|
+
separator_style = AF_NUMBER_FORMAT_MAPPINGS[:separator].fetch(separator_style) do
|
358
|
+
raise ArgumentError, "Unsupported value for separator_style argument: #{separator_style}"
|
332
359
|
end
|
333
|
-
|
334
|
-
|
335
|
-
|
336
|
-
|
337
|
-
|
338
|
-
|
360
|
+
"AFPercent_Format(#{decimals}, #{separator_style});"
|
361
|
+
end
|
362
|
+
|
363
|
+
# Regular expression for matching the AFPercent_Format method.
|
364
|
+
#
|
365
|
+
# See: #apply_af_percent_format
|
366
|
+
AF_PERCENT_FORMAT_RE = /
|
367
|
+
\AAFPercent_Format\(
|
368
|
+
\s*(?<ndec>\d+)\s*,
|
369
|
+
\s*(?<sep_style>[0-3])\s*
|
370
|
+
\);?\z
|
371
|
+
/x
|
372
|
+
|
373
|
+
# Implements the JavaScript AFPercent_Format function and returns the formatted field value.
|
374
|
+
#
|
375
|
+
# The argument +value+ has to be the field's value (a String) and +action_string+ has to be
|
376
|
+
# the JavaScript action string.
|
377
|
+
#
|
378
|
+
# The AFPercent_Format function assumes that the text field's value contains a number (as a
|
379
|
+
# string) and formats it according to the instructions.
|
380
|
+
#
|
381
|
+
# It has the form <tt>AFPercent_Format(no_of_decimals, separator_style)</tt> where the
|
382
|
+
# arguments have the following meaning:
|
383
|
+
#
|
384
|
+
# +no_of_decimals+::
|
385
|
+
# The number of decimal places after the decimal point, e.g. for 3 it would result in
|
386
|
+
# 123.456.
|
387
|
+
#
|
388
|
+
# +separator_style+::
|
389
|
+
# Defines which decimal separator and whether a thousands separator should be used.
|
390
|
+
#
|
391
|
+
# Possible values are:
|
392
|
+
#
|
393
|
+
# +0+:: Comma for thousands separator, point for decimal separator: 12,345.67
|
394
|
+
# +1+:: No thousands separator, point for decimal separator: 12345.67
|
395
|
+
# +2+:: Point for thousands separator, comma for decimal separator: 12.345,67
|
396
|
+
# +3+:: No thousands separator, comma for decimal separator: 12345,67
|
397
|
+
def apply_af_percent_format(value, action_string)
|
398
|
+
return value unless (match = AF_PERCENT_FORMAT_RE.match(action_string))
|
399
|
+
af_format_number(af_make_number(value) * 100, "%.#{match[:ndec]}f%%", match[:sep_style])
|
400
|
+
end
|
339
401
|
|
340
|
-
|
402
|
+
AF_TIME_FORMAT_MAPPINGS = { #:nodoc:
|
403
|
+
format_integers: {
|
404
|
+
hh_mm: 0,
|
405
|
+
0 => 0,
|
406
|
+
hh12_mm: 1,
|
407
|
+
1 => 1,
|
408
|
+
hh_mm_ss: 2,
|
409
|
+
2 => 2,
|
410
|
+
hh12_mm_ss: 3,
|
411
|
+
3 => 3,
|
412
|
+
},
|
413
|
+
strftime_format: {
|
414
|
+
'0' => '%H:%M',
|
415
|
+
'1' => '%l:%M %p',
|
416
|
+
'2' => '%H:%M:%S',
|
417
|
+
'3' => '%l:%M:%S %p',
|
418
|
+
},
|
419
|
+
}
|
420
|
+
|
421
|
+
# Returns the appropriate JavaScript action string for the AFTime_Format function.
|
422
|
+
#
|
423
|
+
# +format+::
|
424
|
+
# Specifies the time format, one of:
|
425
|
+
#
|
426
|
+
# :hh_mm:: (Default) Use 24h time format %H:%M (e.g. 15:25)
|
427
|
+
# :hh12_mm:: (Default) Use 12h time format %l:%M %p (e.g. 3:25 PM)
|
428
|
+
# :hh_mm_ss:: Use 24h time format with seconds %H:%M:%S (e.g. 15:25:37)
|
429
|
+
# :hh12_mm_ss:: Use 24h time format with seconds %l:%M:%S %p (e.g. 3:25:37 PM)
|
430
|
+
#
|
431
|
+
# See: #apply_af_time_format
|
432
|
+
def af_time_format_action(format: :hh_mm)
|
433
|
+
format = AF_TIME_FORMAT_MAPPINGS[:format_integers].fetch(format) do
|
434
|
+
raise ArgumentError, "Unsupported value for time_format argument: #{format}"
|
435
|
+
end
|
436
|
+
"AFTime_Format(#{format});"
|
437
|
+
end
|
438
|
+
|
439
|
+
# Regular expression for matching the AFTime_Format method.
|
440
|
+
#
|
441
|
+
# See: #apply_af_time_format
|
442
|
+
AF_TIME_FORMAT_RE = /
|
443
|
+
\AAFTime_Format\(
|
444
|
+
\s*(?<time_format>[0-3])\s*
|
445
|
+
\);?\z
|
446
|
+
/x
|
447
|
+
|
448
|
+
# Implements the JavaScript AFTime_Format function and returns the formatted field value.
|
449
|
+
#
|
450
|
+
# The argument +value+ has to be the field's value (a String) and +action_string+ has to be
|
451
|
+
# the JavaScript action string.
|
452
|
+
#
|
453
|
+
# The AFTime_Format function assumes that the text field's value contains a valid time
|
454
|
+
# string (for HexaPDF that is anything Time.parse can work with) and formats it according to
|
455
|
+
# the instructions.
|
456
|
+
#
|
457
|
+
# It has the form <tt>AFTime_Format(time_format)</tt> where the argument has the following
|
458
|
+
# meaning:
|
459
|
+
#
|
460
|
+
# +time_format+::
|
461
|
+
# Defines the time format which should be applied.
|
462
|
+
#
|
463
|
+
# Possible values are:
|
464
|
+
#
|
465
|
+
# +0+:: Use 24h time format, e.g. 15:25
|
466
|
+
# +1+:: Use 12h time format, e.g. 3:25 PM
|
467
|
+
# +2+:: Use 24h time format with seconds, e.g. 15:25:37
|
468
|
+
# +3+:: Use 12h time format with seconds, e.g. 3:25:37 PM
|
469
|
+
def apply_af_time_format(value, action_string)
|
470
|
+
return value unless (match = AF_TIME_FORMAT_RE.match(action_string))
|
471
|
+
value = Time.parse(value) rescue nil
|
472
|
+
return "" unless value
|
473
|
+
value.strftime(AF_TIME_FORMAT_MAPPINGS[:strftime_format][match[:time_format]]).strip
|
341
474
|
end
|
342
475
|
|
343
476
|
# Handles JavaScript calculate actions for single-line text fields.
|
@@ -483,6 +616,24 @@ module HexaPDF
|
|
483
616
|
value.to_s.tr(',', '.').to_f
|
484
617
|
end
|
485
618
|
|
619
|
+
# Formats the numeric value according to the format string and separator style.
|
620
|
+
def af_format_number(value, format, sep_style)
|
621
|
+
result = sprintf(format, value)
|
622
|
+
|
623
|
+
before_decimal_point, after_decimal_point = result.split('.')
|
624
|
+
if sep_style == '0' || sep_style == '2'
|
625
|
+
separator = (sep_style == '0' ? ',' : '.')
|
626
|
+
before_decimal_point.gsub!(/\B(?=(\d\d\d)+(?:[^\d]|\z))/, separator)
|
627
|
+
end
|
628
|
+
|
629
|
+
if after_decimal_point
|
630
|
+
decimal_point = (sep_style <= "1" ? '.' : ',')
|
631
|
+
"#{before_decimal_point}#{decimal_point}#{after_decimal_point}"
|
632
|
+
else
|
633
|
+
before_decimal_point
|
634
|
+
end
|
635
|
+
end
|
636
|
+
|
486
637
|
# Returns the JavaScript action string for the given action.
|
487
638
|
def action_string(action)
|
488
639
|
return nil unless action && action[:S] == :JavaScript
|
@@ -170,7 +170,7 @@ module HexaPDF
|
|
170
170
|
elsif comb_text_field? && !key?(:MaxLen)
|
171
171
|
raise HexaPDF::Error, "A comb text field need a valid /MaxLen value"
|
172
172
|
elsif str && !str.kind_of?(String)
|
173
|
-
@document.config['acro_form.on_invalid_value'].call(self, str)
|
173
|
+
str = @document.config['acro_form.on_invalid_value'].call(self, str)
|
174
174
|
end
|
175
175
|
str = str.gsub(/[[:space:]]/, ' ') if str && concrete_field_type == :single_line_text_field
|
176
176
|
if key?(:MaxLen) && str && str.length > self[:MaxLen]
|
@@ -254,9 +254,21 @@ module HexaPDF
|
|
254
254
|
# :number::
|
255
255
|
# Assumes that the field value is a number and formats it according to the given
|
256
256
|
# arguments. See JavaScriptActions.af_number_format_action for details on the arguments.
|
257
|
+
#
|
258
|
+
# :percent::
|
259
|
+
# Assumes that the field value is a number and formats it as percentage (where 1=100%
|
260
|
+
# and 0=0%). See JavaScriptActions.af_percent_format_action for details on the
|
261
|
+
# arguments.
|
262
|
+
#
|
263
|
+
# :time::
|
264
|
+
# Assumes that the field value is a string with a time value and formats it according to
|
265
|
+
# the given argument. See JavaScriptActions.af_time_format_action for details on the
|
266
|
+
# arguments.
|
257
267
|
def set_format_action(type, **arguments)
|
258
268
|
action_string = case type
|
259
269
|
when :number then JavaScriptActions.af_number_format_action(**arguments)
|
270
|
+
when :percent then JavaScriptActions.af_percent_format_action(**arguments)
|
271
|
+
when :time then JavaScriptActions.af_time_format_action(**arguments)
|
260
272
|
else
|
261
273
|
raise ArgumentError, "Invalid value for type argument: #{type.inspect}"
|
262
274
|
end
|
@@ -143,7 +143,8 @@ module HexaPDF
|
|
143
143
|
#
|
144
144
|
# If the dictionary is not found, an error is raised.
|
145
145
|
def font(name)
|
146
|
-
object_getter(:Font, name)
|
146
|
+
font = object_getter(:Font, name)
|
147
|
+
font.kind_of?(Hash) ? document.wrap(font) : font
|
147
148
|
end
|
148
149
|
|
149
150
|
# Adds the font dictionary to the resources and returns the name under which it is stored.
|
data/lib/hexapdf/utils.rb
CHANGED
@@ -39,8 +39,27 @@ require 'geom2d/utils'
|
|
39
39
|
module HexaPDF
|
40
40
|
|
41
41
|
# This module contains helper methods for the whole library.
|
42
|
+
#
|
43
|
+
# Furthermore, it refines Numeric to provide #mm, #cm, and #inch methods.
|
42
44
|
module Utils
|
43
45
|
|
46
|
+
refine Numeric do
|
47
|
+
# Intrepeting self as millimeters returns the equivalent number of points.
|
48
|
+
def mm
|
49
|
+
self * 72 / 25.4
|
50
|
+
end
|
51
|
+
|
52
|
+
# Intrepeting self as centimeters returns the equivalent number of points.
|
53
|
+
def cm
|
54
|
+
self * 72 / 2.54
|
55
|
+
end
|
56
|
+
|
57
|
+
# Intrepeting self as inches returns the equivalent number of points.
|
58
|
+
def inch
|
59
|
+
self * 72
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
44
63
|
# The precision with which to compare floating point numbers.
|
45
64
|
#
|
46
65
|
# This is chosen with respect to precision that is used for serializing floating point numbers.
|
data/lib/hexapdf/version.rb
CHANGED
@@ -20,13 +20,13 @@ describe HexaPDF::Layout::BoxFitter do
|
|
20
20
|
@box_fitter.fit(HexaPDF::Layout::TextBox.new(items: [ibox] * count))
|
21
21
|
end
|
22
22
|
|
23
|
-
def check_result(*pos, content_heights:,
|
23
|
+
def check_result(*pos, content_heights:, success: true, boxes_remain: false)
|
24
24
|
pos.each_slice(2).with_index do |(x, y), index|
|
25
25
|
assert_equal(x, @box_fitter.fit_results[index].x, "x #{index}")
|
26
26
|
assert_equal(y, @box_fitter.fit_results[index].y, "y #{index}")
|
27
27
|
end
|
28
28
|
assert_equal(content_heights, @box_fitter.content_heights)
|
29
|
-
|
29
|
+
success ? assert(@box_fitter.success?) : refute(@box_fitter.success?)
|
30
30
|
rboxes = @box_fitter.remaining_boxes.empty?
|
31
31
|
boxes_remain ? refute(rboxes) : assert(rboxes)
|
32
32
|
end
|
@@ -55,7 +55,7 @@ describe HexaPDF::Layout::BoxFitter do
|
|
55
55
|
fit_box(70)
|
56
56
|
fit_box(40)
|
57
57
|
fit_box(20)
|
58
|
-
check_result(10, 80, 0, 10, 0, 0, 100, 100,
|
58
|
+
check_result(10, 80, 0, 10, 0, 0, 100, 100, success: false, boxes_remain: true,
|
59
59
|
content_heights: [90, 50])
|
60
60
|
assert_equal(2, @box_fitter.remaining_boxes.size)
|
61
61
|
end
|
@@ -50,10 +50,12 @@ describe HexaPDF::Layout::TextBox do
|
|
50
50
|
end
|
51
51
|
|
52
52
|
it "fits into the frame's outline" do
|
53
|
+
@frame.remove_area(Geom2D::Rectangle(0, 80, 20, 20))
|
54
|
+
@frame.remove_area(Geom2D::Rectangle(80, 70, 20, 20))
|
53
55
|
box = create_box([@inline_box] * 20, style: {position: :flow})
|
54
56
|
assert(box.fit(100, 100, @frame))
|
55
57
|
assert_equal(100, box.width)
|
56
|
-
assert_equal(
|
58
|
+
assert_equal(30, box.height)
|
57
59
|
end
|
58
60
|
|
59
61
|
it "takes the style option last_line_gap into account" do
|
@@ -190,6 +192,30 @@ describe HexaPDF::Layout::TextBox do
|
|
190
192
|
[:restore_graphics_state]])
|
191
193
|
end
|
192
194
|
|
195
|
+
it "correctly draws borders, backgrounds... for position :flow" do
|
196
|
+
@frame.remove_area(Geom2D::Rectangle(0, 0, 40, 100))
|
197
|
+
box = create_box([@inline_box], style: {position: :flow, border: {width: 1}})
|
198
|
+
box.fit(60, 100, @frame)
|
199
|
+
box.draw(@canvas, 0, 90)
|
200
|
+
assert_operators(@canvas.contents, [[:save_graphics_state],
|
201
|
+
[:append_rectangle, [40, 90, 10, 10]],
|
202
|
+
[:clip_path_non_zero],
|
203
|
+
[:end_path],
|
204
|
+
[:append_rectangle, [40.5, 90.5, 9.0, 9.0]],
|
205
|
+
[:stroke_path],
|
206
|
+
[:restore_graphics_state],
|
207
|
+
[:save_graphics_state],
|
208
|
+
[:restore_graphics_state],
|
209
|
+
[:save_graphics_state],
|
210
|
+
[:concatenate_matrix, [1, 0, 0, 1, 41, 89]],
|
211
|
+
[:save_graphics_state],
|
212
|
+
[:concatenate_matrix, [1, 0, 0, 1, 0, 0]],
|
213
|
+
[:restore_graphics_state],
|
214
|
+
[:restore_graphics_state],
|
215
|
+
[:save_graphics_state],
|
216
|
+
[:restore_graphics_state]])
|
217
|
+
end
|
218
|
+
|
193
219
|
it "draws nothing onto the canvas if the box is empty" do
|
194
220
|
box = create_box([])
|
195
221
|
box.draw(@canvas, 5, 5)
|
@@ -323,11 +323,13 @@ describe HexaPDF::Dictionary do
|
|
323
323
|
end
|
324
324
|
end
|
325
325
|
|
326
|
-
describe "
|
327
|
-
it "returns a
|
328
|
-
|
326
|
+
describe "to_hash" do
|
327
|
+
it "returns a copy of the value where each entry is pre-processed" do
|
328
|
+
@dict[:value] = HexaPDF::Reference.new(1, 0)
|
329
|
+
obj = @dict.to_hash
|
329
330
|
refute_equal(obj.object_id, @dict.value.object_id)
|
330
|
-
assert_equal(obj,
|
331
|
+
assert_equal(:obj, obj[:Object])
|
332
|
+
assert_equal("deref", obj[:value])
|
331
333
|
end
|
332
334
|
end
|
333
335
|
|
data/test/hexapdf/test_parser.rb
CHANGED
@@ -367,6 +367,11 @@ describe HexaPDF::Parser do
|
|
367
367
|
assert_equal(5, @parser.startxref_offset)
|
368
368
|
end
|
369
369
|
|
370
|
+
it "handles the case of multiple %%EOF and the last one being invalid" do
|
371
|
+
create_parser("startxref\n5\n%%EOF\ntartxref\n3\n%%EOF")
|
372
|
+
assert_equal(5, @parser.startxref_offset)
|
373
|
+
end
|
374
|
+
|
370
375
|
it "fails even in big files when nothing is found" do
|
371
376
|
create_parser("\nhallo" * 5000)
|
372
377
|
exp = assert_raises(HexaPDF::MalformedPDFError) { @parser.startxref_offset }
|
@@ -402,6 +407,13 @@ describe HexaPDF::Parser do
|
|
402
407
|
exp = assert_raises(HexaPDF::MalformedPDFError) { @parser.startxref_offset }
|
403
408
|
assert_match(/startxref on same line/, exp.message)
|
404
409
|
end
|
410
|
+
|
411
|
+
it "fails on strict parsing if there are multiple %%EOF and the last one is invalid" do
|
412
|
+
@document.config['parser.on_correctable_error'] = proc { true }
|
413
|
+
create_parser("startxref\n5\n%%EOF\ntartxref\n3\n%%EOF")
|
414
|
+
exp = assert_raises(HexaPDF::MalformedPDFError) { @parser.startxref_offset }
|
415
|
+
assert_match(/missing startxref keyword/, exp.message)
|
416
|
+
end
|
405
417
|
end
|
406
418
|
|
407
419
|
describe "file_header_version" do
|
data/test/hexapdf/test_utils.rb
CHANGED
@@ -6,6 +6,22 @@ require 'hexapdf/utils'
|
|
6
6
|
describe HexaPDF::Utils do
|
7
7
|
include HexaPDF::Utils
|
8
8
|
|
9
|
+
describe "Numeric refinement" do
|
10
|
+
using HexaPDF::Utils
|
11
|
+
|
12
|
+
it "converts mm to points" do
|
13
|
+
assert_equal(72, 25.4.mm)
|
14
|
+
end
|
15
|
+
|
16
|
+
it "converts cm to points" do
|
17
|
+
assert_equal(72, 2.54.cm)
|
18
|
+
end
|
19
|
+
|
20
|
+
it "converts inch to points" do
|
21
|
+
assert_equal(144, 2.inch)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
9
25
|
it "checks floats for equality with a certain precision" do
|
10
26
|
assert(float_equal(1.0, 1))
|
11
27
|
assert(float_equal(1.0, 1.0000003))
|
@@ -93,6 +93,11 @@ describe HexaPDF::Type::AcroForm::ButtonField do
|
|
93
93
|
@field.field_value = "check"
|
94
94
|
assert_equal(:check, @field[:V])
|
95
95
|
assert_raises(HexaPDF::Error) { @field.field_value = :unknown }
|
96
|
+
|
97
|
+
@field.field_value = :Off
|
98
|
+
@field.create_widget(@doc.pages[0], value: :other)
|
99
|
+
@field.field_value = true
|
100
|
+
assert_equal(:check, @field[:V])
|
96
101
|
end
|
97
102
|
|
98
103
|
it "returns the correct concrete field type" do
|
@@ -128,6 +128,12 @@ describe HexaPDF::Type::AcroForm::Form do
|
|
128
128
|
@acro_form = @doc.acro_form(create: true)
|
129
129
|
end
|
130
130
|
|
131
|
+
it "creates a pure namespace field" do
|
132
|
+
field = @acro_form.create_namespace_field('text')
|
133
|
+
assert_equal('text', field.full_field_name)
|
134
|
+
assert_nil(field.concrete_field_type)
|
135
|
+
end
|
136
|
+
|
131
137
|
describe "handles the general case" do
|
132
138
|
it "works for names with a dot" do
|
133
139
|
@acro_form[:Fields] = [{T: "root"}]
|
@@ -142,8 +148,13 @@ describe HexaPDF::Type::AcroForm::Form do
|
|
142
148
|
assert([field], @acro_form[:Fields])
|
143
149
|
end
|
144
150
|
|
145
|
-
it "
|
146
|
-
|
151
|
+
it "creates the parent fields as namespace fields if necessary" do
|
152
|
+
field = @acro_form.create_text_field("root.sub.field")
|
153
|
+
level1 = @acro_form.field_by_name('root')
|
154
|
+
assert_equal(1, level1[:Kids].size)
|
155
|
+
level2 = @acro_form.field_by_name('root.sub')
|
156
|
+
assert_equal(1, level2[:Kids].size)
|
157
|
+
assert_same(field, level2[:Kids][0])
|
147
158
|
end
|
148
159
|
end
|
149
160
|
|
@@ -241,6 +252,103 @@ describe HexaPDF::Type::AcroForm::Form do
|
|
241
252
|
end
|
242
253
|
end
|
243
254
|
|
255
|
+
describe "delete_field" do
|
256
|
+
before do
|
257
|
+
@field = @acro_form.create_signature_field("sig")
|
258
|
+
end
|
259
|
+
|
260
|
+
it "deletes a field via name" do
|
261
|
+
@acro_form.delete_field('sig')
|
262
|
+
assert_equal(0, @acro_form.root_fields.size)
|
263
|
+
end
|
264
|
+
|
265
|
+
it "deletes a field via field object" do
|
266
|
+
@acro_form.delete_field(@field)
|
267
|
+
assert_equal(0, @acro_form.root_fields.size)
|
268
|
+
end
|
269
|
+
|
270
|
+
it "deletes the set signature object" do
|
271
|
+
obj = @doc.add({})
|
272
|
+
@field.field_value = obj
|
273
|
+
@acro_form.delete_field(@field)
|
274
|
+
assert(obj.null?)
|
275
|
+
end
|
276
|
+
|
277
|
+
it "deletes all widget annotations from the document and the annotation array" do
|
278
|
+
widget1 = @field.create_widget(@doc.pages.add, Rect: [0, 0, 0, 0])
|
279
|
+
widget2 = @field.create_widget(@doc.pages.add, Rect: [0, 0, 0, 0])
|
280
|
+
refute(@doc.pages[1][:Annots].empty?)
|
281
|
+
@acro_form.delete_field(@field)
|
282
|
+
assert(@doc.pages[0][:Annots].empty?)
|
283
|
+
assert(@doc.pages[1][:Annots].empty?)
|
284
|
+
assert(@doc.object(widget1).null?)
|
285
|
+
assert(@doc.object(widget2).null?)
|
286
|
+
end
|
287
|
+
|
288
|
+
it "deletes the field from the field hierarchy" do
|
289
|
+
@acro_form.delete_field('sig')
|
290
|
+
refute(@acro_form.field_by_name('sig'))
|
291
|
+
assert(@acro_form[:Fields].empty?)
|
292
|
+
|
293
|
+
@acro_form.create_signature_field("sub.sub.sig")
|
294
|
+
@acro_form.delete_field("sub.sub.sig")
|
295
|
+
refute(@acro_form.field_by_name('sub.sub.sig'))
|
296
|
+
assert(@acro_form[:Fields][0][:Kids][0][:Kids].empty?)
|
297
|
+
end
|
298
|
+
|
299
|
+
it "deletes the field itself" do
|
300
|
+
@acro_form.delete_field('sig')
|
301
|
+
assert(@doc.object(@field).null?)
|
302
|
+
end
|
303
|
+
end
|
304
|
+
|
305
|
+
describe "fill" do
|
306
|
+
it "works for text field types" do
|
307
|
+
field = @acro_form.create_text_field('test')
|
308
|
+
@acro_form.fill("test" => "value")
|
309
|
+
assert_equal("value", field.field_value)
|
310
|
+
end
|
311
|
+
|
312
|
+
it "works for radio buttons" do
|
313
|
+
field = @acro_form.create_radio_button("test")
|
314
|
+
field.create_widget(@doc.pages.add, value: :name)
|
315
|
+
@acro_form.fill("test" => "name")
|
316
|
+
assert_equal(:name, field.field_value)
|
317
|
+
end
|
318
|
+
|
319
|
+
it "works for check boxes" do
|
320
|
+
field = @acro_form.create_check_box('test')
|
321
|
+
field.create_widget(@doc.pages.add)
|
322
|
+
|
323
|
+
["t", "true", "y", "yes"].each do |value|
|
324
|
+
@acro_form.fill("test" => value)
|
325
|
+
assert_equal(:Yes, field.field_value)
|
326
|
+
field.field_value = :Off
|
327
|
+
end
|
328
|
+
|
329
|
+
["f", "false", "n", "no"].each do |value|
|
330
|
+
@acro_form.fill("test" => value)
|
331
|
+
assert_nil(field.field_value)
|
332
|
+
field.field_value = :Yes
|
333
|
+
end
|
334
|
+
|
335
|
+
field.create_widget(@doc.pages.add, value: :Other)
|
336
|
+
@acro_form.fill("test" => "Other")
|
337
|
+
assert_equal(:Other, field.field_value)
|
338
|
+
end
|
339
|
+
|
340
|
+
it "raises an error if a field is not found" do
|
341
|
+
error = assert_raises(HexaPDF::Error) { @acro_form.fill("unknown" => "test") }
|
342
|
+
assert_match(/named 'unknown' not found/, error.message)
|
343
|
+
end
|
344
|
+
|
345
|
+
it "raises an error if a field type is not supported for filling in" do
|
346
|
+
@acro_form.create_check_box('test').initialize_as_push_button
|
347
|
+
error = assert_raises(HexaPDF::Error) { @acro_form.fill("test" => "test") }
|
348
|
+
assert_match(/push_button not yet supported/, error.message)
|
349
|
+
end
|
350
|
+
end
|
351
|
+
|
244
352
|
it "returns the default resources" do
|
245
353
|
assert_kind_of(HexaPDF::Type::Resources, @acro_form.default_resources)
|
246
354
|
end
|