hexapdf 0.40.0 → 0.42.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 +71 -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 +113 -0
- data/lib/hexapdf/cli/command.rb +25 -11
- data/lib/hexapdf/cli/files.rb +31 -7
- data/lib/hexapdf/cli/form.rb +46 -38
- data/lib/hexapdf/cli/info.rb +4 -0
- 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 +11 -1
- 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/button_field.rb +7 -5
- data/lib/hexapdf/type/acro_form/field.rb +14 -0
- data/lib/hexapdf/type/acro_form/form.rb +70 -8
- data/lib/hexapdf/type/acro_form/java_script_actions.rb +649 -0
- data/lib/hexapdf/type/acro_form/text_field.rb +90 -0
- data/lib/hexapdf/type/acro_form.rb +1 -0
- data/lib/hexapdf/type/annotations/widget.rb +1 -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/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/test_utils.rb +16 -0
- data/test/hexapdf/type/acro_form/test_appearance_generator.rb +31 -47
- data/test/hexapdf/type/acro_form/test_button_field.rb +5 -0
- data/test/hexapdf/type/acro_form/test_field.rb +11 -0
- data/test/hexapdf/type/acro_form/test_form.rb +80 -0
- data/test/hexapdf/type/acro_form/test_java_script_actions.rb +327 -0
- data/test/hexapdf/type/acro_form/test_text_field.rb +62 -0
- data/test/hexapdf/type/test_resources.rb +5 -0
- metadata +8 -2
@@ -36,6 +36,7 @@
|
|
36
36
|
|
37
37
|
require 'hexapdf/error'
|
38
38
|
require 'hexapdf/type/acro_form/variable_text_field'
|
39
|
+
require 'hexapdf/type/acro_form/java_script_actions'
|
39
40
|
|
40
41
|
module HexaPDF
|
41
42
|
module Type
|
@@ -168,6 +169,8 @@ module HexaPDF
|
|
168
169
|
raise HexaPDF::Error, "Storing a field value for a password field is not allowed"
|
169
170
|
elsif comb_text_field? && !key?(:MaxLen)
|
170
171
|
raise HexaPDF::Error, "A comb text field need a valid /MaxLen value"
|
172
|
+
elsif str && !str.kind_of?(String)
|
173
|
+
str = @document.config['acro_form.on_invalid_value'].call(self, str)
|
171
174
|
end
|
172
175
|
str = str.gsub(/[[:space:]]/, ' ') if str && concrete_field_type == :single_line_text_field
|
173
176
|
if key?(:MaxLen) && str && str.length > self[:MaxLen]
|
@@ -241,6 +244,93 @@ module HexaPDF
|
|
241
244
|
create_appearances(force: true)
|
242
245
|
end
|
243
246
|
|
247
|
+
# Sets the specified JavaScript format action on the field's widgets.
|
248
|
+
#
|
249
|
+
# This action is executed when the field value needs to be formatted for rendering in the
|
250
|
+
# appearance streams of the associated widgets.
|
251
|
+
#
|
252
|
+
# The argument +type+ can be one of the following:
|
253
|
+
#
|
254
|
+
# :number::
|
255
|
+
# Assumes that the field value is a number and formats it according to the given
|
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.
|
267
|
+
def set_format_action(type, **arguments)
|
268
|
+
action_string = case type
|
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)
|
272
|
+
else
|
273
|
+
raise ArgumentError, "Invalid value for type argument: #{type.inspect}"
|
274
|
+
end
|
275
|
+
self[:AA] ||= {}
|
276
|
+
self[:AA][:F] = {S: :JavaScript, JS: action_string}
|
277
|
+
end
|
278
|
+
|
279
|
+
# Sets the specified JavaScript calculate action on the field.
|
280
|
+
#
|
281
|
+
# This action is executed by a viewer when any field's value changes so as to recalculate
|
282
|
+
# the value of this field. Usually, the field is also flagged as read only to avoid a user
|
283
|
+
# changing the value manually.
|
284
|
+
#
|
285
|
+
# Note that HexaPDF *doesn't* automatically recalculate field values, use
|
286
|
+
# Form#recalculate_fields to manually kick off recalculation.
|
287
|
+
#
|
288
|
+
# The argument +type+ can be one of the following:
|
289
|
+
#
|
290
|
+
# :sum::
|
291
|
+
# Sums the values of the given +fields+.
|
292
|
+
#
|
293
|
+
# :average::
|
294
|
+
# Calculates the average value of the given +fields+.
|
295
|
+
#
|
296
|
+
# :product::
|
297
|
+
# Multiplies the values of the given +fields+.
|
298
|
+
#
|
299
|
+
# :min::
|
300
|
+
# Uses the minimum value of the given +fields+.
|
301
|
+
#
|
302
|
+
# :max::
|
303
|
+
# Uses the maximum value of the given +fields+.
|
304
|
+
#
|
305
|
+
# :sfn::
|
306
|
+
# Uses the Simplified Field Notation for calculating the field's value. This allows for
|
307
|
+
# more complex calculations involving addition, subtraction, multiplication and
|
308
|
+
# division. Field values are specified by using the full field names which should not
|
309
|
+
# contain spaces or punctuation characters except point.
|
310
|
+
#
|
311
|
+
# The +fields+ argument needs to contain a string with the SFN calculation rule.
|
312
|
+
#
|
313
|
+
# Here are some examples:
|
314
|
+
#
|
315
|
+
# field1 + field2 - field3
|
316
|
+
# (field.1 + field.2) * (field.3 - field.4)
|
317
|
+
#
|
318
|
+
# Note: Setting this action appends the field to the main Form's /CO entry which specifies
|
319
|
+
# the calculation order. Rearrange the entries as needed.
|
320
|
+
def set_calculate_action(type, fields: nil)
|
321
|
+
action_string = case type
|
322
|
+
when :sum, :average, :product, :min, :max
|
323
|
+
JavaScriptActions.af_simple_calculate_action(type, fields)
|
324
|
+
when :sfn
|
325
|
+
JavaScriptActions.simplified_field_notation_action(document.acro_form, fields)
|
326
|
+
else
|
327
|
+
raise ArgumentError, "Invalid value for type argument: #{type.inspect}"
|
328
|
+
end
|
329
|
+
self[:AA] ||= {}
|
330
|
+
self[:AA][:C] = {S: :JavaScript, JS: action_string}
|
331
|
+
(document.acro_form[:CO] ||= []) << self
|
332
|
+
end
|
333
|
+
|
244
334
|
private
|
245
335
|
|
246
336
|
def perform_validation #:nodoc:
|
@@ -85,7 +85,7 @@ module HexaPDF
|
|
85
85
|
define_field :BS, type: :Border, version: '1.2'
|
86
86
|
define_field :Parent, type: Dictionary
|
87
87
|
|
88
|
-
#
|
88
|
+
# Returns the AcroForm field object to which this widget annotation belongs.
|
89
89
|
#
|
90
90
|
# Since a widget and a field can share the same dictionary object, the returned object is
|
91
91
|
# often just the widget re-wrapped in the correct field class.
|
@@ -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
@@ -17,7 +17,6 @@ describe HexaPDF::Encryption::AES do
|
|
17
17
|
end
|
18
18
|
|
19
19
|
def process(data)
|
20
|
-
raise "invalid data" if data.empty? || data.length % 16 != 0
|
21
20
|
data
|
22
21
|
end
|
23
22
|
end
|
@@ -56,10 +55,17 @@ describe HexaPDF::Encryption::AES do
|
|
56
55
|
assert_equal('', @algorithm_class.decrypt('some key' * 2, 'iv' * 8))
|
57
56
|
end
|
58
57
|
|
59
|
-
it "
|
58
|
+
it "handles invalid files where not enough bytes are provided" do
|
59
|
+
@algorithm_class.decrypt('some' * 4, 'a' * 36) do |msg|
|
60
|
+
assert_match(/32 \+ 16/, msg)
|
61
|
+
false
|
62
|
+
end
|
60
63
|
assert_raises(HexaPDF::EncryptionError) do
|
61
64
|
@algorithm_class.decrypt('some' * 4, 'no iv')
|
62
65
|
end
|
66
|
+
assert_raises(HexaPDF::EncryptionError) do
|
67
|
+
@algorithm_class.decrypt('some' * 4, 'no iv') { true }
|
68
|
+
end
|
63
69
|
end
|
64
70
|
end
|
65
71
|
|
@@ -126,12 +132,16 @@ describe HexaPDF::Encryption::AES do
|
|
126
132
|
assert_equal('', collector(@algorithm_class.decryption_fiber('key', Fiber.new { '' })))
|
127
133
|
end
|
128
134
|
|
129
|
-
it "
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
+
it "handles invalid files where not enough bytes are provided" do
|
136
|
+
collector(@algorithm_class.decryption_fiber('some' * 4, Fiber.new { 'a' * 40 }) do |msg|
|
137
|
+
assert_match(/32 \+ 16/, msg)
|
138
|
+
false
|
139
|
+
end)
|
140
|
+
assert_raises(HexaPDF::EncryptionError) do
|
141
|
+
collector(@algorithm_class.decryption_fiber('some' * 4, Fiber.new { 'a' * 40 }))
|
142
|
+
end
|
143
|
+
assert_raises(HexaPDF::EncryptionError) do
|
144
|
+
collector(@algorithm_class.decryption_fiber('some' * 4, Fiber.new { 'a' * 40 })) { true }
|
135
145
|
end
|
136
146
|
end
|
137
147
|
end
|
@@ -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
|
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))
|
@@ -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
|
@@ -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
|
@@ -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
|
@@ -241,6 +241,53 @@ describe HexaPDF::Type::AcroForm::Form do
|
|
241
241
|
end
|
242
242
|
end
|
243
243
|
|
244
|
+
describe "fill" do
|
245
|
+
it "works for text field types" do
|
246
|
+
field = @acro_form.create_text_field('test')
|
247
|
+
@acro_form.fill("test" => "value")
|
248
|
+
assert_equal("value", field.field_value)
|
249
|
+
end
|
250
|
+
|
251
|
+
it "works for radio buttons" do
|
252
|
+
field = @acro_form.create_radio_button("test")
|
253
|
+
field.create_widget(@doc.pages.add, value: :name)
|
254
|
+
@acro_form.fill("test" => "name")
|
255
|
+
assert_equal(:name, field.field_value)
|
256
|
+
end
|
257
|
+
|
258
|
+
it "works for check boxes" do
|
259
|
+
field = @acro_form.create_check_box('test')
|
260
|
+
field.create_widget(@doc.pages.add)
|
261
|
+
|
262
|
+
["t", "true", "y", "yes"].each do |value|
|
263
|
+
@acro_form.fill("test" => value)
|
264
|
+
assert_equal(:Yes, field.field_value)
|
265
|
+
field.field_value = :Off
|
266
|
+
end
|
267
|
+
|
268
|
+
["f", "false", "n", "no"].each do |value|
|
269
|
+
@acro_form.fill("test" => value)
|
270
|
+
assert_nil(field.field_value)
|
271
|
+
field.field_value = :Yes
|
272
|
+
end
|
273
|
+
|
274
|
+
field.create_widget(@doc.pages.add, value: :Other)
|
275
|
+
@acro_form.fill("test" => "Other")
|
276
|
+
assert_equal(:Other, field.field_value)
|
277
|
+
end
|
278
|
+
|
279
|
+
it "raises an error if a field is not found" do
|
280
|
+
error = assert_raises(HexaPDF::Error) { @acro_form.fill("unknown" => "test") }
|
281
|
+
assert_match(/named 'unknown' not found/, error.message)
|
282
|
+
end
|
283
|
+
|
284
|
+
it "raises an error if a field type is not supported for filling in" do
|
285
|
+
@acro_form.create_check_box('test').initialize_as_push_button
|
286
|
+
error = assert_raises(HexaPDF::Error) { @acro_form.fill("test" => "test") }
|
287
|
+
assert_match(/push_button not yet supported/, error.message)
|
288
|
+
end
|
289
|
+
end
|
290
|
+
|
244
291
|
it "returns the default resources" do
|
245
292
|
assert_kind_of(HexaPDF::Type::Resources, @acro_form.default_resources)
|
246
293
|
end
|
@@ -342,6 +389,39 @@ describe HexaPDF::Type::AcroForm::Form do
|
|
342
389
|
end
|
343
390
|
end
|
344
391
|
|
392
|
+
describe "recalculate_fields" do
|
393
|
+
before do
|
394
|
+
@text1 = @acro_form.create_text_field('text1')
|
395
|
+
@text2 = @acro_form.create_text_field('text2')
|
396
|
+
@text3 = @acro_form.create_text_field('text3')
|
397
|
+
end
|
398
|
+
|
399
|
+
it "recalculates all fields listed in /CO" do
|
400
|
+
@text1.field_value = "10"
|
401
|
+
@text2.field_value = "30"
|
402
|
+
@text3.set_calculate_action(:sum, fields: ['text1', @text2])
|
403
|
+
@acro_form.recalculate_fields
|
404
|
+
assert_equal("40", @text3.field_value)
|
405
|
+
end
|
406
|
+
|
407
|
+
it "doesn't change the field's value if there is an error" do
|
408
|
+
@text3.set_calculate_action(:sfn, fields: 'text1 - text2')
|
409
|
+
@text3[:AA][:C][:JS] = @text3[:AA][:C][:JS].sub('text1', 'text4')
|
410
|
+
@acro_form.recalculate_fields
|
411
|
+
assert_nil(@text3.field_value)
|
412
|
+
end
|
413
|
+
|
414
|
+
it "works if fields aren't already loaded and correctly wrapped" do
|
415
|
+
@text1.field_value = "10"
|
416
|
+
@text3.set_calculate_action(:sfn, fields: 'text1')
|
417
|
+
@text3[:AA] = {C: HexaPDF::Reference.new(@doc.add(@text3[:AA][:C]).oid)}
|
418
|
+
@acro_form[:CO] = [HexaPDF::Reference.new(@text3.oid, @text3.gen)]
|
419
|
+
@doc.revisions.current.update(@doc.wrap(@text3, type: HexaPDF::Dictionary))
|
420
|
+
@acro_form.recalculate_fields
|
421
|
+
assert_equal("10", @text3.field_value)
|
422
|
+
end
|
423
|
+
end
|
424
|
+
|
345
425
|
describe "perform_validation" do
|
346
426
|
it "checks whether the /DR field is available when /DA is set" do
|
347
427
|
@acro_form[:DA] = 'test'
|