hexapdf 0.39.1 → 0.41.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 +59 -0
- data/examples/019-acro_form.rb +12 -23
- data/examples/027-composer_optional_content.rb +1 -1
- data/examples/030-pdfa.rb +6 -6
- data/examples/031-acro_form_java_script.rb +101 -0
- data/lib/hexapdf/cli/command.rb +11 -0
- data/lib/hexapdf/cli/form.rb +38 -9
- data/lib/hexapdf/cli/info.rb +4 -0
- data/lib/hexapdf/configuration.rb +10 -0
- data/lib/hexapdf/content/canvas.rb +2 -0
- data/lib/hexapdf/document/layout.rb +8 -1
- data/lib/hexapdf/encryption/aes.rb +13 -6
- data/lib/hexapdf/encryption/security_handler.rb +6 -4
- data/lib/hexapdf/font/cmap/parser.rb +1 -5
- data/lib/hexapdf/font/cmap.rb +22 -3
- data/lib/hexapdf/font_loader/from_configuration.rb +1 -1
- data/lib/hexapdf/font_loader/variant_from_name.rb +72 -0
- data/lib/hexapdf/font_loader.rb +1 -0
- data/lib/hexapdf/layout/list_box.rb +40 -33
- data/lib/hexapdf/layout/style.rb +25 -23
- data/lib/hexapdf/layout/text_box.rb +3 -3
- data/lib/hexapdf/type/acro_form/appearance_generator.rb +11 -76
- data/lib/hexapdf/type/acro_form/field.rb +14 -0
- data/lib/hexapdf/type/acro_form/form.rb +25 -8
- data/lib/hexapdf/type/acro_form/java_script_actions.rb +498 -0
- data/lib/hexapdf/type/acro_form/text_field.rb +78 -0
- data/lib/hexapdf/type/acro_form.rb +1 -0
- data/lib/hexapdf/type/annotations/widget.rb +1 -1
- data/lib/hexapdf/version.rb +1 -1
- data/test/hexapdf/encryption/test_aes.rb +18 -8
- data/test/hexapdf/encryption/test_security_handler.rb +17 -0
- data/test/hexapdf/encryption/test_standard_security_handler.rb +1 -1
- data/test/hexapdf/font/cmap/test_parser.rb +5 -3
- data/test/hexapdf/font/test_cmap.rb +8 -0
- data/test/hexapdf/font_loader/test_from_configuration.rb +4 -0
- data/test/hexapdf/font_loader/test_variant_from_name.rb +34 -0
- data/test/hexapdf/layout/test_list_box.rb +30 -1
- data/test/hexapdf/layout/test_style.rb +1 -1
- data/test/hexapdf/layout/test_text_box.rb +4 -4
- data/test/hexapdf/type/acro_form/test_appearance_generator.rb +31 -47
- data/test/hexapdf/type/acro_form/test_field.rb +11 -0
- data/test/hexapdf/type/acro_form/test_form.rb +33 -0
- data/test/hexapdf/type/acro_form/test_java_script_actions.rb +226 -0
- data/test/hexapdf/type/acro_form/test_text_field.rb +44 -0
- metadata +7 -2
@@ -332,11 +332,28 @@ describe HexaPDF::Encryption::SecurityHandler do
|
|
332
332
|
end
|
333
333
|
|
334
334
|
it "enhances a thrown EncryptionError by setting the PDF object" do
|
335
|
+
@document.config['encryption.on_decryption_error'] = proc { true }
|
335
336
|
@handler.set_up_encryption(key_length: 256)
|
336
337
|
error = assert_raises(HexaPDF::EncryptionError) { @handler.decrypt(@obj) }
|
337
338
|
assert_match(/Object \(1,0\):/, error.message)
|
338
339
|
end
|
339
340
|
|
341
|
+
it "uses the encryption.on_decryption_error configuration option" do
|
342
|
+
@handler.set_up_encryption(key_length: 256)
|
343
|
+
@handler.decrypt(@obj)
|
344
|
+
assert_equal('', @obj[:Key])
|
345
|
+
|
346
|
+
@obj[:Key] = @encrypted
|
347
|
+
called = false
|
348
|
+
@document.config['encryption.on_decryption_error'] = proc do |obj, msg|
|
349
|
+
assert_same(@obj, obj)
|
350
|
+
assert_match(/32 \+ 16/, msg)
|
351
|
+
called = true
|
352
|
+
end
|
353
|
+
assert_raises(HexaPDF::EncryptionError) { @handler.decrypt(@obj) }
|
354
|
+
assert(called)
|
355
|
+
end
|
356
|
+
|
340
357
|
it "fails if V < 5 and the object number changes" do
|
341
358
|
@obj.oid = 55
|
342
359
|
@handler.decrypt(@obj)
|
@@ -258,7 +258,7 @@ describe HexaPDF::Encryption::StandardSecurityHandler do
|
|
258
258
|
end
|
259
259
|
|
260
260
|
it "fails if the field cannot be decrypted" do
|
261
|
-
@dict[:Perms].
|
261
|
+
@dict[:Perms].setbyte(-1, (@dict[:Perms].getbyte(-1) + 1) % 256)
|
262
262
|
exp = assert_raises(HexaPDF::EncryptionError) { @handler.set_up_decryption(@dict) }
|
263
263
|
assert_match(/cannot be decrypted/, exp.message)
|
264
264
|
end
|
@@ -33,10 +33,11 @@ describe HexaPDF::Font::CMap::Parser do
|
|
33
33
|
<8145> <8145> 8123
|
34
34
|
<8146> <8148> 9000
|
35
35
|
endcidrange
|
36
|
-
|
36
|
+
4 beginbfrange
|
37
37
|
<0000> <005E> <0020>
|
38
38
|
<1379> <137B> <90FE>
|
39
39
|
<005F> <0061> [ <00660066> <00660069> <00660066006C> ]
|
40
|
+
<E040> <E041> <D840DC3D>
|
40
41
|
endbfrange
|
41
42
|
1 beginbfchar
|
42
43
|
<3A51> <D840DC3E>
|
@@ -82,8 +83,9 @@ describe HexaPDF::Font::CMap::Parser do
|
|
82
83
|
assert_equal("ff", cmap.to_unicode(0x5F))
|
83
84
|
assert_equal("fi", cmap.to_unicode(0x60))
|
84
85
|
assert_equal("ffl", cmap.to_unicode(0x61))
|
85
|
-
|
86
|
-
|
86
|
+
symbol = "\xD8\x40\xDC\x3E".encode("UTF-8", "UTF-16BE")
|
87
|
+
assert_equal(symbol, cmap.to_unicode(0xE041))
|
88
|
+
assert_equal(symbol, cmap.to_unicode(0x3A51))
|
87
89
|
assert_nil(cmap.to_unicode(0xFF))
|
88
90
|
end
|
89
91
|
|
@@ -97,6 +97,14 @@ describe HexaPDF::Font::CMap do
|
|
97
97
|
assert_equal("ABC", @cmap.to_unicode(20))
|
98
98
|
end
|
99
99
|
|
100
|
+
it "allows adding a code range to unicode mapping and retrieving the values" do
|
101
|
+
@cmap.add_unicode_range_mapping(20, 30, [65])
|
102
|
+
@cmap.add_unicode_range_mapping(40, 41, [0xD840, 0xDC3D])
|
103
|
+
assert_equal("A", @cmap.to_unicode(20))
|
104
|
+
assert_equal("K", @cmap.to_unicode(30))
|
105
|
+
assert_equal("𠀾", @cmap.to_unicode(41))
|
106
|
+
end
|
107
|
+
|
100
108
|
it "returns nil for unknown mappings" do
|
101
109
|
assert_nil(@cmap.to_unicode(20))
|
102
110
|
end
|
@@ -37,6 +37,10 @@ describe HexaPDF::FontLoader::FromConfiguration do
|
|
37
37
|
assert_nil(@klass.call(@doc, "Unknown"))
|
38
38
|
end
|
39
39
|
|
40
|
+
it "allows arbitrary keywords arguments" do
|
41
|
+
assert_nil(@klass.call(@doc, "Unknown", something: :other))
|
42
|
+
end
|
43
|
+
|
40
44
|
it "returns a hash with all configured fonts" do
|
41
45
|
assert_equal({'font' => [:none], 'font1' => [:none]}, @klass.available_fonts(@doc))
|
42
46
|
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
|
3
|
+
require 'test_helper'
|
4
|
+
require 'hexapdf/font_loader'
|
5
|
+
require 'hexapdf/document'
|
6
|
+
|
7
|
+
describe HexaPDF::FontLoader::VariantFromName do
|
8
|
+
before do
|
9
|
+
@doc = HexaPDF::Document.new
|
10
|
+
@obj = HexaPDF::FontLoader::VariantFromName
|
11
|
+
end
|
12
|
+
|
13
|
+
it "loads the font if the name contains a valid variant" do
|
14
|
+
wrapper = @obj.call(@doc, "Helvetica bold")
|
15
|
+
assert_equal("Helvetica-Bold", wrapper.wrapped_font.font_name)
|
16
|
+
wrapper = @obj.call(@doc, "Helvetica italic")
|
17
|
+
assert_equal("Helvetica-Oblique", wrapper.wrapped_font.font_name)
|
18
|
+
wrapper = @obj.call(@doc, "Helvetica bold_italic")
|
19
|
+
assert_equal("Helvetica-BoldOblique", wrapper.wrapped_font.font_name)
|
20
|
+
end
|
21
|
+
|
22
|
+
it "returns nil if the font name contains an unknown variant" do
|
23
|
+
assert_nil(@obj.call(@doc, "Helvetica oblique"))
|
24
|
+
end
|
25
|
+
|
26
|
+
it "ignores a supplied variant keyword argument" do
|
27
|
+
wrapper = @obj.call(@doc, "Helvetica bold", variant: :italic)
|
28
|
+
assert_equal("Helvetica-Bold", wrapper.wrapped_font.font_name)
|
29
|
+
end
|
30
|
+
|
31
|
+
it "returns nil for unknown fonts" do
|
32
|
+
assert_nil(@obj.call(@doc, "Unknown"))
|
33
|
+
end
|
34
|
+
end
|
@@ -73,6 +73,12 @@ describe HexaPDF::Layout::ListBox do
|
|
73
73
|
check_box(box, 100, 40)
|
74
74
|
end
|
75
75
|
|
76
|
+
it "respects the set initial height and the property overflow=:truncate" do
|
77
|
+
box = create_box(children: @text_boxes[0, 2], height: 20,
|
78
|
+
style: {overflow: :truncate, position: position})
|
79
|
+
check_box(box, 100, 20)
|
80
|
+
end
|
81
|
+
|
76
82
|
it "respects the border and padding around all list items, position #{position}" do
|
77
83
|
box = create_box(children: @text_boxes[0, 2],
|
78
84
|
style: {border: {width: [5, 4, 3, 2]}, padding: [5, 4, 3, 2], position: position})
|
@@ -108,7 +114,14 @@ describe HexaPDF::Layout::ListBox do
|
|
108
114
|
check_box(box, 100, 70, [[10, 80], [10, 30]])
|
109
115
|
end
|
110
116
|
|
111
|
-
it "
|
117
|
+
it "creates a new box for each marker even if the marker is the same" do
|
118
|
+
box = create_box(children: @text_boxes[0, 2])
|
119
|
+
check_box(box, 100, 40)
|
120
|
+
results = box.instance_variable_get(:@results)
|
121
|
+
refute_same(results[0].marker, results[1].marker)
|
122
|
+
end
|
123
|
+
|
124
|
+
it "fails for unknown marker types" do
|
112
125
|
box = create_box(children: @text_boxes[0, 1], marker_type: :unknown)
|
113
126
|
assert_raises(HexaPDF::Error) { box.fit(100, 100, @frame) }
|
114
127
|
end
|
@@ -136,6 +149,17 @@ describe HexaPDF::Layout::ListBox do
|
|
136
149
|
assert_equal(2, box_b.children.size)
|
137
150
|
assert_equal(1, box_b.start_number)
|
138
151
|
end
|
152
|
+
|
153
|
+
it "splits a list item containg multiple boxes along box lines" do
|
154
|
+
box = create_box(children: [@text_boxes[0], @text_boxes[1, 2]])
|
155
|
+
box.fit(100, 40, @frame)
|
156
|
+
box_a, box_b = box.split(100, 40, @frame)
|
157
|
+
assert_same(box, box_a)
|
158
|
+
assert_equal(:hide_first_marker, box_b.split_box?)
|
159
|
+
assert_equal(1, box_a.instance_variable_get(:@results)[1].box_fitter.fit_results.size)
|
160
|
+
assert_equal(1, box_b.children.size)
|
161
|
+
assert_equal(2, box_b.start_number)
|
162
|
+
end
|
139
163
|
end
|
140
164
|
|
141
165
|
describe "draw" do
|
@@ -315,5 +339,10 @@ describe HexaPDF::Layout::ListBox do
|
|
315
339
|
assert_equal(:ZapfDingbats, @canvas.resources.font(:F1)[:BaseFont])
|
316
340
|
end
|
317
341
|
|
342
|
+
it "fails if the initial height is set and property overflow is set to :error" do
|
343
|
+
box = create_box(children: @fixed_size_boxes[0, 2], height: 10)
|
344
|
+
box.fit(100, 100, @frame)
|
345
|
+
assert_raises(HexaPDF::Error) { box.draw(@canvas, 0, 100 - box.height) }
|
346
|
+
end
|
318
347
|
end
|
319
348
|
end
|
@@ -783,7 +783,7 @@ describe HexaPDF::Layout::Style do
|
|
783
783
|
refute(@style.superscript)
|
784
784
|
refute(@style.last_line_gap)
|
785
785
|
refute(@style.fill_horizontal)
|
786
|
-
assert_equal(:error, @style.
|
786
|
+
assert_equal(:error, @style.overflow)
|
787
787
|
assert_kind_of(HexaPDF::Layout::Style::Layers, @style.underlays)
|
788
788
|
assert_kind_of(HexaPDF::Layout::Style::Layers, @style.overlays)
|
789
789
|
assert_equal(:default, @style.position)
|
@@ -79,13 +79,13 @@ describe HexaPDF::Layout::TextBox do
|
|
79
79
|
end
|
80
80
|
end
|
81
81
|
|
82
|
-
it "respects the style property
|
82
|
+
it "respects the style property overflow when fitting too much text" do
|
83
83
|
box = create_box([@inline_box] * 20, height: 15)
|
84
84
|
refute(box.fit(100, 100, @frame))
|
85
|
-
box.style.
|
85
|
+
box.style.overflow = :truncate
|
86
86
|
assert(box.fit(100, 100, @frame))
|
87
87
|
|
88
|
-
box = create_box([@inline_box] * 20, style: {
|
88
|
+
box = create_box([@inline_box] * 20, style: {overflow: :truncate})
|
89
89
|
refute(box.fit(100, 15, @frame))
|
90
90
|
end
|
91
91
|
|
@@ -196,7 +196,7 @@ describe HexaPDF::Layout::TextBox do
|
|
196
196
|
assert_operators(@canvas.contents, [])
|
197
197
|
end
|
198
198
|
|
199
|
-
it "raises an error if there is too much content for a set height with
|
199
|
+
it "raises an error if there is too much content for a set height with overflow=:error" do
|
200
200
|
box = create_box([@inline_box] * 20, height: 15)
|
201
201
|
box.fit(100, 100, @frame)
|
202
202
|
assert_raises(HexaPDF::Error) { box.draw(@canvas, 0, 0) }
|
@@ -235,6 +235,12 @@ describe HexaPDF::Type::AcroForm::AppearanceGenerator do
|
|
235
235
|
assert(@widget.flagged?(:print))
|
236
236
|
end
|
237
237
|
|
238
|
+
it "removes the hidden flag on the widgets" do
|
239
|
+
@widget.flag(:hidden)
|
240
|
+
@generator.create_appearances
|
241
|
+
refute(@widget.flagged?(:hidden))
|
242
|
+
end
|
243
|
+
|
238
244
|
it "adjusts the /Rect if width is zero" do
|
239
245
|
@generator.create_appearances
|
240
246
|
assert_equal(12, @widget[:Rect].width)
|
@@ -272,6 +278,19 @@ describe HexaPDF::Type::AcroForm::AppearanceGenerator do
|
|
272
278
|
end
|
273
279
|
end
|
274
280
|
|
281
|
+
it "creates the needed appearance dictionary for the normal appearance" do
|
282
|
+
@widget[:AP][:N] = 5
|
283
|
+
@generator.create_appearances
|
284
|
+
assert_equal(:XObject, @widget[:AP][:N][:Off].type)
|
285
|
+
assert_equal(:XObject, @widget[:AP][:N][:Yes].type)
|
286
|
+
|
287
|
+
@field[:V] = :Other
|
288
|
+
@widget[:AP][:N] = @doc.wrap({Type: :XObject, Subtype: :Form})
|
289
|
+
@generator.create_appearances
|
290
|
+
assert_equal(:XObject, @widget[:AP][:N][:Off].type)
|
291
|
+
assert_equal(:XObject, @widget[:AP][:N][:Other].type)
|
292
|
+
end
|
293
|
+
|
275
294
|
it "creates the needed appearance streams" do
|
276
295
|
@widget[:AP][:N].delete(:Off)
|
277
296
|
@generator.create_appearances
|
@@ -309,7 +328,7 @@ describe HexaPDF::Type::AcroForm::AppearanceGenerator do
|
|
309
328
|
[:restore_graphics_state]])
|
310
329
|
end
|
311
330
|
|
312
|
-
it "fails if the appearance
|
331
|
+
it "fails if the appearance dictionary doesn't contain a name for the on state" do
|
313
332
|
@widget[:AP][:N].delete(:Yes)
|
314
333
|
assert_raises(HexaPDF::Error) { @generator.create_appearances }
|
315
334
|
end
|
@@ -371,6 +390,12 @@ describe HexaPDF::Type::AcroForm::AppearanceGenerator do
|
|
371
390
|
assert(@widget.flagged?(:print))
|
372
391
|
end
|
373
392
|
|
393
|
+
it "removes the hidden flag on the widgets" do
|
394
|
+
@widget.flag(:hidden)
|
395
|
+
@generator.create_appearances
|
396
|
+
refute(@widget.flagged?(:hidden))
|
397
|
+
end
|
398
|
+
|
374
399
|
describe "it adjusts the :Rect when necessary" do
|
375
400
|
before do
|
376
401
|
@widget.border_style(width: 3)
|
@@ -549,52 +574,11 @@ describe HexaPDF::Type::AcroForm::AppearanceGenerator do
|
|
549
574
|
end
|
550
575
|
end
|
551
576
|
|
552
|
-
|
553
|
-
|
554
|
-
|
555
|
-
|
556
|
-
|
557
|
-
end
|
558
|
-
|
559
|
-
def assert_format(arg_string, result, range)
|
560
|
-
@action[:JS] = "AFNumber_Format(#{arg_string});"
|
561
|
-
@generator.create_appearances
|
562
|
-
assert_operators(@widget[:AP][:N].stream, result, range: range)
|
563
|
-
end
|
564
|
-
|
565
|
-
it "respects the set number of decimals" do
|
566
|
-
assert_format('0, 2, 0, 0, "E", false',
|
567
|
-
[:show_text, ["1.234.568E"]], 9)
|
568
|
-
assert_format('2, 2, 0, 0, "E", false',
|
569
|
-
[:show_text, ["1.234.567,90E"]], 9)
|
570
|
-
end
|
571
|
-
|
572
|
-
it "respects the digit separator style" do
|
573
|
-
["1,234,567.90", "1234567.90", "1.234.567,90", "1234567,90"].each_with_index do |result, style|
|
574
|
-
assert_format("2, #{style}, 0, 0, \"\", false", [:show_text, [result]], 9)
|
575
|
-
end
|
576
|
-
end
|
577
|
-
|
578
|
-
it "respects the negative value styling" do
|
579
|
-
@field[:V] = '-1234567.898'
|
580
|
-
@widget[:Rect].height = 11.25
|
581
|
-
["-E1234567,90", "E1234567,90", "(E1234567,90)", "(E1234567,90)"].each_with_index do |result, style|
|
582
|
-
assert_format("2, 3, #{style}, 0, \"E\", true",
|
583
|
-
[[:set_device_rgb_non_stroking_color, [style % 2, 0.0, 0.0]],
|
584
|
-
[:begin_text],
|
585
|
-
[:set_text_matrix, [1, 0, 0, 1, 2, 3.183272]],
|
586
|
-
[:show_text, [result]]], 6..9)
|
587
|
-
end
|
588
|
-
end
|
589
|
-
|
590
|
-
it "respects the specified currency string and position" do
|
591
|
-
assert_format('2, 3, 0, 0, " E", false', [:show_text, ["1234567,90 E"]], 9)
|
592
|
-
assert_format('2, 3, 0, 0, "E ", true', [:show_text, ["E 1234567,90"]], 9)
|
593
|
-
end
|
594
|
-
|
595
|
-
it "does nothing to the value if the Javascript method could not be determined " do
|
596
|
-
assert_format('2, 3, 0, 0, " E", false, a', [:show_text, ["1234567.898765"]], 8)
|
597
|
-
end
|
577
|
+
it "applies JavaScript format actions" do
|
578
|
+
@field[:V] = '1234567.898765'
|
579
|
+
@field[:AA] = {F: {S: :JavaScript, JS: 'AFNumber_Format(0, 2, 0, 0, "E", false);'}}
|
580
|
+
@generator.create_appearances
|
581
|
+
assert_operators(@widget[:AP][:N].stream, [:show_text, ["1.234.568E"]], range: 9)
|
598
582
|
end
|
599
583
|
|
600
584
|
it "creates the /N appearance stream according to the set string" do
|
@@ -34,6 +34,13 @@ describe HexaPDF::Type::AcroForm::Field do
|
|
34
34
|
assert_equal(:Ch, @field[:FT])
|
35
35
|
end
|
36
36
|
|
37
|
+
it "wraps fields inside the correct subclass" do
|
38
|
+
field = HexaPDF::Type::AcroForm::Field.wrap(@doc, {FT: :Tx})
|
39
|
+
assert_kind_of(HexaPDF::Type::AcroForm::TextField, field)
|
40
|
+
field = HexaPDF::Type::AcroForm::Field.wrap(@doc, {})
|
41
|
+
assert_kind_of(HexaPDF::Type::AcroForm::Field, field)
|
42
|
+
end
|
43
|
+
|
37
44
|
it "has convenience methods for accessing the field flags" do
|
38
45
|
assert_equal([], @field.flags)
|
39
46
|
refute(@field.flagged?(:required))
|
@@ -100,6 +107,10 @@ describe HexaPDF::Type::AcroForm::Field do
|
|
100
107
|
refute(@field.terminal_field?)
|
101
108
|
end
|
102
109
|
|
110
|
+
it "returns itself when asked for the form field" do
|
111
|
+
assert_same(@field, @field.form_field)
|
112
|
+
end
|
113
|
+
|
103
114
|
it "can check whether a widget is embedded in the field" do
|
104
115
|
refute(@field.embedded_widget?)
|
105
116
|
@field[:Subtype] = :Wdiget
|
@@ -342,6 +342,39 @@ describe HexaPDF::Type::AcroForm::Form do
|
|
342
342
|
end
|
343
343
|
end
|
344
344
|
|
345
|
+
describe "recalculate_fields" do
|
346
|
+
before do
|
347
|
+
@text1 = @acro_form.create_text_field('text1')
|
348
|
+
@text2 = @acro_form.create_text_field('text2')
|
349
|
+
@text3 = @acro_form.create_text_field('text3')
|
350
|
+
end
|
351
|
+
|
352
|
+
it "recalculates all fields listed in /CO" do
|
353
|
+
@text1.field_value = "10"
|
354
|
+
@text2.field_value = "30"
|
355
|
+
@text3.set_calculate_action(:sum, fields: ['text1', @text2])
|
356
|
+
@acro_form.recalculate_fields
|
357
|
+
assert_equal("40", @text3.field_value)
|
358
|
+
end
|
359
|
+
|
360
|
+
it "doesn't change the field's value if there is an error" do
|
361
|
+
@text3.set_calculate_action(:sfn, fields: 'text1 - text2')
|
362
|
+
@text3[:AA][:C][:JS] = @text3[:AA][:C][:JS].sub('text1', 'text4')
|
363
|
+
@acro_form.recalculate_fields
|
364
|
+
assert_nil(@text3.field_value)
|
365
|
+
end
|
366
|
+
|
367
|
+
it "works if fields aren't already loaded and correctly wrapped" do
|
368
|
+
@text1.field_value = "10"
|
369
|
+
@text3.set_calculate_action(:sfn, fields: 'text1')
|
370
|
+
@text3[:AA] = {C: HexaPDF::Reference.new(@doc.add(@text3[:AA][:C]).oid)}
|
371
|
+
@acro_form[:CO] = [HexaPDF::Reference.new(@text3.oid, @text3.gen)]
|
372
|
+
@doc.revisions.current.update(@doc.wrap(@text3, type: HexaPDF::Dictionary))
|
373
|
+
@acro_form.recalculate_fields
|
374
|
+
assert_equal("10", @text3.field_value)
|
375
|
+
end
|
376
|
+
end
|
377
|
+
|
345
378
|
describe "perform_validation" do
|
346
379
|
it "checks whether the /DR field is available when /DA is set" do
|
347
380
|
@acro_form[:DA] = 'test'
|
@@ -0,0 +1,226 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
|
3
|
+
require 'test_helper'
|
4
|
+
require 'hexapdf/document'
|
5
|
+
require 'hexapdf/type/acro_form/java_script_actions'
|
6
|
+
|
7
|
+
describe HexaPDF::Type::AcroForm::JavaScriptActions do
|
8
|
+
before do
|
9
|
+
@klass = HexaPDF::Type::AcroForm::JavaScriptActions
|
10
|
+
@action = {S: :JavaScript}
|
11
|
+
end
|
12
|
+
|
13
|
+
describe "format" do
|
14
|
+
it "returns the original value if the format action can't be processed" do
|
15
|
+
@action[:JS] = 'Unknown();'
|
16
|
+
@klass.apply_format("10", @action)
|
17
|
+
end
|
18
|
+
|
19
|
+
describe "AFNumber_Format" do
|
20
|
+
before do
|
21
|
+
@value = '1234567.898765'
|
22
|
+
@action[:JS] = ''
|
23
|
+
end
|
24
|
+
|
25
|
+
it "returns a correct JavaScript string" do
|
26
|
+
assert_equal('AFNumber_Format(2, 0, 0, 0, "", true);',
|
27
|
+
@klass.af_number_format_action)
|
28
|
+
assert_equal('AFNumber_Format(1, 1, 1, 0, "E", false);',
|
29
|
+
@klass.af_number_format_action(decimals: 1, separator_style: :point_no_thousands,
|
30
|
+
negative_style: :red, currency_string: "E",
|
31
|
+
prepend_currency: false))
|
32
|
+
end
|
33
|
+
|
34
|
+
def assert_format(arg_string, result_value, result_color)
|
35
|
+
@action[:JS] = "AFNumber_Format(#{arg_string});"
|
36
|
+
value, text_color = @klass.apply_format(@value, @action)
|
37
|
+
assert_equal(result_value, value)
|
38
|
+
result_color ? assert_equal(result_color, text_color) : assert_nil(text_color)
|
39
|
+
end
|
40
|
+
|
41
|
+
it "works with both commas and points as decimal separator" do
|
42
|
+
@value = '1234567.898'
|
43
|
+
assert_format('2, 2, 0, 0, "", false', "1.234.567,90", "black")
|
44
|
+
@value = '1234567,898'
|
45
|
+
assert_format('2, 2, 0, 0, "", false', "1.234.567,90", "black")
|
46
|
+
@value = '123,4567,898'
|
47
|
+
assert_format('2, 2, 0, 0, "", false', "123,46", "black")
|
48
|
+
end
|
49
|
+
|
50
|
+
it "respects the set number of decimals" do
|
51
|
+
assert_format('0, 2, 0, 0, "E", false', "1.234.568E", "black")
|
52
|
+
assert_format('2, 2, 0, 0, "E", false', "1.234.567,90E", "black")
|
53
|
+
end
|
54
|
+
|
55
|
+
it "respects the digit separator style" do
|
56
|
+
["1,234,567.90", "1234567.90", "1.234.567,90", "1234567,90"].each_with_index do |result, style|
|
57
|
+
assert_format("2, #{style}, 0, 0, \"\", false", result, "black")
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
it "respects the negative value styling" do
|
62
|
+
@value = '-1234567.898'
|
63
|
+
[["-E1234567,90", "black"], ["E1234567,90", "red"], ["(E1234567,90)", "black"],
|
64
|
+
["(E1234567,90)", "red"]].each_with_index do |result, style|
|
65
|
+
assert_format("2, 3, #{style}, 0, \"E\", true", result[0], result[1])
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
it "respects the specified currency string and position" do
|
70
|
+
assert_format('2, 3, 0, 0, " E", false', "1234567,90 E", "black")
|
71
|
+
assert_format('2, 3, 0, 0, "E ", true', "E 1234567,90", "black")
|
72
|
+
end
|
73
|
+
|
74
|
+
it "allows omitting the trailing semicolon" do
|
75
|
+
@action[:JS] = "AFNumber_Format(2,2,0,0,\"\",false )"
|
76
|
+
value, = @klass.apply_format('1234.567', @action)
|
77
|
+
assert_equal('1.234,57', value)
|
78
|
+
end
|
79
|
+
|
80
|
+
it "does nothing to the value if the JavasSript method could not be determined " do
|
81
|
+
assert_format('2, 3, 0, 0, " E", false, a', "1234567.898765", nil)
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
describe "calculate" do
|
87
|
+
before do
|
88
|
+
@doc = HexaPDF::Document.new
|
89
|
+
@form = @doc.acro_form(create: true)
|
90
|
+
@form.create_text_field('text')
|
91
|
+
@field1 = @form.create_text_field('text.1')
|
92
|
+
@field1.field_value = "10"
|
93
|
+
@field2 = @form.create_text_field('text.2')
|
94
|
+
@field2.field_value = "20"
|
95
|
+
@field3 = @form.create_text_field('text.3')
|
96
|
+
@field3.field_value = "30"
|
97
|
+
end
|
98
|
+
|
99
|
+
it "returns nil if the calculate action is not a JavaScript action" do
|
100
|
+
@action[:S] = :GoTo
|
101
|
+
assert_nil(@klass.calculate(@form, @action))
|
102
|
+
end
|
103
|
+
|
104
|
+
it "returns nil if the calculate action contains unknown JavaScript" do
|
105
|
+
@action[:JS] = 'Unknown();'
|
106
|
+
assert_nil(@klass.calculate(@form, @action))
|
107
|
+
end
|
108
|
+
|
109
|
+
describe "predefined calculations" do
|
110
|
+
it "returns a correct JavaScript string" do
|
111
|
+
assert_equal('AFSimple_Calculate("SUM", ["text.1","text.2"]);',
|
112
|
+
@klass.af_simple_calculate_action(:sum, ['text.1', @field2]))
|
113
|
+
end
|
114
|
+
|
115
|
+
def assert_calculation(function, fields, value)
|
116
|
+
fields = fields.map {|field| "\"#{field.full_field_name}\"" }.join(", ")
|
117
|
+
@action[:JS] = "AFSimple_Calculate(\"#{function}\", new Array(#{fields}));"
|
118
|
+
assert_equal(value, @klass.calculate(@form, @action))
|
119
|
+
end
|
120
|
+
|
121
|
+
it "can sum fields" do
|
122
|
+
assert_calculation('SUM', [@field1, @field2, @field3], "60")
|
123
|
+
end
|
124
|
+
|
125
|
+
it "can average fields" do
|
126
|
+
assert_calculation('AVG', [@field1, @field2, @field3], "20")
|
127
|
+
end
|
128
|
+
|
129
|
+
it "can multiply fields" do
|
130
|
+
assert_calculation('PRD', [@field1, @field2, @field3], "6000")
|
131
|
+
end
|
132
|
+
|
133
|
+
it "can find the minimum field value" do
|
134
|
+
assert_calculation('MIN', [@field1, @field2, @field3], "10")
|
135
|
+
end
|
136
|
+
|
137
|
+
it "can find the maximum field value" do
|
138
|
+
assert_calculation('MAX', [@field1, @field2, @field3], "30")
|
139
|
+
end
|
140
|
+
|
141
|
+
it "works with floats" do
|
142
|
+
@field1.field_value = "10,54"
|
143
|
+
assert_calculation('SUM', [@field1, @field2], "30.54")
|
144
|
+
end
|
145
|
+
|
146
|
+
it "returns nil if a field cannot be resolved" do
|
147
|
+
@action[:JS] = 'AFSimple_Calculate("SUM", ["unknown"]);'
|
148
|
+
assert_nil(@klass.calculate(@form, @action))
|
149
|
+
end
|
150
|
+
|
151
|
+
it "allows omitting the trailing semicolon" do
|
152
|
+
@action[:JS] = 'AFSimple_Calculate("SUM", ["text.1"] )'
|
153
|
+
assert_equal("10", @klass.calculate(@form, @action))
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
describe "simplified field notation calculations" do
|
158
|
+
it "returns a correct JavaScript string" do
|
159
|
+
sfn = '(text.1 + text.2) * text.3 - text.1 / text.1 + 0 + 5.43 + 7,24'
|
160
|
+
assert_equal("/** BVCALC #{sfn} EVCALC **/ " \
|
161
|
+
'event.value = (AFMakeNumber(getField("text.1").value) + ' \
|
162
|
+
'AFMakeNumber(getField("text.2").value)) * ' \
|
163
|
+
'AFMakeNumber(getField("text.3").value) - ' \
|
164
|
+
'AFMakeNumber(getField("text.1").value) / ' \
|
165
|
+
'AFMakeNumber(getField("text.1").value) ' \
|
166
|
+
'+ 0.0 + 5.43 + 7.24',
|
167
|
+
@klass.simplified_field_notation_action(@form, sfn))
|
168
|
+
end
|
169
|
+
|
170
|
+
it "fails if the SFN string is invalid when generating a JavaScript action string" do
|
171
|
+
assert_raises(ArgumentError) { @klass.simplified_field_notation_action(@form, '(test') }
|
172
|
+
end
|
173
|
+
|
174
|
+
def assert_calculation(sfn, value)
|
175
|
+
@action[:JS] = "/** BVCALC #{sfn} EVCALC **/"
|
176
|
+
result = @klass.calculate(@form, @action)
|
177
|
+
value ? assert_equal(value, result) : assert_nil(result)
|
178
|
+
end
|
179
|
+
|
180
|
+
it "works for additions" do
|
181
|
+
assert_calculation('text.1 + text.2 + text.1', "40")
|
182
|
+
end
|
183
|
+
|
184
|
+
it "works for substraction" do
|
185
|
+
assert_calculation('text.2-text\.1', "10")
|
186
|
+
end
|
187
|
+
|
188
|
+
it "works for multiplication" do
|
189
|
+
assert_calculation('text.2\* text\.1 * text\.3', "6000")
|
190
|
+
end
|
191
|
+
|
192
|
+
it "works for division" do
|
193
|
+
assert_calculation('text.2 /text\.1', "2")
|
194
|
+
end
|
195
|
+
|
196
|
+
it "works with parentheses" do
|
197
|
+
assert_calculation('(text.2 + (text.1*text.3))', "320")
|
198
|
+
end
|
199
|
+
|
200
|
+
it "works with numbers" do
|
201
|
+
assert_calculation('text.1 + 10.54 - 1,54 + 3', "22")
|
202
|
+
end
|
203
|
+
|
204
|
+
it "works in a more complex case" do
|
205
|
+
assert_calculation('(text.1 + text.2)/(text.3) * text.1', "10")
|
206
|
+
end
|
207
|
+
|
208
|
+
it "works with floats" do
|
209
|
+
@field1.field_value = "10,54"
|
210
|
+
assert_calculation('text.1 + text.2', "30.54")
|
211
|
+
end
|
212
|
+
|
213
|
+
it "fails if a referenced field is not a terminal field" do
|
214
|
+
assert_calculation('text + text.2', nil)
|
215
|
+
end
|
216
|
+
|
217
|
+
it "fails if a referenced field does not exist" do
|
218
|
+
assert_calculation('unknown + text.2', nil)
|
219
|
+
end
|
220
|
+
|
221
|
+
it "fails if parentheses don't match" do
|
222
|
+
assert_calculation('(text.1 + text.2', nil)
|
223
|
+
end
|
224
|
+
end
|
225
|
+
end
|
226
|
+
end
|