hexapdf 0.40.0 → 0.41.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.
Files changed (41) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +45 -0
  3. data/examples/019-acro_form.rb +12 -23
  4. data/examples/027-composer_optional_content.rb +1 -1
  5. data/examples/030-pdfa.rb +6 -6
  6. data/examples/031-acro_form_java_script.rb +101 -0
  7. data/lib/hexapdf/cli/command.rb +11 -0
  8. data/lib/hexapdf/cli/form.rb +38 -9
  9. data/lib/hexapdf/cli/info.rb +4 -0
  10. data/lib/hexapdf/configuration.rb +10 -0
  11. data/lib/hexapdf/content/canvas.rb +2 -0
  12. data/lib/hexapdf/document/layout.rb +8 -1
  13. data/lib/hexapdf/encryption/aes.rb +13 -6
  14. data/lib/hexapdf/encryption/security_handler.rb +6 -4
  15. data/lib/hexapdf/font/cmap/parser.rb +1 -5
  16. data/lib/hexapdf/font/cmap.rb +22 -3
  17. data/lib/hexapdf/font_loader/from_configuration.rb +1 -1
  18. data/lib/hexapdf/font_loader/variant_from_name.rb +72 -0
  19. data/lib/hexapdf/font_loader.rb +1 -0
  20. data/lib/hexapdf/layout/style.rb +5 -4
  21. data/lib/hexapdf/type/acro_form/appearance_generator.rb +11 -76
  22. data/lib/hexapdf/type/acro_form/field.rb +14 -0
  23. data/lib/hexapdf/type/acro_form/form.rb +25 -8
  24. data/lib/hexapdf/type/acro_form/java_script_actions.rb +498 -0
  25. data/lib/hexapdf/type/acro_form/text_field.rb +78 -0
  26. data/lib/hexapdf/type/acro_form.rb +1 -0
  27. data/lib/hexapdf/type/annotations/widget.rb +1 -1
  28. data/lib/hexapdf/version.rb +1 -1
  29. data/test/hexapdf/encryption/test_aes.rb +18 -8
  30. data/test/hexapdf/encryption/test_security_handler.rb +17 -0
  31. data/test/hexapdf/encryption/test_standard_security_handler.rb +1 -1
  32. data/test/hexapdf/font/cmap/test_parser.rb +5 -3
  33. data/test/hexapdf/font/test_cmap.rb +8 -0
  34. data/test/hexapdf/font_loader/test_from_configuration.rb +4 -0
  35. data/test/hexapdf/font_loader/test_variant_from_name.rb +34 -0
  36. data/test/hexapdf/type/acro_form/test_appearance_generator.rb +31 -47
  37. data/test/hexapdf/type/acro_form/test_field.rb +11 -0
  38. data/test/hexapdf/type/acro_form/test_form.rb +33 -0
  39. data/test/hexapdf/type/acro_form/test_java_script_actions.rb +226 -0
  40. data/test/hexapdf/type/acro_form/test_text_field.rb +44 -0
  41. 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].succ!
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
- 2 beginbfrange
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
- assert_equal("\xD8\x40\xDC\x3E".encode("UTF-8", "UTF-16BE"),
86
- cmap.to_unicode(0x3A51))
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
@@ -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 dictionaries are not set up" do
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
- describe "Javascript action AFNumber_Format" do
553
- before do
554
- @field[:V] = '1234567.898765'
555
- @action = {S: :JavaScript, JS: ''}
556
- @field[:AA] = {F: @action}
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
@@ -124,6 +124,10 @@ describe HexaPDF::Type::AcroForm::TextField do
124
124
  assert_raises(HexaPDF::Error) { @field.field_value = 'test' }
125
125
  end
126
126
 
127
+ it "fails if the provided value is not a string" do
128
+ assert_raises(HexaPDF::Error) { @field.field_value = 10 }
129
+ end
130
+
127
131
  it "fails if the value exceeds the length set by /MaxLen" do
128
132
  @field[:MaxLen] = 5
129
133
  assert_raises(HexaPDF::Error) { @field.field_value = 'testdf' }
@@ -197,6 +201,46 @@ describe HexaPDF::Type::AcroForm::TextField do
197
201
  end
198
202
  end
199
203
 
204
+ describe "set_format_action" do
205
+ it "applies the number format" do
206
+ @doc.acro_form(create: true)
207
+ @field.set_format_action(:number, decimals: 0)
208
+ assert(@field.key?(:AA))
209
+ assert(@field[:AA].key?(:F))
210
+ assert_equal('AFNumber_Format(0, 0, 0, 0, "", true);', @field[:AA][:F][:JS])
211
+ end
212
+
213
+ it "fails if an unknown format action is specified" do
214
+ assert_raises(ArgumentError) { @field.set_format_action(:unknown) }
215
+ end
216
+ end
217
+
218
+ describe "set_calculate_action" do
219
+ before do
220
+ @form = @doc.acro_form(create: true)
221
+ @form.create_text_field('text1')
222
+ @form.create_text_field('text2')
223
+ end
224
+
225
+ it "sets the calculate action using AFSimple_Calculate" do
226
+ @field.set_calculate_action(:sum, fields: ['text1', @form.field_by_name('text2')])
227
+ assert(@field.key?(:AA))
228
+ assert(@field[:AA].key?(:C))
229
+ assert_equal('AFSimple_Calculate("SUM", ["text1","text2"]);', @field[:AA][:C][:JS])
230
+ assert_equal([@field], @form[:CO].value)
231
+ end
232
+
233
+ it "sets the simplified field notation calculate action" do
234
+ @field.set_calculate_action(:sfn, fields: "text1")
235
+ assert_equal('/** BVCALC text1 EVCALC **/ event.value = AFMakeNumber(getField("text1").value)',
236
+ @field[:AA][:C][:JS])
237
+ end
238
+
239
+ it "fails if an unknown calculate action is specified" do
240
+ assert_raises(ArgumentError) { @field.set_calculate_action(:unknown) }
241
+ end
242
+ end
243
+
200
244
  describe "validation" do
201
245
  it "checks the value of the /FT field" do
202
246
  @field.delete(:FT)
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: hexapdf
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.40.0
4
+ version: 0.41.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Thomas Leitner
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2024-03-23 00:00:00.000000000 Z
11
+ date: 2024-05-05 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: cmdparse
@@ -306,6 +306,7 @@ files:
306
306
  - examples/028-frame_mask_mode.rb
307
307
  - examples/029-composer_fallback_fonts.rb
308
308
  - examples/030-pdfa.rb
309
+ - examples/031-acro_form_java_script.rb
309
310
  - examples/emoji-smile.png
310
311
  - examples/emoji-wink.png
311
312
  - examples/machupicchu.jpg
@@ -429,6 +430,7 @@ files:
429
430
  - lib/hexapdf/font_loader/from_configuration.rb
430
431
  - lib/hexapdf/font_loader/from_file.rb
431
432
  - lib/hexapdf/font_loader/standard14.rb
433
+ - lib/hexapdf/font_loader/variant_from_name.rb
432
434
  - lib/hexapdf/image_loader.rb
433
435
  - lib/hexapdf/image_loader/jpeg.rb
434
436
  - lib/hexapdf/image_loader/pdf.rb
@@ -477,6 +479,7 @@ files:
477
479
  - lib/hexapdf/type/acro_form/choice_field.rb
478
480
  - lib/hexapdf/type/acro_form/field.rb
479
481
  - lib/hexapdf/type/acro_form/form.rb
482
+ - lib/hexapdf/type/acro_form/java_script_actions.rb
480
483
  - lib/hexapdf/type/acro_form/signature_field.rb
481
484
  - lib/hexapdf/type/acro_form/text_field.rb
482
485
  - lib/hexapdf/type/acro_form/variable_text_field.rb
@@ -698,6 +701,7 @@ files:
698
701
  - test/hexapdf/font_loader/test_from_configuration.rb
699
702
  - test/hexapdf/font_loader/test_from_file.rb
700
703
  - test/hexapdf/font_loader/test_standard14.rb
704
+ - test/hexapdf/font_loader/test_variant_from_name.rb
701
705
  - test/hexapdf/image_loader/test_jpeg.rb
702
706
  - test/hexapdf/image_loader/test_pdf.rb
703
707
  - test/hexapdf/image_loader/test_png.rb
@@ -748,6 +752,7 @@ files:
748
752
  - test/hexapdf/type/acro_form/test_choice_field.rb
749
753
  - test/hexapdf/type/acro_form/test_field.rb
750
754
  - test/hexapdf/type/acro_form/test_form.rb
755
+ - test/hexapdf/type/acro_form/test_java_script_actions.rb
751
756
  - test/hexapdf/type/acro_form/test_signature_field.rb
752
757
  - test/hexapdf/type/acro_form/test_text_field.rb
753
758
  - test/hexapdf/type/acro_form/test_variable_text_field.rb