hexapdf 0.14.4 → 0.15.4
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 +68 -0
- data/lib/hexapdf/cli/form.rb +30 -8
- data/lib/hexapdf/configuration.rb +18 -3
- data/lib/hexapdf/content/canvas.rb +1 -0
- data/lib/hexapdf/encryption/standard_security_handler.rb +16 -0
- data/lib/hexapdf/error.rb +4 -3
- data/lib/hexapdf/parser.rb +18 -6
- data/lib/hexapdf/revision.rb +16 -0
- data/lib/hexapdf/type/acro_form.rb +1 -0
- data/lib/hexapdf/type/acro_form/appearance_generator.rb +29 -17
- data/lib/hexapdf/type/acro_form/button_field.rb +8 -4
- data/lib/hexapdf/type/acro_form/field.rb +1 -0
- data/lib/hexapdf/type/acro_form/form.rb +37 -0
- data/lib/hexapdf/type/acro_form/signature_field.rb +223 -0
- data/lib/hexapdf/type/annotation.rb +18 -9
- data/lib/hexapdf/type/annotations/widget.rb +3 -1
- data/lib/hexapdf/type/font_descriptor.rb +9 -2
- data/lib/hexapdf/type/page.rb +81 -0
- data/lib/hexapdf/utils/graphics_helpers.rb +4 -4
- data/lib/hexapdf/version.rb +1 -1
- data/test/hexapdf/content/test_canvas.rb +21 -0
- data/test/hexapdf/encryption/test_standard_security_handler.rb +27 -0
- data/test/hexapdf/test_parser.rb +23 -3
- data/test/hexapdf/test_revision.rb +21 -0
- data/test/hexapdf/test_writer.rb +2 -2
- data/test/hexapdf/type/acro_form/test_appearance_generator.rb +21 -2
- data/test/hexapdf/type/acro_form/test_button_field.rb +13 -7
- data/test/hexapdf/type/acro_form/test_field.rb +5 -0
- data/test/hexapdf/type/acro_form/test_form.rb +46 -2
- data/test/hexapdf/type/acro_form/test_signature_field.rb +38 -0
- data/test/hexapdf/type/annotations/test_widget.rb +2 -0
- data/test/hexapdf/type/test_annotation.rb +24 -10
- data/test/hexapdf/type/test_font_descriptor.rb +7 -0
- data/test/hexapdf/type/test_page.rb +187 -49
- data/test/hexapdf/utils/test_graphics_helpers.rb +8 -0
- metadata +4 -2
|
@@ -47,18 +47,18 @@ module HexaPDF
|
|
|
47
47
|
#
|
|
48
48
|
# +rwidth+::
|
|
49
49
|
# The requested width. If +rheight+ is not specified, it is chosen so that the aspect
|
|
50
|
-
# ratio is maintained
|
|
50
|
+
# ratio is maintained. In case of +width+ begin zero, +height+ is used for the height.
|
|
51
51
|
#
|
|
52
52
|
# +rheight+::
|
|
53
53
|
# The requested height. If +rwidth+ is not specified, it is chosen so that the aspect
|
|
54
|
-
# ratio is maintained
|
|
54
|
+
# ratio is maintained. In case of +height+ begin zero, +width+ is used for the width.
|
|
55
55
|
def calculate_dimensions(width, height, rwidth: nil, rheight: nil)
|
|
56
56
|
if rwidth && rheight
|
|
57
57
|
[rwidth, rheight]
|
|
58
58
|
elsif rwidth
|
|
59
|
-
[rwidth, height * rwidth / width.to_f]
|
|
59
|
+
[rwidth, width == 0 ? height : height * rwidth / width.to_f]
|
|
60
60
|
elsif rheight
|
|
61
|
-
[width * rheight / height.to_f, rheight]
|
|
61
|
+
[height == 0 ? width : width * rheight / height.to_f, rheight]
|
|
62
62
|
else
|
|
63
63
|
[width, height]
|
|
64
64
|
end
|
data/lib/hexapdf/version.rb
CHANGED
|
@@ -831,6 +831,17 @@ describe HexaPDF::Content::Canvas do
|
|
|
831
831
|
[:restore_graphics_state]])
|
|
832
832
|
end
|
|
833
833
|
|
|
834
|
+
it "doesn't do anything if the image's width or height is zero" do
|
|
835
|
+
@image[:Width] = 0
|
|
836
|
+
@canvas.xobject(@image, at: [0, 0])
|
|
837
|
+
assert_operators(@page.contents, [])
|
|
838
|
+
|
|
839
|
+
@image[:Width] = 10
|
|
840
|
+
@image[:Height] = 0
|
|
841
|
+
@canvas.xobject(@image, at: [0, 0])
|
|
842
|
+
assert_operators(@page.contents, [])
|
|
843
|
+
end
|
|
844
|
+
|
|
834
845
|
it "correctly serializes the form with no options" do
|
|
835
846
|
@canvas.xobject(@form, at: [1, 2])
|
|
836
847
|
assert_operators(@page.contents, [[:save_graphics_state],
|
|
@@ -862,6 +873,16 @@ describe HexaPDF::Content::Canvas do
|
|
|
862
873
|
[:paint_xobject, [:XO1]],
|
|
863
874
|
[:restore_graphics_state]])
|
|
864
875
|
end
|
|
876
|
+
|
|
877
|
+
it "doesn't do anything if the form's width or height is zero" do
|
|
878
|
+
@form[:BBox] = [100, 50, 100, 200]
|
|
879
|
+
@canvas.xobject(@form, at: [0, 0])
|
|
880
|
+
assert_operators(@page.contents, [])
|
|
881
|
+
|
|
882
|
+
@form[:BBox] = [100, 50, 150, 50]
|
|
883
|
+
@canvas.xobject(@form, at: [0, 0])
|
|
884
|
+
assert_operators(@page.contents, [])
|
|
885
|
+
end
|
|
865
886
|
end
|
|
866
887
|
|
|
867
888
|
describe "character_spacing" do
|
|
@@ -292,4 +292,31 @@ describe HexaPDF::Encryption::StandardSecurityHandler do
|
|
|
292
292
|
@handler.set_up_encryption(permissions: perms)
|
|
293
293
|
assert_equal([:copy_content, :modify_content], @handler.permissions.sort)
|
|
294
294
|
end
|
|
295
|
+
|
|
296
|
+
describe "handling of metadata streams" do
|
|
297
|
+
before do
|
|
298
|
+
@doc = HexaPDF::Document.new
|
|
299
|
+
@output = StringIO.new(''.b)
|
|
300
|
+
end
|
|
301
|
+
|
|
302
|
+
it "doesn't decrypt or encrypt a metadata stream if /EncryptMetadata is false" do
|
|
303
|
+
@doc.encrypt(encrypt_metadata: false)
|
|
304
|
+
@doc.catalog[:Metadata] = @doc.wrap({Type: :Metadata, Subtype: :XML}, stream: "HELLODATA")
|
|
305
|
+
@doc.write(@output)
|
|
306
|
+
assert_match(/stream\nHELLODATA\nendstream/, @output.string)
|
|
307
|
+
|
|
308
|
+
doc = HexaPDF::Document.new(io: @output)
|
|
309
|
+
assert_equal('HELLODATA', doc.catalog[:Metadata].stream)
|
|
310
|
+
end
|
|
311
|
+
|
|
312
|
+
it "doesn't modify decryption/encryption for metadata streams if /V is not 4 or 5" do
|
|
313
|
+
@doc.encrypt(encrypt_metadata: false, algorithm: :arc4)
|
|
314
|
+
@doc.catalog[:Metadata] = @doc.wrap({Type: :Metadata, Subtype: :XML}, stream: "HELLODATA")
|
|
315
|
+
@doc.write(@output)
|
|
316
|
+
refute_match(/stream\nHELLODATA\nendstream/, @output.string)
|
|
317
|
+
|
|
318
|
+
doc = HexaPDF::Document.new(io: @output)
|
|
319
|
+
assert_equal('HELLODATA', doc.catalog[:Metadata].stream)
|
|
320
|
+
end
|
|
321
|
+
end
|
|
295
322
|
end
|
data/test/hexapdf/test_parser.rb
CHANGED
|
@@ -50,7 +50,8 @@ describe HexaPDF::Parser do
|
|
|
50
50
|
end
|
|
51
51
|
|
|
52
52
|
def create_parser(str)
|
|
53
|
-
@
|
|
53
|
+
@parse_io = StringIO.new(str)
|
|
54
|
+
@parser = HexaPDF::Parser.new(@parse_io, @document)
|
|
54
55
|
end
|
|
55
56
|
|
|
56
57
|
describe "parse_indirect_object" do
|
|
@@ -94,6 +95,12 @@ describe HexaPDF::Parser do
|
|
|
94
95
|
assert_equal('12', TestHelper.collector(stream.fiber))
|
|
95
96
|
end
|
|
96
97
|
|
|
98
|
+
it "handles keyword stream followed by space and CR LF" do
|
|
99
|
+
create_parser("1 0 obj<</Length 2>> stream \r\n12\nendstream endobj")
|
|
100
|
+
*, stream = @parser.parse_indirect_object
|
|
101
|
+
assert_equal('12', TestHelper.collector(stream.fiber))
|
|
102
|
+
end
|
|
103
|
+
|
|
97
104
|
it "handles invalid indirect object value consisting of number followed by endobj without space" do
|
|
98
105
|
create_parser("1 0 obj 749endobj")
|
|
99
106
|
object, * = @parser.parse_indirect_object
|
|
@@ -166,7 +173,13 @@ describe HexaPDF::Parser do
|
|
|
166
173
|
it "fails if keyword stream is followed by space and CR or LF instead of LF or CR/LF" do
|
|
167
174
|
create_parser("1 0 obj<</Length 2>> stream \n12\nendstream endobj")
|
|
168
175
|
exp = assert_raises(HexaPDF::MalformedPDFError) { @parser.parse_indirect_object }
|
|
169
|
-
assert_match(/
|
|
176
|
+
assert_match(/followed by space instead/, exp.message)
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
it "fails if keyword stream is followed by space and CR LF instead of LF or CR/LF" do
|
|
180
|
+
create_parser("1 0 obj<</Length 2>> stream \r\n12\nendstream endobj")
|
|
181
|
+
exp = assert_raises(HexaPDF::MalformedPDFError) { @parser.parse_indirect_object }
|
|
182
|
+
assert_match(/followed by space instead/, exp.message)
|
|
170
183
|
end
|
|
171
184
|
|
|
172
185
|
it "fails for numbers followed by endobj without space" do
|
|
@@ -511,6 +524,13 @@ describe HexaPDF::Parser do
|
|
|
511
524
|
assert_match(/not a cross-reference stream/, exp.message)
|
|
512
525
|
end
|
|
513
526
|
|
|
527
|
+
it "fails if the cross-reference stream is missing data" do
|
|
528
|
+
@parse_io.string[287..288] = ''
|
|
529
|
+
exp = assert_raises(HexaPDF::MalformedPDFError) { @parser.load_revision(212) }
|
|
530
|
+
assert_match(/missing data/, exp.message)
|
|
531
|
+
assert_equal(212, exp.pos)
|
|
532
|
+
end
|
|
533
|
+
|
|
514
534
|
it "fails on strict parsing if the cross-reference stream doesn't contain an entry for itself" do
|
|
515
535
|
@document.config['parser.on_correctable_error'] = proc { true }
|
|
516
536
|
create_parser("2 0 obj\n<</Type/XRef/Length 3/W [1 1 1]/Size 1>>" \
|
|
@@ -578,7 +598,7 @@ describe HexaPDF::Parser do
|
|
|
578
598
|
end
|
|
579
599
|
|
|
580
600
|
it "uses the first trailer in case of a linearized file" do
|
|
581
|
-
create_parser("
|
|
601
|
+
create_parser("1 0 obj\n<</Linearized true>>\nendobj\ntrailer <</Size 1/Prev 342>>\ntrailer <</Size 2>>")
|
|
582
602
|
assert_equal({Size: 1}, @parser.reconstructed_revision.trailer.value)
|
|
583
603
|
end
|
|
584
604
|
|
|
@@ -105,6 +105,27 @@ describe HexaPDF::Revision do
|
|
|
105
105
|
end
|
|
106
106
|
end
|
|
107
107
|
|
|
108
|
+
describe "update" do
|
|
109
|
+
before do
|
|
110
|
+
@rev.add(@obj)
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
it "updates the object if it has the same data instance" do
|
|
114
|
+
x = HexaPDF::Object.new(@obj.data)
|
|
115
|
+
y = @rev.update(x)
|
|
116
|
+
assert_same(x, y)
|
|
117
|
+
refute_same(x, @obj)
|
|
118
|
+
assert_same(x, @rev.object(@ref))
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
it "doesn't update the object if it refers to a different data instance" do
|
|
122
|
+
x = HexaPDF::Object.new(:value, oid: 5)
|
|
123
|
+
assert_nil(@rev.update(x))
|
|
124
|
+
x.data.oid = 1
|
|
125
|
+
assert_nil(@rev.update(x))
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
|
|
108
129
|
describe "delete" do
|
|
109
130
|
before do
|
|
110
131
|
@rev.add(@obj)
|
data/test/hexapdf/test_writer.rb
CHANGED
|
@@ -40,7 +40,7 @@ describe HexaPDF::Writer do
|
|
|
40
40
|
219
|
|
41
41
|
%%EOF
|
|
42
42
|
3 0 obj
|
|
43
|
-
<</Producer(HexaPDF version 0.
|
|
43
|
+
<</Producer(HexaPDF version 0.15.4)>>
|
|
44
44
|
endobj
|
|
45
45
|
xref
|
|
46
46
|
3 1
|
|
@@ -72,7 +72,7 @@ describe HexaPDF::Writer do
|
|
|
72
72
|
141
|
|
73
73
|
%%EOF
|
|
74
74
|
6 0 obj
|
|
75
|
-
<</Producer(HexaPDF version 0.
|
|
75
|
+
<</Producer(HexaPDF version 0.15.4)>>
|
|
76
76
|
endobj
|
|
77
77
|
2 0 obj
|
|
78
78
|
<</Length 10>>stream
|
|
@@ -748,17 +748,36 @@ describe HexaPDF::Type::AcroForm::AppearanceGenerator do
|
|
|
748
748
|
|
|
749
749
|
describe "font resolution in case the referenced font is not usable" do
|
|
750
750
|
before do
|
|
751
|
-
@doc.config['acro_form.fallback_font'] = ['Times', {variant: :
|
|
751
|
+
@doc.config['acro_form.fallback_font'] = ['Times', {variant: :italic}]
|
|
752
752
|
@field[:V] = 'Test'
|
|
753
753
|
end
|
|
754
754
|
|
|
755
755
|
it "uses the fallback font if the font is not usable" do
|
|
756
756
|
def (@form.default_resources.font(:F1)).font_wrapper; nil; end
|
|
757
757
|
@generator.create_appearances
|
|
758
|
-
assert_equal(:'Times-
|
|
758
|
+
assert_equal(:'Times-Italic', @widget[:AP][:N][:Resources][:Font][:F2][:BaseFont])
|
|
759
759
|
end
|
|
760
760
|
|
|
761
761
|
it "uses the fallback font if the font is not found" do
|
|
762
|
+
@form.default_resources[:Font].delete(:F1)
|
|
763
|
+
@generator.create_appearances
|
|
764
|
+
assert_equal(:'Times-Italic', @widget[:AP][:N][:Resources][:Font][:F1][:BaseFont])
|
|
765
|
+
end
|
|
766
|
+
|
|
767
|
+
it "respects a simple fallback font name" do
|
|
768
|
+
@doc.config['acro_form.fallback_font'] = 'Times'
|
|
769
|
+
@form.default_resources[:Font].delete(:F1)
|
|
770
|
+
@generator.create_appearances
|
|
771
|
+
assert_equal(:'Times-Roman', @widget[:AP][:N][:Resources][:Font][:F1][:BaseFont])
|
|
772
|
+
end
|
|
773
|
+
|
|
774
|
+
it "respects a fallback font callable object" do
|
|
775
|
+
field = @field
|
|
776
|
+
@doc.config['acro_form.fallback_font'] = proc do |field_arg, font_arg|
|
|
777
|
+
assert_same(field.data, field_arg.data)
|
|
778
|
+
assert_nil(font_arg)
|
|
779
|
+
'Times'
|
|
780
|
+
end
|
|
762
781
|
@form.default_resources[:Font].delete(:F1)
|
|
763
782
|
@generator.create_appearances
|
|
764
783
|
assert_equal(:'Times-Roman', @widget[:AP][:N][:Resources][:Font][:F1][:BaseFont])
|
|
@@ -225,20 +225,26 @@ describe HexaPDF::Type::AcroForm::ButtonField do
|
|
|
225
225
|
it "won't generate appearances if they already exist" do
|
|
226
226
|
widget = @field.create_widget(@doc.pages.add, Rect: [0, 0, 0, 0])
|
|
227
227
|
@field.create_appearances
|
|
228
|
-
yes = widget.
|
|
229
|
-
off = widget.
|
|
230
|
-
widget.appearance.normal_appearance.delete(:Off)
|
|
228
|
+
yes = widget.appearance_dict.normal_appearance[:Yes]
|
|
229
|
+
off = widget.appearance_dict.normal_appearance[:Off]
|
|
231
230
|
@field.create_appearances
|
|
232
|
-
assert_same(yes, widget.
|
|
233
|
-
|
|
231
|
+
assert_same(yes, widget.appearance_dict.normal_appearance[:Yes])
|
|
232
|
+
assert_same(off, widget.appearance_dict.normal_appearance[:Off])
|
|
233
|
+
|
|
234
|
+
@field.delete_widget(widget)
|
|
235
|
+
@field.flag(:push_button)
|
|
236
|
+
widget = @field.create_widget(@doc.pages.add, Rect: [0, 0, 0, 0], AP: {N: @doc.wrap({}, stream: '')})
|
|
237
|
+
appearance = widget.appearance_dict.normal_appearance
|
|
238
|
+
@field.create_appearances
|
|
239
|
+
assert_same(appearance, widget.appearance_dict.normal_appearance)
|
|
234
240
|
end
|
|
235
241
|
|
|
236
242
|
it "always generates appearances if force is true" do
|
|
237
243
|
widget = @field.create_widget(@doc.pages.add, Rect: [0, 0, 0, 0])
|
|
238
244
|
@field.create_appearances
|
|
239
|
-
yes = widget.
|
|
245
|
+
yes = widget.appearance_dict.normal_appearance[:Yes]
|
|
240
246
|
@field.create_appearances(force: true)
|
|
241
|
-
refute_same(yes, widget.
|
|
247
|
+
refute_same(yes, widget.appearance_dict.normal_appearance[:Yes])
|
|
242
248
|
end
|
|
243
249
|
|
|
244
250
|
it "fails for unsupported button types" do
|
|
@@ -200,9 +200,14 @@ describe HexaPDF::Type::AcroForm::Field do
|
|
|
200
200
|
|
|
201
201
|
it "deletes the widget if it is embedded" do
|
|
202
202
|
widget = @field.create_widget(@page)
|
|
203
|
+
@doc.revisions.current.update(widget)
|
|
204
|
+
assert_same(widget, @doc.object(widget))
|
|
205
|
+
refute_same(@field, @doc.object(@field))
|
|
206
|
+
|
|
203
207
|
@field.delete_widget(widget)
|
|
204
208
|
refute(@field.key?(:Subtype))
|
|
205
209
|
assert(@page[:Annots].empty?)
|
|
210
|
+
assert_same(@field, @doc.object(@field))
|
|
206
211
|
end
|
|
207
212
|
|
|
208
213
|
it "deletes the widget if it is not embedded" do
|
|
@@ -254,8 +254,8 @@ describe HexaPDF::Type::AcroForm::Form do
|
|
|
254
254
|
|
|
255
255
|
it "creates the appearances of all field widgets if necessary" do
|
|
256
256
|
@acro_form.create_appearances
|
|
257
|
-
assert(@tf.each_widget.all? {|w| w.
|
|
258
|
-
assert(@cb.each_widget.all? {|w| w.
|
|
257
|
+
assert(@tf.each_widget.all? {|w| w.appearance_dict.normal_appearance.kind_of?(HexaPDF::Stream) })
|
|
258
|
+
assert(@cb.each_widget.all? {|w| w.appearance_dict.normal_appearance[:Yes].kind_of?(HexaPDF::Stream) })
|
|
259
259
|
end
|
|
260
260
|
|
|
261
261
|
it "force the creation of appearances if force is true" do
|
|
@@ -268,6 +268,50 @@ describe HexaPDF::Type::AcroForm::Form do
|
|
|
268
268
|
end
|
|
269
269
|
end
|
|
270
270
|
|
|
271
|
+
describe "flatten" do
|
|
272
|
+
before do
|
|
273
|
+
@acro_form.root_fields << @doc.wrap({T: 'test'})
|
|
274
|
+
@tf = @acro_form.create_text_field('textfields')
|
|
275
|
+
@tf.set_default_appearance_string
|
|
276
|
+
@tf[:V] = 'Test'
|
|
277
|
+
@tf.create_widget(@doc.pages.add)
|
|
278
|
+
@cb = @acro_form.create_check_box('test.checkbox')
|
|
279
|
+
@cb.create_widget(@doc.pages[0])
|
|
280
|
+
@cb.create_widget(@doc.pages.add)
|
|
281
|
+
end
|
|
282
|
+
|
|
283
|
+
it "creates the missing appearances if instructed to do so" do
|
|
284
|
+
assert_equal(3, @acro_form.flatten(create_appearances: false).size)
|
|
285
|
+
assert_equal(0, @acro_form.flatten(create_appearances: true).size)
|
|
286
|
+
end
|
|
287
|
+
|
|
288
|
+
it "flattens the whole interactive form" do
|
|
289
|
+
result = @acro_form.flatten
|
|
290
|
+
assert(result.empty?)
|
|
291
|
+
assert(@tf.null?)
|
|
292
|
+
assert(@cb.null?)
|
|
293
|
+
assert(@acro_form.null?)
|
|
294
|
+
refute(@doc.catalog.key?(:AcroForm))
|
|
295
|
+
end
|
|
296
|
+
|
|
297
|
+
it "flattens the given fields" do
|
|
298
|
+
result = @acro_form.flatten(fields: [@cb])
|
|
299
|
+
assert(result.empty?)
|
|
300
|
+
assert(@cb.null?)
|
|
301
|
+
refute(@tf.null?)
|
|
302
|
+
refute(@acro_form.null?)
|
|
303
|
+
assert(@doc.catalog.key?(:AcroForm))
|
|
304
|
+
end
|
|
305
|
+
|
|
306
|
+
it "doesn't delete the form object if not all fields were flattened" do
|
|
307
|
+
@acro_form.create_appearances
|
|
308
|
+
@tf.delete(:AP)
|
|
309
|
+
result = @acro_form.flatten(create_appearances: false)
|
|
310
|
+
assert_equal(1, result.size)
|
|
311
|
+
assert(@doc.catalog.key?(:AcroForm))
|
|
312
|
+
end
|
|
313
|
+
end
|
|
314
|
+
|
|
271
315
|
describe "perform_validation" do
|
|
272
316
|
it "checks whether the /DR field is available when /DA is set" do
|
|
273
317
|
@acro_form[:DA] = 'test'
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
|
2
|
+
|
|
3
|
+
require 'test_helper'
|
|
4
|
+
require 'hexapdf/document'
|
|
5
|
+
require 'hexapdf/type/acro_form/signature_field'
|
|
6
|
+
|
|
7
|
+
describe HexaPDF::Type::AcroForm::SignatureField::LockDictionary do
|
|
8
|
+
it "validates the presence of the /Fields key" do
|
|
9
|
+
doc = HexaPDF::Document.new
|
|
10
|
+
obj = HexaPDF::Type::AcroForm::SignatureField::LockDictionary.new({Action: :All}, document: doc)
|
|
11
|
+
assert(obj.validate)
|
|
12
|
+
obj[:Action] = :Include
|
|
13
|
+
refute(obj.validate)
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
describe HexaPDF::Type::AcroForm::SignatureField do
|
|
18
|
+
before do
|
|
19
|
+
@doc = HexaPDF::Document.new
|
|
20
|
+
@field = @doc.wrap({}, type: :XXAcroFormField, subtype: :Sig)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
it "sets the field value" do
|
|
24
|
+
@field.field_value = {Empty: :True}
|
|
25
|
+
assert_equal({Empty: :True}, @field[:V].value)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
it "gets the field value" do
|
|
29
|
+
@field[:V] = {Empty: :True}
|
|
30
|
+
assert_equal({Empty: :True}, @field.field_value.value)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
it "validates the value of the /FT field" do
|
|
34
|
+
refute(@field.validate(auto_correct: false))
|
|
35
|
+
assert(@field.validate)
|
|
36
|
+
assert_equal(:Sig, @field.field_type)
|
|
37
|
+
end
|
|
38
|
+
end
|
|
@@ -56,6 +56,8 @@ describe HexaPDF::Type::Annotations::Widget do
|
|
|
56
56
|
|
|
57
57
|
describe "background_color" do
|
|
58
58
|
it "returns the current background color" do
|
|
59
|
+
assert_nil(@widget.background_color)
|
|
60
|
+
@widget[:MK] = {BG: []}
|
|
59
61
|
assert_nil(@widget.background_color)
|
|
60
62
|
@widget[:MK] = {BG: [1]}
|
|
61
63
|
assert_equal([1], @widget.background_color.components)
|
|
@@ -40,19 +40,33 @@ describe HexaPDF::Type::Annotation do
|
|
|
40
40
|
|
|
41
41
|
it "returns the appearance dictionary" do
|
|
42
42
|
@annot[:AP] = :yes
|
|
43
|
-
assert_equal(:yes, @annot.
|
|
43
|
+
assert_equal(:yes, @annot.appearance_dict)
|
|
44
44
|
end
|
|
45
45
|
|
|
46
|
-
it "
|
|
47
|
-
|
|
46
|
+
it "returns the appearance stream of the given type" do
|
|
47
|
+
assert_nil(@annot.appearance)
|
|
48
|
+
|
|
48
49
|
@annot[:AP] = {N: {}}
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
@annot[:AP][:N] =
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
50
|
+
assert_nil(@annot.appearance)
|
|
51
|
+
|
|
52
|
+
stream = @doc.wrap({}, stream: '')
|
|
53
|
+
@annot[:AP][:N] = stream
|
|
54
|
+
assert_nil(@annot.appearance)
|
|
55
|
+
|
|
56
|
+
stream[:BBox] = [1, 2, 3, 4]
|
|
57
|
+
appearance = @annot.appearance
|
|
58
|
+
assert_same(stream.data, appearance.data)
|
|
59
|
+
assert_equal(:Form, appearance[:Subtype])
|
|
60
|
+
|
|
61
|
+
@annot[:AP][:N] = {X: {}}
|
|
62
|
+
assert_nil(@annot.appearance)
|
|
63
|
+
|
|
64
|
+
@annot[:AS] = :X
|
|
65
|
+
@annot[:AP][:N][:X] = stream
|
|
66
|
+
assert_same(stream.data, @annot.appearance.data)
|
|
67
|
+
|
|
68
|
+
@annot[:AP][:D] = {X: stream}
|
|
69
|
+
assert_same(stream.data, @annot.appearance(:down).data)
|
|
56
70
|
end
|
|
57
71
|
|
|
58
72
|
describe "flags" do
|