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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +45 -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/style.rb +5 -4
- 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/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
|
|
@@ -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
|
|
@@ -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.
|
|
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-
|
|
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
|