hexapdf 0.45.0 → 0.47.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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