hexapdf 0.40.0 → 0.42.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 +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'
|