hexapdf 0.45.0 → 0.47.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (68) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +120 -47
  3. data/examples/019-acro_form.rb +5 -0
  4. data/lib/hexapdf/cli/inspect.rb +5 -0
  5. data/lib/hexapdf/composer.rb +1 -1
  6. data/lib/hexapdf/configuration.rb +19 -0
  7. data/lib/hexapdf/digital_signature/cms_handler.rb +31 -3
  8. data/lib/hexapdf/digital_signature/signing/default_handler.rb +9 -1
  9. data/lib/hexapdf/digital_signature/signing/signed_data_creator.rb +5 -1
  10. data/lib/hexapdf/document/layout.rb +48 -27
  11. data/lib/hexapdf/document.rb +24 -2
  12. data/lib/hexapdf/encryption/standard_security_handler.rb +32 -26
  13. data/lib/hexapdf/importer.rb +15 -5
  14. data/lib/hexapdf/layout/box.rb +25 -28
  15. data/lib/hexapdf/layout/frame.rb +1 -1
  16. data/lib/hexapdf/layout/inline_box.rb +17 -23
  17. data/lib/hexapdf/layout/list_box.rb +24 -29
  18. data/lib/hexapdf/layout/page_style.rb +23 -16
  19. data/lib/hexapdf/layout/style.rb +2 -2
  20. data/lib/hexapdf/layout/table_box.rb +57 -10
  21. data/lib/hexapdf/layout/text_box.rb +2 -6
  22. data/lib/hexapdf/parser.rb +5 -1
  23. data/lib/hexapdf/revisions.rb +1 -1
  24. data/lib/hexapdf/stream.rb +3 -3
  25. data/lib/hexapdf/task/optimize.rb +4 -4
  26. data/lib/hexapdf/tokenizer.rb +3 -2
  27. data/lib/hexapdf/type/acro_form/appearance_generator.rb +8 -4
  28. data/lib/hexapdf/type/acro_form/button_field.rb +2 -0
  29. data/lib/hexapdf/type/acro_form/choice_field.rb +2 -0
  30. data/lib/hexapdf/type/acro_form/field.rb +8 -0
  31. data/lib/hexapdf/type/acro_form/form.rb +10 -6
  32. data/lib/hexapdf/type/acro_form/signature_field.rb +2 -1
  33. data/lib/hexapdf/type/acro_form/text_field.rb +2 -0
  34. data/lib/hexapdf/type/acro_form/variable_text_field.rb +11 -3
  35. data/lib/hexapdf/type/annotations/widget.rb +4 -2
  36. data/lib/hexapdf/version.rb +1 -1
  37. data/lib/hexapdf/writer.rb +1 -0
  38. data/test/data/standard-security-handler/bothpwd-aes-256bit-V5-R5.pdf +43 -0
  39. data/test/data/standard-security-handler/nopwd-aes-256bit-V5-R5.pdf +44 -0
  40. data/test/data/standard-security-handler/ownerpwd-aes-256bit-V5-R5.pdf +43 -0
  41. data/test/data/standard-security-handler/userpwd-aes-256bit-V5-R5.pdf +0 -0
  42. data/test/hexapdf/digital_signature/common.rb +66 -84
  43. data/test/hexapdf/digital_signature/signing/test_default_handler.rb +7 -0
  44. data/test/hexapdf/digital_signature/signing/test_signed_data_creator.rb +9 -0
  45. data/test/hexapdf/digital_signature/test_cms_handler.rb +41 -1
  46. data/test/hexapdf/digital_signature/test_handler.rb +2 -1
  47. data/test/hexapdf/digital_signature/test_signatures.rb +4 -4
  48. data/test/hexapdf/document/test_layout.rb +28 -5
  49. data/test/hexapdf/encryption/test_standard_security_handler.rb +5 -2
  50. data/test/hexapdf/layout/test_box.rb +12 -5
  51. data/test/hexapdf/layout/test_frame.rb +12 -2
  52. data/test/hexapdf/layout/test_inline_box.rb +17 -28
  53. data/test/hexapdf/layout/test_list_box.rb +5 -5
  54. data/test/hexapdf/layout/test_page_style.rb +7 -2
  55. data/test/hexapdf/layout/test_table_box.rb +52 -0
  56. data/test/hexapdf/layout/test_text_box.rb +3 -9
  57. data/test/hexapdf/layout/test_text_layouter.rb +0 -3
  58. data/test/hexapdf/task/test_optimize.rb +2 -0
  59. data/test/hexapdf/test_document.rb +30 -3
  60. data/test/hexapdf/test_importer.rb +24 -0
  61. data/test/hexapdf/test_revisions.rb +54 -41
  62. data/test/hexapdf/test_writer.rb +11 -2
  63. data/test/hexapdf/type/acro_form/test_appearance_generator.rb +22 -5
  64. data/test/hexapdf/type/acro_form/test_form.rb +9 -5
  65. data/test/hexapdf/type/acro_form/test_signature_field.rb +3 -1
  66. data/test/hexapdf/type/acro_form/test_variable_text_field.rb +14 -1
  67. data/test/hexapdf/type/annotations/test_widget.rb +4 -0
  68. 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
- [:concatenate_matrix, [1, 0, 0, 1, 5, 10]],
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, [0.5, 0.5, 9.0, 9.0]],
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, 0, 88)
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
- it "merges the two revisions of a linearized PDF into one" do
361
- io = StringIO.new(<<~EOF)
362
- %PDF-1.2
363
- 5 0 obj
364
- <</Linearized 1>>
365
- endobj
366
- xref
367
- 5 1
368
- 0000000009 00000 n
369
- trailer
370
- <</ID[(a)(b)]/Info 1 0 R/Root 2 0 R/Size 6/Prev 394>>
371
- %
372
- 1 0 obj
373
- <</ModDate(D:20221205233910+01'00')/Producer(HexaPDF version 0.27.0)>>
374
- endobj
375
- 2 0 obj
376
- <</Type/Catalog/Pages 3 0 R>>
377
- endobj
378
- 3 0 obj
379
- <</Type/Pages/Kids[4 0 R]/Count 1>>
380
- endobj
381
- 4 0 obj
382
- <</Type/Page/MediaBox[0 0 595 842]/Parent 3 0 R/Resources<<>>>>
383
- endobj
384
- xref
385
- 0 5
386
- 0000000000 65535 f
387
- 0000000133 00000 n
388
- 0000000219 00000 n
389
- 0000000264 00000 n
390
- 0000000315 00000 n
391
- trailer
392
- <</ID[(a)(b)]/Info 1 0 R/Root 2 0 R/Size 5>>
393
- startxref
394
- 41
395
- %%EOF
396
- EOF
397
- doc = HexaPDF::Document.new(io: io, config: {'parser.try_xref_reconstruction' => false})
398
- assert(doc.revisions.parser.linearized?)
399
- assert_equal(1, doc.revisions.count)
400
- assert_same(5, doc.revisions.current.xref_section.max_oid)
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
@@ -64,7 +64,7 @@ describe HexaPDF::Writer do
64
64
  20
65
65
  endobj
66
66
  3 0 obj
67
- <</Type/XRef/Size 6/W[1 1 2]/Index[0 4 5 1]/Filter/FlateDecode/DecodeParms<</Columns 4/Predictor 12>>/Length 31>>stream
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
- <</Type/XRef/Size 7/Root<</Type/Catalog>>/Info 6 0 R/Prev 141/W[1 2 2]/Index[2 1 4 1 6 1]/Filter/FlateDecode/DecodeParms<</Columns 5/Predictor 12>>/Length 22>>stream
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
- assert_equal({Empty: :True}, @field.field_value.value)
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 "fails if no /DA value is set" do
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.45.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-06-18 00:00:00.000000000 Z
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