hexapdf 0.45.0 → 0.47.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 +120 -47
- data/examples/019-acro_form.rb +5 -0
- data/lib/hexapdf/cli/inspect.rb +5 -0
- data/lib/hexapdf/composer.rb +1 -1
- data/lib/hexapdf/configuration.rb +19 -0
- data/lib/hexapdf/digital_signature/cms_handler.rb +31 -3
- data/lib/hexapdf/digital_signature/signing/default_handler.rb +9 -1
- data/lib/hexapdf/digital_signature/signing/signed_data_creator.rb +5 -1
- data/lib/hexapdf/document/layout.rb +48 -27
- data/lib/hexapdf/document.rb +24 -2
- data/lib/hexapdf/encryption/standard_security_handler.rb +32 -26
- data/lib/hexapdf/importer.rb +15 -5
- data/lib/hexapdf/layout/box.rb +25 -28
- data/lib/hexapdf/layout/frame.rb +1 -1
- data/lib/hexapdf/layout/inline_box.rb +17 -23
- data/lib/hexapdf/layout/list_box.rb +24 -29
- data/lib/hexapdf/layout/page_style.rb +23 -16
- data/lib/hexapdf/layout/style.rb +2 -2
- data/lib/hexapdf/layout/table_box.rb +57 -10
- data/lib/hexapdf/layout/text_box.rb +2 -6
- data/lib/hexapdf/parser.rb +5 -1
- data/lib/hexapdf/revisions.rb +1 -1
- data/lib/hexapdf/stream.rb +3 -3
- data/lib/hexapdf/task/optimize.rb +4 -4
- data/lib/hexapdf/tokenizer.rb +3 -2
- data/lib/hexapdf/type/acro_form/appearance_generator.rb +8 -4
- data/lib/hexapdf/type/acro_form/button_field.rb +2 -0
- data/lib/hexapdf/type/acro_form/choice_field.rb +2 -0
- data/lib/hexapdf/type/acro_form/field.rb +8 -0
- data/lib/hexapdf/type/acro_form/form.rb +10 -6
- data/lib/hexapdf/type/acro_form/signature_field.rb +2 -1
- data/lib/hexapdf/type/acro_form/text_field.rb +2 -0
- data/lib/hexapdf/type/acro_form/variable_text_field.rb +11 -3
- data/lib/hexapdf/type/annotations/widget.rb +4 -2
- data/lib/hexapdf/version.rb +1 -1
- data/lib/hexapdf/writer.rb +1 -0
- data/test/data/standard-security-handler/bothpwd-aes-256bit-V5-R5.pdf +43 -0
- data/test/data/standard-security-handler/nopwd-aes-256bit-V5-R5.pdf +44 -0
- data/test/data/standard-security-handler/ownerpwd-aes-256bit-V5-R5.pdf +43 -0
- data/test/data/standard-security-handler/userpwd-aes-256bit-V5-R5.pdf +0 -0
- data/test/hexapdf/digital_signature/common.rb +66 -84
- data/test/hexapdf/digital_signature/signing/test_default_handler.rb +7 -0
- data/test/hexapdf/digital_signature/signing/test_signed_data_creator.rb +9 -0
- data/test/hexapdf/digital_signature/test_cms_handler.rb +41 -1
- data/test/hexapdf/digital_signature/test_handler.rb +2 -1
- data/test/hexapdf/digital_signature/test_signatures.rb +4 -4
- data/test/hexapdf/document/test_layout.rb +28 -5
- data/test/hexapdf/encryption/test_standard_security_handler.rb +5 -2
- data/test/hexapdf/layout/test_box.rb +12 -5
- data/test/hexapdf/layout/test_frame.rb +12 -2
- data/test/hexapdf/layout/test_inline_box.rb +17 -28
- data/test/hexapdf/layout/test_list_box.rb +5 -5
- data/test/hexapdf/layout/test_page_style.rb +7 -2
- data/test/hexapdf/layout/test_table_box.rb +52 -0
- data/test/hexapdf/layout/test_text_box.rb +3 -9
- data/test/hexapdf/layout/test_text_layouter.rb +0 -3
- data/test/hexapdf/task/test_optimize.rb +2 -0
- data/test/hexapdf/test_document.rb +30 -3
- data/test/hexapdf/test_importer.rb +24 -0
- data/test/hexapdf/test_revisions.rb +54 -41
- data/test/hexapdf/test_writer.rb +11 -2
- data/test/hexapdf/type/acro_form/test_appearance_generator.rb +22 -5
- data/test/hexapdf/type/acro_form/test_form.rb +9 -5
- data/test/hexapdf/type/acro_form/test_signature_field.rb +3 -1
- data/test/hexapdf/type/acro_form/test_variable_text_field.rb +14 -1
- data/test/hexapdf/type/annotations/test_widget.rb +4 -0
- metadata +6 -2
@@ -511,6 +511,58 @@ describe HexaPDF::Layout::TableBox do
|
|
511
511
|
[0, 66, 39.75, 22], [39.75, 66, 39.75, 22], [79.5, 44, 39.75, 44], [119.25, 66, 39.75, 22]])
|
512
512
|
end
|
513
513
|
|
514
|
+
describe "row spans" do
|
515
|
+
# ----------
|
516
|
+
# | a | b |
|
517
|
+
# | a | |
|
518
|
+
# | a |----|
|
519
|
+
# | a | c |
|
520
|
+
# | a | |
|
521
|
+
# ----------
|
522
|
+
it "works if content of a row span cell is larger than the rows" do
|
523
|
+
cells = [[{row_span: 2, content: @fixed_size_boxes[0..2]}, @fixed_size_boxes[3]],
|
524
|
+
[@fixed_size_boxes[4]]]
|
525
|
+
check_box(create_box(cells: cells, cell_style: {padding: 0, border: {width: 0}}),
|
526
|
+
:success, 160, 30,
|
527
|
+
[[0, 0, 80, 30], [80, 0, 80, 15], [0, 0, 80, 30], [80, 15, 80, 15]])
|
528
|
+
end
|
529
|
+
|
530
|
+
# ----------
|
531
|
+
# | a | b |
|
532
|
+
# | |----|
|
533
|
+
# | | c |
|
534
|
+
# ----------
|
535
|
+
it "works if content of a row span cell is smaller than the rows" do
|
536
|
+
cells = [[{row_span: 2, content: @fixed_size_boxes[0]}, @fixed_size_boxes[3]],
|
537
|
+
[@fixed_size_boxes[4]]]
|
538
|
+
check_box(create_box(cells: cells, cell_style: {padding: 0, border: {width: 0}}),
|
539
|
+
:success, 160, 20,
|
540
|
+
[[0, 0, 80, 20], [80, 0, 80, 10], [0, 0, 80, 20], [80, 10, 80, 10]])
|
541
|
+
end
|
542
|
+
|
543
|
+
# -----------------
|
544
|
+
# | a | b | c | d |
|
545
|
+
# | a | b |---| d |
|
546
|
+
# | a | b | e | |
|
547
|
+
# | a | | e | |
|
548
|
+
# --------| e | |
|
549
|
+
# | f | g | e | |
|
550
|
+
# ----------------
|
551
|
+
it "works if multiple, possibly overlapping row spans are involved" do
|
552
|
+
cells = [[{row_span: 2, content: @fixed_size_boxes[0..2]},
|
553
|
+
{row_span: 2, content: @fixed_size_boxes[3..4]},
|
554
|
+
@fixed_size_boxes[5],
|
555
|
+
{row_span: 3, content: @fixed_size_boxes[6..7]}],
|
556
|
+
[{row_span: 2, content: @fixed_size_boxes[8, 3]}],
|
557
|
+
[@fixed_size_boxes[11], @fixed_size_boxes[12]]]
|
558
|
+
check_box(create_box(cells: cells, cell_style: {padding: 0, border: {width: 0}}),
|
559
|
+
:success, 160, 40,
|
560
|
+
[[0, 0, 40, 30], [40, 0, 40, 30], [80, 0, 40, 10], [120, 0, 40, 40],
|
561
|
+
[0, 0, 40, 30], [40, 0, 40, 30], [80, 10, 40, 30], [120, 0, 40, 40],
|
562
|
+
[0, 30, 40, 10], [40, 30, 40, 10], [80, 10, 40, 30], [120, 0, 40, 40]])
|
563
|
+
end
|
564
|
+
end
|
565
|
+
|
514
566
|
it "fits a table with header rows" do
|
515
567
|
result = [[0, 0, 80, 10], [80, 0, 80, 10], [0, 10, 80, 10], [80, 10, 80, 10]]
|
516
568
|
header = lambda {|_| [@fixed_size_boxes[10, 2], @fixed_size_boxes[12, 2]] }
|
@@ -178,15 +178,12 @@ describe HexaPDF::Layout::TextBox do
|
|
178
178
|
assert_operators(@canvas.contents, [[:save_graphics_state],
|
179
179
|
[:restore_graphics_state],
|
180
180
|
[:save_graphics_state],
|
181
|
-
[:
|
182
|
-
[:save_graphics_state],
|
183
|
-
[:append_rectangle, [0, 0, 10, 10]],
|
181
|
+
[:append_rectangle, [5, 10, 10, 10]],
|
184
182
|
[:clip_path_non_zero],
|
185
183
|
[:end_path],
|
186
|
-
[:append_rectangle, [
|
184
|
+
[:append_rectangle, [5.5, 10.5, 9.0, 9.0]],
|
187
185
|
[:stroke_path],
|
188
186
|
[:restore_graphics_state],
|
189
|
-
[:restore_graphics_state],
|
190
187
|
[:save_graphics_state],
|
191
188
|
[:restore_graphics_state]])
|
192
189
|
end
|
@@ -195,7 +192,7 @@ describe HexaPDF::Layout::TextBox do
|
|
195
192
|
@frame.remove_area(Geom2D::Rectangle(0, 0, 40, 100))
|
196
193
|
box = create_box([@inline_box], style: {position: :flow, border: {width: 1}})
|
197
194
|
box.fit(60, 100, @frame)
|
198
|
-
box.draw(@canvas,
|
195
|
+
box.draw(@canvas, 40, 88)
|
199
196
|
assert_operators(@canvas.contents, [[:save_graphics_state],
|
200
197
|
[:append_rectangle, [40, 88, 12, 12]],
|
201
198
|
[:clip_path_non_zero],
|
@@ -207,9 +204,6 @@ describe HexaPDF::Layout::TextBox do
|
|
207
204
|
[:restore_graphics_state],
|
208
205
|
[:save_graphics_state],
|
209
206
|
[:concatenate_matrix, [1, 0, 0, 1, 41, 89]],
|
210
|
-
[:save_graphics_state],
|
211
|
-
[:concatenate_matrix, [1, 0, 0, 1, 0, 0]],
|
212
|
-
[:restore_graphics_state],
|
213
207
|
[:restore_graphics_state],
|
214
208
|
[:save_graphics_state],
|
215
209
|
[:restore_graphics_state]])
|
@@ -812,11 +812,8 @@ describe HexaPDF::Layout::TextLayouter do
|
|
812
812
|
[:restore_graphics_state],
|
813
813
|
[:save_graphics_state],
|
814
814
|
[:concatenate_matrix, [1, 0, 0, 1, 10, -40]],
|
815
|
-
[:save_graphics_state],
|
816
|
-
[:concatenate_matrix, [1, 0, 0, 1, 0, 0]],
|
817
815
|
[:set_line_width, [2]],
|
818
816
|
[:restore_graphics_state],
|
819
|
-
[:restore_graphics_state],
|
820
817
|
[:save_graphics_state],
|
821
818
|
[:restore_graphics_state]])
|
822
819
|
end
|
@@ -8,6 +8,7 @@ describe HexaPDF::Task::Optimize do
|
|
8
8
|
class TestType < HexaPDF::Dictionary
|
9
9
|
|
10
10
|
define_type :Test
|
11
|
+
define_field :Type, type: Symbol, default: type
|
11
12
|
define_field :Optional, type: Symbol, default: :Optional
|
12
13
|
|
13
14
|
end
|
@@ -46,6 +47,7 @@ describe HexaPDF::Task::Optimize do
|
|
46
47
|
end
|
47
48
|
|
48
49
|
def assert_default_deleted
|
50
|
+
assert(@doc.object(1).key?(:Type))
|
49
51
|
refute(@doc.object(1).key?(:Optional))
|
50
52
|
end
|
51
53
|
|
@@ -497,10 +497,10 @@ describe HexaPDF::Document do
|
|
497
497
|
|
498
498
|
it "returns all signature fields of the document" do
|
499
499
|
form = @doc.acro_form(create: true)
|
500
|
-
sig1 = @doc.add({FT: :Sig, T: 'sig1', V: :sig1})
|
501
|
-
sig2 = @doc.add({FT: :Sig, T: 'sig2', V: :sig2})
|
500
|
+
sig1 = @doc.add({FT: :Sig, T: 'sig1', V: {k: :sig1}})
|
501
|
+
sig2 = @doc.add({FT: :Sig, T: 'sig2', V: {k: :sig2}})
|
502
502
|
form.root_fields << sig1 << sig2
|
503
|
-
assert_equal([:sig1, :sig2], @doc.signatures.to_a)
|
503
|
+
assert_equal([{k: :sig1}, {k: :sig2}], @doc.signatures.to_a)
|
504
504
|
end
|
505
505
|
|
506
506
|
it "allows to conveniently sign a document" do
|
@@ -568,4 +568,31 @@ describe HexaPDF::Document do
|
|
568
568
|
it "can be inspected and the output is not too large" do
|
569
569
|
assert_match(/HexaPDF::Document:\d+/, @doc.inspect)
|
570
570
|
end
|
571
|
+
|
572
|
+
describe "duplicate" do
|
573
|
+
it "creates an in-memory copy" do
|
574
|
+
doc = HexaPDF::Document.new
|
575
|
+
doc.pages.add.canvas.line_width(10)
|
576
|
+
doc.trailer.info[:Author] = 'HexaPDF'
|
577
|
+
doc.dispatch_message(:complete_objects)
|
578
|
+
|
579
|
+
dupped = doc.duplicate
|
580
|
+
assert_equal('HexaPDF', dupped.trailer.info[:Author])
|
581
|
+
doc.pages[0].canvas.line_cap_style(:round)
|
582
|
+
assert_equal("10 w\n", dupped.pages[0].contents)
|
583
|
+
end
|
584
|
+
|
585
|
+
it "doesn't copy the encryption state" do
|
586
|
+
doc = HexaPDF::Document.new
|
587
|
+
doc.pages.add.canvas.line_width(10)
|
588
|
+
doc.encrypt
|
589
|
+
io = StringIO.new
|
590
|
+
doc.write(io)
|
591
|
+
|
592
|
+
doc = HexaPDF::Document.new(io: io)
|
593
|
+
dupped = doc.duplicate
|
594
|
+
assert_equal("10 w\n", dupped.pages[0].contents)
|
595
|
+
refute(dupped.encrypted?)
|
596
|
+
end
|
597
|
+
end
|
571
598
|
end
|
@@ -47,6 +47,13 @@ describe HexaPDF::Importer do
|
|
47
47
|
refute_same(obj1, obj2)
|
48
48
|
refute_same(obj1[:ref], obj2[:ref])
|
49
49
|
end
|
50
|
+
|
51
|
+
it "duplicates the whole document" do
|
52
|
+
trailer = HexaPDF::Importer.copy(@dest, @source.trailer, allow_all: true)
|
53
|
+
refute_same(@source.catalog, trailer[:Root])
|
54
|
+
refute_same(@source.pages.root, trailer[:Root][:Pages])
|
55
|
+
assert_equal(90, trailer[:Root][:Pages][:Kids][0][:Rotate])
|
56
|
+
end
|
50
57
|
end
|
51
58
|
|
52
59
|
describe "import" do
|
@@ -121,6 +128,16 @@ describe HexaPDF::Importer do
|
|
121
128
|
refute_same(dst_obj.data.stream, src_obj.data.stream)
|
122
129
|
end
|
123
130
|
|
131
|
+
it "duplicates the stream if it is a FiberDoubleForString, e.g. when using Canvas" do
|
132
|
+
src_page = @source.pages[0]
|
133
|
+
src_page.canvas.line_width(10)
|
134
|
+
dst_page = @importer.import(src_page)
|
135
|
+
refute_same(dst_page, src_page)
|
136
|
+
refute_same(dst_page[:Contents].data.stream, src_page[:Contents].data.stream)
|
137
|
+
src_page.canvas.line_width(20)
|
138
|
+
assert_equal("10 w\n", dst_page.contents)
|
139
|
+
end
|
140
|
+
|
124
141
|
it "does not import objects of type Catalog or Pages" do
|
125
142
|
@obj[:catalog] = @source.catalog
|
126
143
|
@obj[:pages] = @source.catalog.pages
|
@@ -130,6 +147,13 @@ describe HexaPDF::Importer do
|
|
130
147
|
assert_nil(obj[:pages])
|
131
148
|
end
|
132
149
|
|
150
|
+
it "handles null values correctly" do
|
151
|
+
@source.add(@hash)
|
152
|
+
@source.delete(@hash)
|
153
|
+
obj = @importer.import(@obj)
|
154
|
+
assert_nil(obj[:hash])
|
155
|
+
end
|
156
|
+
|
133
157
|
it "imports Page objects correctly by copying the inherited values" do
|
134
158
|
page = @importer.import(@source.pages[0])
|
135
159
|
assert_equal(90, page[:Rotate])
|
@@ -357,46 +357,59 @@ describe HexaPDF::Revisions do
|
|
357
357
|
end
|
358
358
|
end
|
359
359
|
|
360
|
-
|
361
|
-
|
362
|
-
|
363
|
-
|
364
|
-
|
365
|
-
|
366
|
-
|
367
|
-
|
368
|
-
|
369
|
-
|
370
|
-
|
371
|
-
|
372
|
-
|
373
|
-
|
374
|
-
|
375
|
-
|
376
|
-
|
377
|
-
|
378
|
-
|
379
|
-
|
380
|
-
|
381
|
-
|
382
|
-
|
383
|
-
|
384
|
-
|
385
|
-
|
386
|
-
|
387
|
-
|
388
|
-
|
389
|
-
|
390
|
-
|
391
|
-
|
392
|
-
|
393
|
-
|
394
|
-
|
395
|
-
|
396
|
-
|
397
|
-
|
398
|
-
|
399
|
-
|
400
|
-
|
360
|
+
describe "linearzied PDFs" do
|
361
|
+
before do
|
362
|
+
@io = StringIO.new(<<~EOF)
|
363
|
+
%PDF-1.2
|
364
|
+
5 0 obj
|
365
|
+
<</Linearized 1>>
|
366
|
+
endobj
|
367
|
+
xref
|
368
|
+
5 1
|
369
|
+
0000000009 00000 n
|
370
|
+
trailer
|
371
|
+
<</ID[(a)(b)]/Info 1 0 R/Root 2 0 R/Size 6/Prev 394>>
|
372
|
+
%
|
373
|
+
1 0 obj
|
374
|
+
<</ModDate(D:20221205233910+01'00')/Producer(HexaPDF version 0.27.0)>>
|
375
|
+
endobj
|
376
|
+
2 0 obj
|
377
|
+
<</Type/Catalog/Pages 3 0 R>>
|
378
|
+
endobj
|
379
|
+
3 0 obj
|
380
|
+
<</Type/Pages/Kids[4 0 R]/Count 1>>
|
381
|
+
endobj
|
382
|
+
4 0 obj
|
383
|
+
<</Type/Page/MediaBox[0 0 595 842]/Parent 3 0 R/Resources<<>>>>
|
384
|
+
endobj
|
385
|
+
xref
|
386
|
+
0 5
|
387
|
+
0000000000 65535 f
|
388
|
+
0000000133 00000 n
|
389
|
+
0000000219 00000 n
|
390
|
+
0000000264 00000 n
|
391
|
+
0000000315 00000 n
|
392
|
+
trailer
|
393
|
+
<</ID[(a)(b)]/Info 1 0 R/Root 2 0 R/Size 5>>
|
394
|
+
startxref
|
395
|
+
41
|
396
|
+
%%EOF
|
397
|
+
EOF
|
398
|
+
end
|
399
|
+
|
400
|
+
it "merges the two revisions of a linearized PDF into one" do
|
401
|
+
doc = HexaPDF::Document.new(io: @io, config: {'parser.try_xref_reconstruction' => false})
|
402
|
+
assert(doc.revisions.parser.linearized?)
|
403
|
+
assert_equal(1, doc.revisions.count)
|
404
|
+
assert_same(5, doc.revisions.current.xref_section.max_oid)
|
405
|
+
end
|
406
|
+
|
407
|
+
it "works for a fake linearized PDF where the first xref section isn't actually used" do
|
408
|
+
@io.string[-9..-1] = "394\n%%EOF\n"
|
409
|
+
doc = HexaPDF::Document.new(io: @io, config: {'parser.try_xref_reconstruction' => false})
|
410
|
+
assert(doc.revisions.parser.linearized?)
|
411
|
+
assert_equal(1, doc.revisions.count)
|
412
|
+
assert_same(4, doc.revisions.current.xref_section.max_oid)
|
413
|
+
end
|
401
414
|
end
|
402
415
|
end
|
data/test/hexapdf/test_writer.rb
CHANGED
@@ -64,7 +64,7 @@ describe HexaPDF::Writer do
|
|
64
64
|
20
|
65
65
|
endobj
|
66
66
|
3 0 obj
|
67
|
-
<</Type/XRef/
|
67
|
+
<</Size 6/Type/XRef/W[1 1 2]/Index[0 4 5 1]/Filter/FlateDecode/DecodeParms<</Columns 4/Predictor 12>>/Length 31>>stream
|
68
68
|
x\xDAcb`\xF8\xFF\x9F\x89\x89\x95\x91\x91\xE9\x7F\x19\x03\x03\x13\x83\x10\x88he`\x00\x00B4\x04\x1E
|
69
69
|
endstream
|
70
70
|
endobj
|
@@ -80,7 +80,7 @@ describe HexaPDF::Writer do
|
|
80
80
|
endstream
|
81
81
|
endobj
|
82
82
|
4 0 obj
|
83
|
-
<</
|
83
|
+
<</Size 7/Root<</Type/Catalog>>/Info 6 0 R/Prev 141/Type/XRef/W[1 2 2]/Index[2 1 4 1 6 1]/Filter/FlateDecode/DecodeParms<</Columns 5/Predictor 12>>/Length 22>>stream
|
84
84
|
x\xDAcbdlg``b`\xB0\x04\x93\x93\x18\x18\x00\f\e\x01[
|
85
85
|
endstream
|
86
86
|
endobj
|
@@ -270,4 +270,13 @@ describe HexaPDF::Writer do
|
|
270
270
|
doc = HexaPDF::Document.new(io: io)
|
271
271
|
refute(doc.trailer.key?(:XRefStm))
|
272
272
|
end
|
273
|
+
|
274
|
+
it "removes the /Type entry in a non-xref stream trailer" do
|
275
|
+
io = StringIO.new
|
276
|
+
doc = HexaPDF::Document.new
|
277
|
+
doc.trailer[:Type] = :XRef
|
278
|
+
doc.write(io)
|
279
|
+
doc = HexaPDF::Document.new(io: io)
|
280
|
+
refute(doc.trailer.key?(:Type))
|
281
|
+
end
|
273
282
|
end
|
@@ -291,6 +291,22 @@ describe HexaPDF::Type::AcroForm::AppearanceGenerator do
|
|
291
291
|
assert_equal(:XObject, @widget[:AP][:N][:Other].type)
|
292
292
|
end
|
293
293
|
|
294
|
+
it "uses the field's value or :Yes for the on state if the appearance dictionary doesn't contain a name for it" do
|
295
|
+
@widget[:AP][:N].delete(:Yes)
|
296
|
+
@generator.create_appearances
|
297
|
+
assert_equal(:XObject, @widget[:AP][:N][:Yes].type)
|
298
|
+
|
299
|
+
@widget[:AP][:N].delete(:Yes)
|
300
|
+
@field[:V] = nil
|
301
|
+
@generator.create_appearances
|
302
|
+
assert_equal(:XObject, @widget[:AP][:N][:Yes].type)
|
303
|
+
|
304
|
+
@widget[:AP][:N].delete(:Yes)
|
305
|
+
@field[:V] = "other" # some PDFs use a string instead of the correct symbol
|
306
|
+
@generator.create_appearances
|
307
|
+
assert_equal(:XObject, @widget[:AP][:N][:other].type)
|
308
|
+
end
|
309
|
+
|
294
310
|
it "creates the needed appearance streams" do
|
295
311
|
@widget[:AP][:N].delete(:Off)
|
296
312
|
@generator.create_appearances
|
@@ -327,11 +343,6 @@ describe HexaPDF::Type::AcroForm::AppearanceGenerator do
|
|
327
343
|
[:end_text],
|
328
344
|
[:restore_graphics_state]])
|
329
345
|
end
|
330
|
-
|
331
|
-
it "fails if the appearance dictionary doesn't contain a name for the on state" do
|
332
|
-
@widget[:AP][:N].delete(:Yes)
|
333
|
-
assert_raises(HexaPDF::Error) { @generator.create_appearances }
|
334
|
-
end
|
335
346
|
end
|
336
347
|
|
337
348
|
describe "radio button" do
|
@@ -441,6 +452,12 @@ describe HexaPDF::Type::AcroForm::AppearanceGenerator do
|
|
441
452
|
assert_equal(form, @widget[:AP][:N])
|
442
453
|
refute(form.key?(:key))
|
443
454
|
assert_match(/test1/, form.contents)
|
455
|
+
|
456
|
+
form.delete(:Type)
|
457
|
+
@widget[:AP][:N] = @doc.wrap(form, type: HexaPDF::Type::Annotation)
|
458
|
+
@field[:V] = 'test2'
|
459
|
+
@generator.create_appearances
|
460
|
+
assert_match(/test2/, form.contents)
|
444
461
|
end
|
445
462
|
|
446
463
|
describe "takes the rotation into account" do
|
@@ -274,6 +274,15 @@ describe HexaPDF::Type::AcroForm::Form do
|
|
274
274
|
assert(obj.null?)
|
275
275
|
end
|
276
276
|
|
277
|
+
it "deletes a field with an embedded widget annotation" do
|
278
|
+
widget = @field.create_widget(@doc.pages.add, Rect: [0, 0, 0, 0])
|
279
|
+
assert_equal(widget, @field)
|
280
|
+
refute(@doc.pages[0][:Annots].empty?)
|
281
|
+
@acro_form.delete_field(@field)
|
282
|
+
assert(@doc.pages[0][:Annots].empty?)
|
283
|
+
assert(@field.null?)
|
284
|
+
end
|
285
|
+
|
277
286
|
it "deletes all widget annotations from the document and the annotation array" do
|
278
287
|
widget1 = @field.create_widget(@doc.pages.add, Rect: [0, 0, 0, 0])
|
279
288
|
widget2 = @field.create_widget(@doc.pages.add, Rect: [0, 0, 0, 0])
|
@@ -498,11 +507,6 @@ describe HexaPDF::Type::AcroForm::Form do
|
|
498
507
|
assert(@acro_form.validate)
|
499
508
|
end
|
500
509
|
|
501
|
-
it "set the default appearance string, though optional, to a valid value to avoid problems" do
|
502
|
-
assert(@acro_form.validate)
|
503
|
-
assert_equal("0.0 g /F1 0 Tf", @acro_form[:DA])
|
504
|
-
end
|
505
|
-
|
506
510
|
describe "field hierarchy validation" do
|
507
511
|
before do
|
508
512
|
@acro_form[:Fields] = [
|
@@ -31,7 +31,9 @@ describe HexaPDF::Type::AcroForm::SignatureField do
|
|
31
31
|
|
32
32
|
it "gets the field value" do
|
33
33
|
@field[:V] = {Empty: :True}
|
34
|
-
|
34
|
+
value = @field.field_value
|
35
|
+
assert_kind_of(HexaPDF::DigitalSignature::Signature, value)
|
36
|
+
assert_equal({Empty: :True}, value)
|
35
37
|
end
|
36
38
|
|
37
39
|
it "validates the value of the /FT field" do
|
@@ -102,9 +102,22 @@ describe HexaPDF::Type::AcroForm::VariableTextField do
|
|
102
102
|
@field.parse_default_appearance_string)
|
103
103
|
end
|
104
104
|
|
105
|
-
it "
|
105
|
+
it "sets a standard /DA value if no other /DA is found" do
|
106
106
|
@doc.acro_form.delete(:DA)
|
107
|
+
assert_equal([:F1, 0, HexaPDF::Content::ColorSpace.prenormalized_device_color([0])],
|
108
|
+
@field.parse_default_appearance_string)
|
109
|
+
end
|
110
|
+
|
111
|
+
it "converts the /DA to a string in case an invalid PDF uses a Symbol" do
|
112
|
+
@field[:DA] = :"1 g /F1 20 Tf"
|
113
|
+
assert_equal([:F1, 20, @color], @field.parse_default_appearance_string)
|
114
|
+
end
|
115
|
+
|
116
|
+
it "fails if no /DA value is set and no default appearance string should be set" do
|
117
|
+
@doc.acro_form.delete(:DA)
|
118
|
+
@doc.config['acro_form.fallback_default_appearance'] = nil
|
107
119
|
assert_raises(HexaPDF::Error) { @field.parse_default_appearance_string }
|
108
120
|
end
|
121
|
+
|
109
122
|
end
|
110
123
|
end
|
@@ -194,6 +194,8 @@ describe HexaPDF::Type::Annotations::Widget do
|
|
194
194
|
|
195
195
|
it "returns the default size if none is set" do
|
196
196
|
assert_equal(0, @widget.marker_style.size)
|
197
|
+
@widget.form_field[:DA] = "0.0 g"
|
198
|
+
assert_equal(0, @widget.marker_style.size)
|
197
199
|
end
|
198
200
|
|
199
201
|
it "sets the given size" do
|
@@ -212,6 +214,8 @@ describe HexaPDF::Type::Annotations::Widget do
|
|
212
214
|
|
213
215
|
it "returns the default color if none is set" do
|
214
216
|
assert_equal([0], @widget.marker_style.color.components)
|
217
|
+
@widget.form_field[:DA] = "/ZaDb 10 Tfg"
|
218
|
+
assert_equal([0], @widget.marker_style.color.components)
|
215
219
|
end
|
216
220
|
|
217
221
|
it "sets the given color" do
|
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.47.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-09-07 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: cmdparse
|
@@ -594,21 +594,25 @@ files:
|
|
594
594
|
- test/data/minimal.pdf
|
595
595
|
- test/data/standard-security-handler/README
|
596
596
|
- test/data/standard-security-handler/bothpwd-aes-128bit-V4.pdf
|
597
|
+
- test/data/standard-security-handler/bothpwd-aes-256bit-V5-R5.pdf
|
597
598
|
- test/data/standard-security-handler/bothpwd-aes-256bit-V5.pdf
|
598
599
|
- test/data/standard-security-handler/bothpwd-arc4-128bit-V2.pdf
|
599
600
|
- test/data/standard-security-handler/bothpwd-arc4-128bit-V4.pdf
|
600
601
|
- test/data/standard-security-handler/bothpwd-arc4-40bit-V1.pdf
|
601
602
|
- test/data/standard-security-handler/nopwd-aes-128bit-V4.pdf
|
603
|
+
- test/data/standard-security-handler/nopwd-aes-256bit-V5-R5.pdf
|
602
604
|
- test/data/standard-security-handler/nopwd-aes-256bit-V5.pdf
|
603
605
|
- test/data/standard-security-handler/nopwd-arc4-128bit-V2.pdf
|
604
606
|
- test/data/standard-security-handler/nopwd-arc4-128bit-V4.pdf
|
605
607
|
- test/data/standard-security-handler/nopwd-arc4-40bit-V1.pdf
|
606
608
|
- test/data/standard-security-handler/ownerpwd-aes-128bit-V4.pdf
|
609
|
+
- test/data/standard-security-handler/ownerpwd-aes-256bit-V5-R5.pdf
|
607
610
|
- test/data/standard-security-handler/ownerpwd-aes-256bit-V5.pdf
|
608
611
|
- test/data/standard-security-handler/ownerpwd-arc4-128bit-V2.pdf
|
609
612
|
- test/data/standard-security-handler/ownerpwd-arc4-128bit-V4.pdf
|
610
613
|
- test/data/standard-security-handler/ownerpwd-arc4-40bit-V1.pdf
|
611
614
|
- test/data/standard-security-handler/userpwd-aes-128bit-V4.pdf
|
615
|
+
- test/data/standard-security-handler/userpwd-aes-256bit-V5-R5.pdf
|
612
616
|
- test/data/standard-security-handler/userpwd-aes-256bit-V5.pdf
|
613
617
|
- test/data/standard-security-handler/userpwd-arc4-128bit-V2.pdf
|
614
618
|
- test/data/standard-security-handler/userpwd-arc4-128bit-V4.pdf
|