hexapdf 0.46.0 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (87) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +83 -16
  3. data/lib/hexapdf/composer.rb +7 -0
  4. data/lib/hexapdf/configuration.rb +13 -0
  5. data/lib/hexapdf/content/parser.rb +3 -1
  6. data/lib/hexapdf/digital_signature/cms_handler.rb +13 -0
  7. data/lib/hexapdf/digital_signature/signature.rb +1 -1
  8. data/lib/hexapdf/digital_signature/signing/default_handler.rb +1 -0
  9. data/lib/hexapdf/document.rb +14 -3
  10. data/lib/hexapdf/encryption/standard_security_handler.rb +32 -26
  11. data/lib/hexapdf/font/cmap/writer.rb +58 -4
  12. data/lib/hexapdf/font/cmap.rb +7 -0
  13. data/lib/hexapdf/font/true_type_wrapper.rb +41 -16
  14. data/lib/hexapdf/importer.rb +1 -1
  15. data/lib/hexapdf/layout/table_box.rb +57 -10
  16. data/lib/hexapdf/layout/text_fragment.rb +2 -1
  17. data/lib/hexapdf/object.rb +1 -1
  18. data/lib/hexapdf/parser.rb +1 -1
  19. data/lib/hexapdf/reference.rb +1 -1
  20. data/lib/hexapdf/task/merge_acro_form.rb +164 -0
  21. data/lib/hexapdf/task/optimize.rb +4 -4
  22. data/lib/hexapdf/task.rb +1 -0
  23. data/lib/hexapdf/tokenizer.rb +2 -0
  24. data/lib/hexapdf/type/acro_form/appearance_generator.rb +8 -4
  25. data/lib/hexapdf/type/acro_form/form.rb +14 -24
  26. data/lib/hexapdf/type/acro_form/signature_field.rb +18 -7
  27. data/lib/hexapdf/type/acro_form/variable_text_field.rb +12 -4
  28. data/lib/hexapdf/type/actions/go_to.rb +1 -0
  29. data/lib/hexapdf/type/actions/go_to_r.rb +1 -0
  30. data/lib/hexapdf/type/actions/launch.rb +5 -1
  31. data/lib/hexapdf/type/annotation.rb +6 -1
  32. data/lib/hexapdf/type/annotations/markup_annotation.rb +14 -1
  33. data/lib/hexapdf/type/annotations/widget.rb +4 -2
  34. data/lib/hexapdf/type/catalog.rb +3 -0
  35. data/lib/hexapdf/type/cid_font.rb +4 -1
  36. data/lib/hexapdf/type/file_specification.rb +17 -14
  37. data/lib/hexapdf/type/font_descriptor.rb +4 -3
  38. data/lib/hexapdf/type/font_simple.rb +3 -1
  39. data/lib/hexapdf/type/font_true_type.rb +2 -0
  40. data/lib/hexapdf/type/font_type0.rb +1 -1
  41. data/lib/hexapdf/type/font_type1.rb +7 -0
  42. data/lib/hexapdf/type/font_type3.rb +0 -1
  43. data/lib/hexapdf/type/form.rb +5 -2
  44. data/lib/hexapdf/type/graphics_state_parameter.rb +7 -4
  45. data/lib/hexapdf/type/image.rb +8 -4
  46. data/lib/hexapdf/type/info.rb +2 -2
  47. data/lib/hexapdf/type/mark_information.rb +2 -2
  48. data/lib/hexapdf/type/optional_content_configuration.rb +1 -1
  49. data/lib/hexapdf/type/optional_content_membership.rb +1 -1
  50. data/lib/hexapdf/type/page.rb +5 -3
  51. data/lib/hexapdf/type/resources.rb +6 -6
  52. data/lib/hexapdf/type/viewer_preferences.rb +4 -3
  53. data/lib/hexapdf/version.rb +1 -1
  54. data/lib/hexapdf/writer.rb +1 -0
  55. data/test/data/standard-security-handler/bothpwd-aes-256bit-V5-R5.pdf +43 -0
  56. data/test/data/standard-security-handler/nopwd-aes-256bit-V5-R5.pdf +44 -0
  57. data/test/data/standard-security-handler/ownerpwd-aes-256bit-V5-R5.pdf +43 -0
  58. data/test/data/standard-security-handler/userpwd-aes-256bit-V5-R5.pdf +0 -0
  59. data/test/hexapdf/common_tokenizer_tests.rb +5 -0
  60. data/test/hexapdf/digital_signature/signing/test_default_handler.rb +6 -0
  61. data/test/hexapdf/digital_signature/test_cms_handler.rb +12 -7
  62. data/test/hexapdf/digital_signature/test_signature.rb +7 -0
  63. data/test/hexapdf/digital_signature/test_signatures.rb +12 -7
  64. data/test/hexapdf/encryption/test_standard_security_handler.rb +5 -2
  65. data/test/hexapdf/font/cmap/test_writer.rb +73 -16
  66. data/test/hexapdf/font/test_true_type_wrapper.rb +17 -3
  67. data/test/hexapdf/layout/test_list_box.rb +7 -7
  68. data/test/hexapdf/layout/test_table_box.rb +52 -0
  69. data/test/hexapdf/layout/test_text_fragment.rb +3 -3
  70. data/test/hexapdf/layout/test_text_layouter.rb +4 -2
  71. data/test/hexapdf/task/test_merge_acro_form.rb +104 -0
  72. data/test/hexapdf/task/test_optimize.rb +2 -0
  73. data/test/hexapdf/test_composer.rb +8 -0
  74. data/test/hexapdf/test_document.rb +12 -3
  75. data/test/hexapdf/test_importer.rb +7 -0
  76. data/test/hexapdf/test_parser.rb +7 -0
  77. data/test/hexapdf/test_writer.rb +19 -5
  78. data/test/hexapdf/type/acro_form/test_appearance_generator.rb +40 -23
  79. data/test/hexapdf/type/acro_form/test_form.rb +7 -8
  80. data/test/hexapdf/type/acro_form/test_signature_field.rb +3 -1
  81. data/test/hexapdf/type/acro_form/test_variable_text_field.rb +14 -1
  82. data/test/hexapdf/type/actions/test_launch.rb +6 -2
  83. data/test/hexapdf/type/annotations/test_widget.rb +4 -0
  84. data/test/hexapdf/type/test_font_type1.rb +5 -0
  85. data/test/hexapdf/type/test_form.rb +1 -1
  86. data/test/hexapdf/type/test_page.rb +7 -1
  87. metadata +8 -2
@@ -0,0 +1,104 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ require 'test_helper'
4
+ require 'hexapdf/document'
5
+ require 'hexapdf/task/merge_acro_form'
6
+
7
+ describe HexaPDF::Task::MergeAcroForm do
8
+ before do
9
+ @doc = HexaPDF::Document.new
10
+ @doc.pages.add
11
+ @doc.pages.add
12
+ form = @doc.acro_form(create: true)
13
+ field = form.create_text_field("Text")
14
+ field.create_widget(@doc.pages[0], Rect: [0, 0, 0, 0])
15
+ field.create_widget(@doc.pages[1], Rect: [0, 0, 0, 0])
16
+
17
+ form.create_text_field("Calc.Field a")
18
+ form.create_text_field("Calc.Field b")
19
+ field = form.create_text_field('Other.Calculation 1')
20
+ field.set_calculate_action(:sum, fields: ["Calc.Field a", "Calc.Field b"])
21
+ field.create_widget(@doc.pages[1])
22
+ field = form.create_text_field('Other.Calculation 2')
23
+ field.set_calculate_action(:sfn, fields: "Calc.Field\\ a + Calc.Field\\ b")
24
+ field.create_widget(@doc.pages[1])
25
+
26
+ @root_fields = @doc.acro_form.root_fields
27
+
28
+ @doc.dispatch_message(:complete_objects)
29
+ @doc.validate
30
+ @doc1 = @doc.duplicate
31
+ @pages = []
32
+ @pages << @doc.pages.add(@doc.import(@doc1.pages[0]))
33
+ @pages << @doc.pages.add(@doc.import(@doc1.pages[1]))
34
+ end
35
+
36
+ it "selects a unique name for the root field" do
37
+ @doc.task(:merge_acro_form, source: @doc1, pages: @pages)
38
+ assert_equal('merged_1', @root_fields[3][:T])
39
+
40
+ @root_fields << @doc.wrap({T: 'merged_23'})
41
+ @doc.task(:merge_acro_form, source: @doc1, pages: @pages)
42
+ assert_equal('merged_24', @root_fields[5][:T])
43
+ end
44
+
45
+ it "merges the /DR entry of the main AcroForm dictionary" do
46
+ @doc.task(:merge_acro_form, source: @doc1, pages: @pages)
47
+ assert(@doc.acro_form.default_resources[:Font].key?(:F2))
48
+ end
49
+
50
+ it "updates the /SigFlags if necessary" do
51
+ @doc.task(:merge_acro_form, source: @doc1, pages: [@pages[0]])
52
+ refute(@doc.acro_form.signature_flag?(:signatures_exist))
53
+
54
+ @pages[0][:Annots][0].form_field[:FT] = :Sig
55
+ @doc.task(:merge_acro_form, source: @doc1, pages: [@pages[0]])
56
+ refute(@doc.acro_form.signature_flag?(:signatures_exist))
57
+
58
+ @doc1.acro_form.signature_flag(:signatures_exist)
59
+ @doc.task(:merge_acro_form, source: @doc1, pages: [@pages[0]])
60
+ assert(@doc.acro_form.signature_flag?(:signatures_exist))
61
+ end
62
+
63
+ it "applies the /DA and /Q entries of the source AcroForm to the created root field" do
64
+ @doc1.acro_form.set_default_appearance_string
65
+ @doc1.acro_form[:Q] = @doc1.add(5)
66
+ @doc.task(:merge_acro_form, source: @doc1, pages: [@pages[0]])
67
+ assert_equal('0.0 g /F2 0 Tf', @root_fields[3][:DA])
68
+ assert_equal(5, @root_fields[3][:Q])
69
+ end
70
+
71
+ it "merges only the fields references in the given pages" do
72
+ @doc.task(:merge_acro_form, source: @doc1, pages: [@pages[0]])
73
+ assert_equal('merged_1', @root_fields[3][:T])
74
+ assert_equal(1, @root_fields[3][:Kids].size)
75
+ end
76
+
77
+ it "only merges fields that have at least one widget" do
78
+ @doc.task(:merge_acro_form, source: @doc1, pages: @pages)
79
+ assert_equal(2, @root_fields[3][:Kids].size)
80
+ assert_nil(@doc.acro_form.field_by_name('merged_1.Calc'))
81
+ end
82
+
83
+ it "updates the /DA entries of widgets and fields" do
84
+ @pages[0][:Annots][0][:DA] = '/F1 10 Tf'
85
+ @doc.task(:merge_acro_form, source: @doc1, pages: @pages)
86
+ field = @doc.acro_form.field_by_name('merged_1.Text')
87
+ assert_equal('0.0 g /F2 0 Tf', field[:DA])
88
+ assert_equal('/F2 10 Tf', field.each_widget.to_a[0][:DA])
89
+ end
90
+
91
+ it "doesn't update the calculation actions if no field with one is merged" do
92
+ @doc.task(:merge_acro_form, source: @doc1, pages: [@pages[0]])
93
+ assert_equal(2, @doc.acro_form[:CO].size)
94
+ end
95
+
96
+ it "updates the field names in known calculation actions" do
97
+ @doc.task(:merge_acro_form, source: @doc1, pages: @pages)
98
+ assert_equal(4, @doc.acro_form[:CO].size)
99
+ js = @doc.acro_form.field_by_name('merged_1.Other.Calculation 1')[:AA][:C][:JS]
100
+ assert_match(/merged_1.Calc.Field a/, js)
101
+ js = @doc.acro_form.field_by_name('merged_1.Other.Calculation 2')[:AA][:C][:JS]
102
+ assert_match(/merged_1.Calc.Field a/, js)
103
+ end
104
+ 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
 
@@ -63,6 +63,14 @@ describe HexaPDF::Composer do
63
63
  end
64
64
  end
65
65
 
66
+ it "writes the document to a string" do
67
+ pdf = HexaPDF::Composer.new
68
+ pdf.new_page
69
+ str = pdf.write_to_string
70
+ doc = HexaPDF::Document.new(io: StringIO.new(str))
71
+ assert_equal(2, doc.pages.count)
72
+ end
73
+
66
74
  describe "new_page" do
67
75
  it "creates a new page" do
68
76
  c = HexaPDF::Composer.new(page_size: [0, 0, 50, 100], margin: 10)
@@ -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
@@ -595,4 +595,13 @@ describe HexaPDF::Document do
595
595
  refute(dupped.encrypted?)
596
596
  end
597
597
  end
598
+
599
+ it "writes the document to a string" do
600
+ doc = HexaPDF::Document.new
601
+ doc.trailer.info[:test] = :test
602
+ str = doc.write_to_string(update_fields: false)
603
+ assert_equal(Encoding::ASCII_8BIT, str.encoding)
604
+ doc = HexaPDF::Document.new(io: StringIO.new(str))
605
+ assert_equal(:test, doc.trailer.info[:test])
606
+ end
598
607
  end
@@ -147,6 +147,13 @@ describe HexaPDF::Importer do
147
147
  assert_nil(obj[:pages])
148
148
  end
149
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
+
150
157
  it "imports Page objects correctly by copying the inherited values" do
151
158
  page = @importer.import(@source.pages[0])
152
159
  assert_equal(90, page[:Rotate])
@@ -152,6 +152,13 @@ describe HexaPDF::Parser do
152
152
  assert_equal('12', collector(stream.fiber))
153
153
  end
154
154
 
155
+ it "recovers from a non-existing indirect reference to a stream length value" do
156
+ create_parser("1 0 obj<</Length 2 0 R>> stream\n12(ab\nendstream endobj")
157
+ obj, _, _, stream = @parser.parse_indirect_object
158
+ assert_equal(5, obj[:Length])
159
+ assert_equal('12(ab', collector(stream.fiber))
160
+ end
161
+
155
162
  it "works even if the keyword endobj is missing or mangled" do
156
163
  create_parser("1 0 obj<</Length 4>>5")
157
164
  object, * = @parser.parse_indirect_object
@@ -48,10 +48,15 @@ describe HexaPDF::Writer do
48
48
  trailer
49
49
  <</Size 4/Root<</Type/Catalog>>/Info 3 0 R/Prev 219>>
50
50
  startxref
51
- 349
51
+ #{343 + HexaPDF::VERSION.length}
52
52
  %%EOF
53
53
  EOF
54
54
 
55
+ xref_stream = case HexaPDF::VERSION.length
56
+ when 5 then "x\xDAcbdlc``b`\xB0\x04\x93\x93\x19\x18\x00\f\x0F\x01["
57
+ when 6 then "x\xDAcbdlg``b`\xB0\x04\x93\x93\x18\x18\x00\f\e\x01["
58
+ else fail
59
+ end
55
60
  @compressed_input_io = StringIO.new(<<~EOF.force_encoding(Encoding::BINARY))
56
61
  %PDF-1.7
57
62
  %\xCF\xEC\xFF\xE8\xD7\xCB\xCD
@@ -64,7 +69,7 @@ describe HexaPDF::Writer do
64
69
  20
65
70
  endobj
66
71
  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
72
+ <</Size 6/Type/XRef/W[1 1 2]/Index[0 4 5 1]/Filter/FlateDecode/DecodeParms<</Columns 4/Predictor 12>>/Length 31>>stream
68
73
  x\xDAcb`\xF8\xFF\x9F\x89\x89\x95\x91\x91\xE9\x7F\x19\x03\x03\x13\x83\x10\x88he`\x00\x00B4\x04\x1E
69
74
  endstream
70
75
  endobj
@@ -80,12 +85,12 @@ describe HexaPDF::Writer do
80
85
  endstream
81
86
  endobj
82
87
  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
84
- x\xDAcbdlg``b`\xB0\x04\x93\x93\x18\x18\x00\f\e\x01[
88
+ <</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
89
+ #{xref_stream}
85
90
  endstream
86
91
  endobj
87
92
  startxref
88
- 448
93
+ #{442 + HexaPDF::VERSION.length}
89
94
  %%EOF
90
95
  EOF
91
96
  end
@@ -270,4 +275,13 @@ describe HexaPDF::Writer do
270
275
  doc = HexaPDF::Document.new(io: io)
271
276
  refute(doc.trailer.key?(:XRefStm))
272
277
  end
278
+
279
+ it "removes the /Type entry in a non-xref stream trailer" do
280
+ io = StringIO.new
281
+ doc = HexaPDF::Document.new
282
+ doc.trailer[:Type] = :XRef
283
+ doc.write(io)
284
+ doc = HexaPDF::Document.new(io: io)
285
+ refute(doc.trailer.key?(:Type))
286
+ end
273
287
  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
@@ -532,7 +549,7 @@ describe HexaPDF::Type::AcroForm::AppearanceGenerator do
532
549
  @field.text_alignment(:left)
533
550
  @generator.create_appearances
534
551
  assert_operators(@widget[:AP][:N].stream,
535
- [:set_text_matrix, [1, 0, 0, 1, 2, 6.41]],
552
+ [:move_text, [2, 6.41]],
536
553
  range: 7)
537
554
  end
538
555
 
@@ -540,7 +557,7 @@ describe HexaPDF::Type::AcroForm::AppearanceGenerator do
540
557
  @field.text_alignment(:right)
541
558
  @generator.create_appearances
542
559
  assert_operators(@widget[:AP][:N].stream,
543
- [:set_text_matrix, [1, 0, 0, 1, 78.55, 6.41]],
560
+ [:move_text, [78.55, 6.41]],
544
561
  range: 7)
545
562
  end
546
563
 
@@ -548,7 +565,7 @@ describe HexaPDF::Type::AcroForm::AppearanceGenerator do
548
565
  @field.text_alignment(:center)
549
566
  @generator.create_appearances
550
567
  assert_operators(@widget[:AP][:N].stream,
551
- [:set_text_matrix, [1, 0, 0, 1, 40.275, 6.41]],
568
+ [:move_text, [40.275, 6.41]],
552
569
  range: 7)
553
570
  end
554
571
 
@@ -559,7 +576,7 @@ describe HexaPDF::Type::AcroForm::AppearanceGenerator do
559
576
 
560
577
  @generator.create_appearances
561
578
  assert_operators(@widget[:AP][:N].stream,
562
- [:set_text_matrix, [1, 0, 0, 1, 2, 6.41]],
579
+ [:move_text, [2, 6.41]],
563
580
  range: 7)
564
581
  ensure
565
582
  font_metrics.cap_height = cap_height
@@ -569,7 +586,7 @@ describe HexaPDF::Type::AcroForm::AppearanceGenerator do
569
586
  @widget[:Rect].height = 5
570
587
  @generator.create_appearances
571
588
  assert_operators(@widget[:AP][:N].stream,
572
- [:set_text_matrix, [1, 0, 0, 1, 2, 3.07]],
589
+ [:move_text, [2, 3.07]],
573
590
  range: 7)
574
591
  end
575
592
  end
@@ -597,7 +614,7 @@ describe HexaPDF::Type::AcroForm::AppearanceGenerator do
597
614
  [:set_font_and_size, [:F1, 10]],
598
615
  [:set_device_rgb_non_stroking_color, [1.0, 0.0, 0.0]],
599
616
  [:begin_text],
600
- [:set_text_matrix, [1, 0, 0, 1, 2, 2.035]],
617
+ [:move_text, [2, 2.035]],
601
618
  [:show_text, ["Te "]],
602
619
  [:set_font_and_size, [:F2, 10]],
603
620
  [:move_text, [14.45, 0]],
@@ -628,7 +645,7 @@ describe HexaPDF::Type::AcroForm::AppearanceGenerator do
628
645
  @field.text_alignment(:left)
629
646
  @generator.create_appearances
630
647
  assert_operators(@widget[:AP][:N].stream,
631
- [:set_text_matrix, [1, 0, 0, 1, 2, 16.195]],
648
+ [:move_text, [2, 16.195]],
632
649
  range: 9)
633
650
  end
634
651
 
@@ -636,7 +653,7 @@ describe HexaPDF::Type::AcroForm::AppearanceGenerator do
636
653
  @field.text_alignment(:right)
637
654
  @generator.create_appearances
638
655
  assert_operators(@widget[:AP][:N].stream,
639
- [:set_text_matrix, [1, 0, 0, 1, 78.55, 16.195]],
656
+ [:move_text, [78.55, 16.195]],
640
657
  range: 9)
641
658
  end
642
659
 
@@ -644,7 +661,7 @@ describe HexaPDF::Type::AcroForm::AppearanceGenerator do
644
661
  @field.text_alignment(:center)
645
662
  @generator.create_appearances
646
663
  assert_operators(@widget[:AP][:N].stream,
647
- [:set_text_matrix, [1, 0, 0, 1, 40.275, 16.195]],
664
+ [:move_text, [40.275, 16.195]],
648
665
  range: 9)
649
666
  end
650
667
  end
@@ -664,7 +681,7 @@ describe HexaPDF::Type::AcroForm::AppearanceGenerator do
664
681
  [:set_font_and_size, [:F1, 10]],
665
682
  [:set_device_rgb_non_stroking_color, [1.0, 0.0, 0.0]],
666
683
  [:begin_text],
667
- [:set_text_matrix, [1, 0, 0, 1, 2, 16.195]],
684
+ [:move_text, [2, 16.195]],
668
685
  [:show_text, ['Test']],
669
686
  [:move_text_next_line],
670
687
  [:show_text, ['Value']],
@@ -686,7 +703,7 @@ describe HexaPDF::Type::AcroForm::AppearanceGenerator do
686
703
  [:set_leading, [9.25]],
687
704
  [:set_font_and_size, [:F1, 8]],
688
705
  [:begin_text],
689
- [:set_text_matrix, [1, 0, 0, 1, 2, 18.556]],
706
+ [:move_text, [2, 18.556]],
690
707
  [:show_text, ['Test']],
691
708
  [:move_text_next_line],
692
709
  [:show_text, ['Test']],
@@ -717,7 +734,7 @@ describe HexaPDF::Type::AcroForm::AppearanceGenerator do
717
734
  @field.text_alignment(:left)
718
735
  @generator.create_appearances
719
736
  assert_operators(@widget[:AP][:N].stream,
720
- [:set_text_matrix, [1, 0, 0, 1, 2.945, 6.41]],
737
+ [:move_text, [2.945, 6.41]],
721
738
  range: 7)
722
739
  end
723
740
 
@@ -725,7 +742,7 @@ describe HexaPDF::Type::AcroForm::AppearanceGenerator do
725
742
  @field.text_alignment(:right)
726
743
  @generator.create_appearances
727
744
  assert_operators(@widget[:AP][:N].stream,
728
- [:set_text_matrix, [1, 0, 0, 1, 62.945, 6.41]],
745
+ [:move_text, [62.945, 6.41]],
729
746
  range: 7)
730
747
  end
731
748
 
@@ -733,7 +750,7 @@ describe HexaPDF::Type::AcroForm::AppearanceGenerator do
733
750
  @field.text_alignment(:center)
734
751
  @generator.create_appearances
735
752
  assert_operators(@widget[:AP][:N].stream,
736
- [:set_text_matrix, [1, 0, 0, 1, 32.945, 6.41]],
753
+ [:move_text, [32.945, 6.41]],
737
754
  range: 7)
738
755
  end
739
756
 
@@ -742,7 +759,7 @@ describe HexaPDF::Type::AcroForm::AppearanceGenerator do
742
759
  @field.text_alignment(:center)
743
760
  @generator.create_appearances
744
761
  assert_operators(@widget[:AP][:N].stream,
745
- [:set_text_matrix, [1, 0, 0, 1, 22.39, 6.41]],
762
+ [:move_text, [22.39, 6.41]],
746
763
  range: 7)
747
764
  end
748
765
  end
@@ -760,7 +777,7 @@ describe HexaPDF::Type::AcroForm::AppearanceGenerator do
760
777
  [:set_font_and_size, [:F1, 10]],
761
778
  [:set_device_rgb_non_stroking_color, [1.0, 0.0, 0.0]],
762
779
  [:begin_text],
763
- [:set_text_matrix, [1, 0, 0, 1, 2.945, 6.41]],
780
+ [:move_text, [2.945, 6.41]],
764
781
  [:show_text_with_positioning, [['T', -416.5, 'e', -472, 'x', -611, 't']]],
765
782
  [:end_text],
766
783
  [:restore_graphics_state],
@@ -772,7 +789,7 @@ describe HexaPDF::Type::AcroForm::AppearanceGenerator do
772
789
  @generator.create_appearances
773
790
  assert_operators(@widget[:AP][:N].stream,
774
791
  [[:begin_text],
775
- [:set_text_matrix, [1, 0, 0, 1, 2, 6.41]],
792
+ [:move_text, [2, 6.41]],
776
793
  [:end_text]], range: 6..8)
777
794
  end
778
795
 
@@ -853,7 +870,7 @@ describe HexaPDF::Type::AcroForm::AppearanceGenerator do
853
870
  [:set_font_and_size, [:F1, 12]],
854
871
  [:set_device_rgb_non_stroking_color, [1.0, 0.0, 0.0]],
855
872
  [:begin_text],
856
- [:set_text_matrix, [1, 0, 0, 1, 2, 23.609]],
873
+ [:move_text, [2, 23.609]],
857
874
  [:show_text, ["a"]],
858
875
  [:move_text_next_line],
859
876
  [:show_text, ["b"]],
@@ -159,11 +159,16 @@ describe HexaPDF::Type::AcroForm::Form do
159
159
  end
160
160
 
161
161
  def applies_variable_text_properties(method, **args)
162
- field = @acro_form.send(method, "field", **args, font: 'Times')
162
+ field = @acro_form.send(method, "field", **args)
163
163
  font_name, font_size, font_color = field.parse_default_appearance_string
164
- assert_equal(:'Times-Roman', @acro_form.default_resources.font(font_name)[:BaseFont])
164
+ assert_equal(:'Helvetica', @acro_form.default_resources.font(font_name)[:BaseFont])
165
165
  assert_equal(0, font_size)
166
166
  assert_equal(HexaPDF::Content::ColorSpace::DeviceGray.new.color(0), font_color)
167
+ assert_equal(0, field.value[:Q])
168
+
169
+ field = @acro_form.send(method, "field", **args, font: 'Times')
170
+ font_name, font_size, font_color = field.parse_default_appearance_string
171
+ assert_equal(:'Times-Roman', @acro_form.default_resources.font(font_name)[:BaseFont])
167
172
 
168
173
  field = @acro_form.send(method, "field", **args, font_options: {variant: :bold})
169
174
  font_name, = field.parse_default_appearance_string
@@ -171,7 +176,6 @@ describe HexaPDF::Type::AcroForm::Form do
171
176
 
172
177
  field = @acro_form.send(method, "field", **args, font_size: 10)
173
178
  font_name, font_size = field.parse_default_appearance_string
174
- assert_equal(:Helvetica, @acro_form.default_resources.font(font_name)[:BaseFont])
175
179
  assert_equal(10, font_size)
176
180
 
177
181
  field = @acro_form.send(method, "field", **args, font_color: "red")
@@ -507,11 +511,6 @@ describe HexaPDF::Type::AcroForm::Form do
507
511
  assert(@acro_form.validate)
508
512
  end
509
513
 
510
- it "set the default appearance string, though optional, to a valid value to avoid problems" do
511
- assert(@acro_form.validate)
512
- assert_equal("0.0 g /F1 0 Tf", @acro_form[:DA])
513
- end
514
-
515
514
  describe "field hierarchy validation" do
516
515
  before do
517
516
  @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
@@ -14,10 +14,14 @@ describe HexaPDF::Type::Actions::Launch do
14
14
  it "needs a launch target" do
15
15
  refute(@action.validate)
16
16
 
17
- @action.value = {F: {}}
17
+ @action.value = {Win: {F: "test.exe"}}
18
+ assert(@action.validate)
19
+ @action.value = {Mac: 'test'}
20
+ assert(@action.validate)
21
+ @action.value = {Unix: 'test'}
18
22
  assert(@action.validate)
19
23
 
20
- @action.value = {Win: {F: "test.exe"}}
24
+ @action.value = {F: {}}
21
25
  assert(@action.validate)
22
26
  end
23
27
  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
@@ -138,5 +138,10 @@ describe HexaPDF::Type::FontType1 do
138
138
  @embedded_font.delete(:FontDescriptor)
139
139
  refute(@embedded_font.validate)
140
140
  end
141
+
142
+ it "ensures a correct Symbol value for the /Encoding key" do
143
+ @font[:Encoding] = :Other
144
+ refute(@font.validate)
145
+ end
141
146
  end
142
147
  end
@@ -57,7 +57,7 @@ describe HexaPDF::Type::Form do
57
57
  it "creates the resource dictionary if it is not found" do
58
58
  resources = @form.resources
59
59
  assert_equal(:XXResources, resources.type)
60
- assert_equal({ProcSet: [:PDF, :Text, :ImageB, :ImageC, :ImageI]}, resources.value)
60
+ assert_equal({}, resources.value)
61
61
  end
62
62
 
63
63
  it "returns the already used resource dictionary" do
@@ -358,6 +358,12 @@ describe HexaPDF::Type::Page do
358
358
  page[:Contents] = [@doc.wrap({}, stream: 'q 10'), @doc.wrap({}, stream: 'w Q')]
359
359
  assert_equal('q 10 w Q', page.contents)
360
360
  end
361
+
362
+ it "handles null objects in the /Contents array" do
363
+ page = @doc.pages.add
364
+ page[:Contents] = [@doc.wrap({}, stream: 'q 10'), nil]
365
+ assert_equal('q 10 ', page.contents)
366
+ end
361
367
  end
362
368
 
363
369
  describe "contents=" do
@@ -389,7 +395,7 @@ describe HexaPDF::Type::Page do
389
395
  page = @doc.add({Type: :Page, Parent: @doc.pages.root})
390
396
  resources = page.resources
391
397
  assert_equal(:XXResources, resources.type)
392
- assert_equal({ProcSet: [:PDF, :Text, :ImageB, :ImageC, :ImageI]}, resources.value)
398
+ assert_equal({}, resources.value)
393
399
  end
394
400
 
395
401
  it "returns the already used resource dictionary" 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.46.0
4
+ version: 1.0.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-08-11 00:00:00.000000000 Z
11
+ date: 2024-10-26 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: cmdparse
@@ -469,6 +469,7 @@ files:
469
469
  - lib/hexapdf/stream.rb
470
470
  - lib/hexapdf/task.rb
471
471
  - lib/hexapdf/task/dereference.rb
472
+ - lib/hexapdf/task/merge_acro_form.rb
472
473
  - lib/hexapdf/task/optimize.rb
473
474
  - lib/hexapdf/task/pdfa.rb
474
475
  - lib/hexapdf/test_utils.rb
@@ -594,21 +595,25 @@ files:
594
595
  - test/data/minimal.pdf
595
596
  - test/data/standard-security-handler/README
596
597
  - test/data/standard-security-handler/bothpwd-aes-128bit-V4.pdf
598
+ - test/data/standard-security-handler/bothpwd-aes-256bit-V5-R5.pdf
597
599
  - test/data/standard-security-handler/bothpwd-aes-256bit-V5.pdf
598
600
  - test/data/standard-security-handler/bothpwd-arc4-128bit-V2.pdf
599
601
  - test/data/standard-security-handler/bothpwd-arc4-128bit-V4.pdf
600
602
  - test/data/standard-security-handler/bothpwd-arc4-40bit-V1.pdf
601
603
  - test/data/standard-security-handler/nopwd-aes-128bit-V4.pdf
604
+ - test/data/standard-security-handler/nopwd-aes-256bit-V5-R5.pdf
602
605
  - test/data/standard-security-handler/nopwd-aes-256bit-V5.pdf
603
606
  - test/data/standard-security-handler/nopwd-arc4-128bit-V2.pdf
604
607
  - test/data/standard-security-handler/nopwd-arc4-128bit-V4.pdf
605
608
  - test/data/standard-security-handler/nopwd-arc4-40bit-V1.pdf
606
609
  - test/data/standard-security-handler/ownerpwd-aes-128bit-V4.pdf
610
+ - test/data/standard-security-handler/ownerpwd-aes-256bit-V5-R5.pdf
607
611
  - test/data/standard-security-handler/ownerpwd-aes-256bit-V5.pdf
608
612
  - test/data/standard-security-handler/ownerpwd-arc4-128bit-V2.pdf
609
613
  - test/data/standard-security-handler/ownerpwd-arc4-128bit-V4.pdf
610
614
  - test/data/standard-security-handler/ownerpwd-arc4-40bit-V1.pdf
611
615
  - test/data/standard-security-handler/userpwd-aes-128bit-V4.pdf
616
+ - test/data/standard-security-handler/userpwd-aes-256bit-V5-R5.pdf
612
617
  - test/data/standard-security-handler/userpwd-aes-256bit-V5.pdf
613
618
  - test/data/standard-security-handler/userpwd-arc4-128bit-V2.pdf
614
619
  - test/data/standard-security-handler/userpwd-arc4-128bit-V4.pdf
@@ -724,6 +729,7 @@ files:
724
729
  - test/hexapdf/layout/test_text_shaper.rb
725
730
  - test/hexapdf/layout/test_width_from_polygon.rb
726
731
  - test/hexapdf/task/test_dereference.rb
732
+ - test/hexapdf/task/test_merge_acro_form.rb
727
733
  - test/hexapdf/task/test_optimize.rb
728
734
  - test/hexapdf/task/test_pdfa.rb
729
735
  - test/hexapdf/test_composer.rb