hexapdf 0.47.0 → 1.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (75) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +65 -16
  3. data/lib/hexapdf/cli.rb +14 -1
  4. data/lib/hexapdf/composer.rb +7 -0
  5. data/lib/hexapdf/configuration.rb +2 -0
  6. data/lib/hexapdf/content/parser.rb +3 -1
  7. data/lib/hexapdf/digital_signature/cms_handler.rb +13 -0
  8. data/lib/hexapdf/digital_signature/signature.rb +1 -1
  9. data/lib/hexapdf/digital_signature/signing/default_handler.rb +1 -0
  10. data/lib/hexapdf/document.rb +14 -3
  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/layout/text_fragment.rb +2 -1
  15. data/lib/hexapdf/object.rb +1 -1
  16. data/lib/hexapdf/parser.rb +6 -2
  17. data/lib/hexapdf/reference.rb +1 -1
  18. data/lib/hexapdf/task/merge_acro_form.rb +164 -0
  19. data/lib/hexapdf/task.rb +1 -0
  20. data/lib/hexapdf/tokenizer.rb +2 -0
  21. data/lib/hexapdf/type/acro_form/form.rb +14 -27
  22. data/lib/hexapdf/type/acro_form/signature_field.rb +16 -6
  23. data/lib/hexapdf/type/acro_form/variable_text_field.rb +1 -1
  24. data/lib/hexapdf/type/actions/go_to.rb +1 -0
  25. data/lib/hexapdf/type/actions/go_to_r.rb +1 -0
  26. data/lib/hexapdf/type/actions/launch.rb +5 -1
  27. data/lib/hexapdf/type/annotation.rb +6 -1
  28. data/lib/hexapdf/type/annotations/markup_annotation.rb +14 -1
  29. data/lib/hexapdf/type/catalog.rb +3 -0
  30. data/lib/hexapdf/type/cid_font.rb +4 -1
  31. data/lib/hexapdf/type/file_specification.rb +17 -14
  32. data/lib/hexapdf/type/font_descriptor.rb +4 -3
  33. data/lib/hexapdf/type/font_simple.rb +3 -1
  34. data/lib/hexapdf/type/font_true_type.rb +2 -0
  35. data/lib/hexapdf/type/font_type0.rb +1 -1
  36. data/lib/hexapdf/type/font_type1.rb +7 -0
  37. data/lib/hexapdf/type/font_type3.rb +0 -1
  38. data/lib/hexapdf/type/form.rb +5 -2
  39. data/lib/hexapdf/type/graphics_state_parameter.rb +7 -4
  40. data/lib/hexapdf/type/image.rb +8 -4
  41. data/lib/hexapdf/type/info.rb +2 -2
  42. data/lib/hexapdf/type/mark_information.rb +2 -2
  43. data/lib/hexapdf/type/optional_content_configuration.rb +1 -1
  44. data/lib/hexapdf/type/optional_content_membership.rb +1 -1
  45. data/lib/hexapdf/type/page.rb +5 -3
  46. data/lib/hexapdf/type/resources.rb +6 -6
  47. data/lib/hexapdf/type/viewer_preferences.rb +4 -3
  48. data/lib/hexapdf/utils/sorted_tree_node.rb +12 -2
  49. data/lib/hexapdf/version.rb +1 -1
  50. data/lib/hexapdf/writer.rb +1 -0
  51. data/lib/hexapdf/xref_section.rb +20 -4
  52. data/test/hexapdf/common_tokenizer_tests.rb +5 -0
  53. data/test/hexapdf/digital_signature/signing/test_default_handler.rb +6 -0
  54. data/test/hexapdf/digital_signature/test_cms_handler.rb +12 -7
  55. data/test/hexapdf/digital_signature/test_signature.rb +7 -0
  56. data/test/hexapdf/digital_signature/test_signatures.rb +8 -3
  57. data/test/hexapdf/font/cmap/test_writer.rb +73 -16
  58. data/test/hexapdf/font/test_true_type_wrapper.rb +17 -3
  59. data/test/hexapdf/layout/test_list_box.rb +7 -7
  60. data/test/hexapdf/layout/test_text_fragment.rb +3 -3
  61. data/test/hexapdf/layout/test_text_layouter.rb +4 -2
  62. data/test/hexapdf/task/test_merge_acro_form.rb +104 -0
  63. data/test/hexapdf/test_composer.rb +8 -0
  64. data/test/hexapdf/test_document.rb +9 -0
  65. data/test/hexapdf/test_parser.rb +23 -6
  66. data/test/hexapdf/test_writer.rb +10 -5
  67. data/test/hexapdf/test_xref_section.rb +15 -0
  68. data/test/hexapdf/type/acro_form/test_appearance_generator.rb +18 -18
  69. data/test/hexapdf/type/acro_form/test_form.rb +7 -3
  70. data/test/hexapdf/type/actions/test_launch.rb +6 -2
  71. data/test/hexapdf/type/test_font_type1.rb +5 -0
  72. data/test/hexapdf/type/test_form.rb +1 -1
  73. data/test/hexapdf/type/test_page.rb +7 -1
  74. data/test/hexapdf/utils/test_sorted_tree_node.rb +7 -6
  75. metadata +4 -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
@@ -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)
@@ -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
@@ -33,18 +33,23 @@ describe HexaPDF::Parser do
33
33
  endstream
34
34
  endobj
35
35
 
36
+ 5 0 obj
37
+ 1 0 R
38
+ endobj
39
+
36
40
  xref
37
41
  0 4
38
42
  0000000000 65535 f
39
43
  0000000010 00000 n
40
44
  0000000029 00000 n
41
45
  0000000000 65535 f
42
- 3 1
46
+ 3 2
43
47
  0000000556 00000 n
48
+ 0000000308 00000 n
44
49
  trailer
45
50
  << /Test (now) >>
46
51
  startxref
47
- 308
52
+ 330
48
53
  %%EOF
49
54
  EOF
50
55
  end
@@ -152,6 +157,13 @@ describe HexaPDF::Parser do
152
157
  assert_equal('12', collector(stream.fiber))
153
158
  end
154
159
 
160
+ it "recovers from a non-existing indirect reference to a stream length value" do
161
+ create_parser("1 0 obj<</Length 2 0 R>> stream\n12(ab\nendstream endobj")
162
+ obj, _, _, stream = @parser.parse_indirect_object
163
+ assert_equal(5, obj[:Length])
164
+ assert_equal('12(ab', collector(stream.fiber))
165
+ end
166
+
155
167
  it "works even if the keyword endobj is missing or mangled" do
156
168
  create_parser("1 0 obj<</Length 4>>5")
157
169
  object, * = @parser.parse_indirect_object
@@ -298,6 +310,11 @@ describe HexaPDF::Parser do
298
310
  assert_equal(0, obj.gen)
299
311
  end
300
312
 
313
+ it "handles the case of the value of an indirect object being an indirect reference" do
314
+ obj = @parser.load_object(HexaPDF::XRefSection.in_use_entry(5, 0, 308))
315
+ assert_equal(1, obj.oid)
316
+ end
317
+
301
318
  describe "with strict parsing" do
302
319
  it "raises an error if an indirect object has an offset of 0" do
303
320
  @document.config['parser.on_correctable_error'] = proc { true }
@@ -336,13 +353,13 @@ describe HexaPDF::Parser do
336
353
 
337
354
  describe "startxref_offset" do
338
355
  it "caches the offset value" do
339
- assert_equal(308, @parser.startxref_offset)
340
- @parser.instance_eval { @io }.string.sub!(/308\n/, "309\n")
341
- assert_equal(308, @parser.startxref_offset)
356
+ assert_equal(330, @parser.startxref_offset)
357
+ @parser.instance_eval { @io }.string.sub!(/330\n/, "309\n")
358
+ assert_equal(330, @parser.startxref_offset)
342
359
  end
343
360
 
344
361
  it "returns the correct offset" do
345
- assert_equal(308, @parser.startxref_offset)
362
+ assert_equal(330, @parser.startxref_offset)
346
363
  end
347
364
 
348
365
  it "ignores garbage at the end of the file" do
@@ -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\xDAcbdlg``b`\xB0\x04\x93\x93\x19\x18\x00\f\x1E\x01\\"
57
+ when 6 then "x\xDAcbd\xEC```b`\xB0\x04\x93\x93\x18\x18\x00\f*\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,8 +69,8 @@ describe HexaPDF::Writer do
64
69
  20
65
70
  endobj
66
71
  3 0 obj
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
- x\xDAcb`\xF8\xFF\x9F\x89\x89\x95\x91\x91\xE9\x7F\x19\x03\x03\x13\x83\x10\x88he`\x00\x00B4\x04\x1E
72
+ <</Size 6/Type/XRef/W[1 1 2]/Index[0 6]/Filter/FlateDecode/DecodeParms<</Columns 4/Predictor 12>>/Length 36>>stream
73
+ x\xDAcb`\xF8\xFF\x9F\x89\x89\x95\x91\x91\xE9\x7F\x19\x03\x03\x13\x83\x10\x90\xF8_\f\x14c\x14bd\x04\x00lk\a
69
74
  endstream
70
75
  endobj
71
76
  startxref
@@ -81,11 +86,11 @@ describe HexaPDF::Writer do
81
86
  endobj
82
87
  4 0 obj
83
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
84
- x\xDAcbdlg``b`\xB0\x04\x93\x93\x18\x18\x00\f\e\x01[
89
+ #{xref_stream}
85
90
  endstream
86
91
  endobj
87
92
  startxref
88
- 448
93
+ #{443 + HexaPDF::VERSION.length}
89
94
  %%EOF
90
95
  EOF
91
96
  end
@@ -57,5 +57,20 @@ describe HexaPDF::XRefSection do
57
57
  @xref_section.add_in_use_entry(20, 0, 0)
58
58
  assert_subsections([[1, 2], [10, 11], [20]])
59
59
  end
60
+
61
+ it "yields a single subsection if the section was marked as the initial one" do
62
+ @xref_section.mark_as_initial_section!
63
+ @xref_section.add_in_use_entry(6, 0, 0)
64
+ @xref_section.add_in_use_entry(7, 0, 0)
65
+ @xref_section.add_in_use_entry(9, 0, 0)
66
+ @xref_section.add_in_use_entry(1, 0, 0)
67
+ @xref_section.add_in_use_entry(2, 0, 0)
68
+ result = @xref_section.each_subsection.map {|s| s.map {|e| [e.oid, e.type] }}
69
+ assert_equal([[[1, :in_use], [2, :in_use],
70
+ [3, :free], [4, :free], [5, :free],
71
+ [6, :in_use], [7, :in_use],
72
+ [8, :free],
73
+ [9, :in_use]]], result)
74
+ end
60
75
  end
61
76
  end
@@ -549,7 +549,7 @@ describe HexaPDF::Type::AcroForm::AppearanceGenerator do
549
549
  @field.text_alignment(:left)
550
550
  @generator.create_appearances
551
551
  assert_operators(@widget[:AP][:N].stream,
552
- [:set_text_matrix, [1, 0, 0, 1, 2, 6.41]],
552
+ [:move_text, [2, 6.41]],
553
553
  range: 7)
554
554
  end
555
555
 
@@ -557,7 +557,7 @@ describe HexaPDF::Type::AcroForm::AppearanceGenerator do
557
557
  @field.text_alignment(:right)
558
558
  @generator.create_appearances
559
559
  assert_operators(@widget[:AP][:N].stream,
560
- [:set_text_matrix, [1, 0, 0, 1, 78.55, 6.41]],
560
+ [:move_text, [78.55, 6.41]],
561
561
  range: 7)
562
562
  end
563
563
 
@@ -565,7 +565,7 @@ describe HexaPDF::Type::AcroForm::AppearanceGenerator do
565
565
  @field.text_alignment(:center)
566
566
  @generator.create_appearances
567
567
  assert_operators(@widget[:AP][:N].stream,
568
- [:set_text_matrix, [1, 0, 0, 1, 40.275, 6.41]],
568
+ [:move_text, [40.275, 6.41]],
569
569
  range: 7)
570
570
  end
571
571
 
@@ -576,7 +576,7 @@ describe HexaPDF::Type::AcroForm::AppearanceGenerator do
576
576
 
577
577
  @generator.create_appearances
578
578
  assert_operators(@widget[:AP][:N].stream,
579
- [:set_text_matrix, [1, 0, 0, 1, 2, 6.41]],
579
+ [:move_text, [2, 6.41]],
580
580
  range: 7)
581
581
  ensure
582
582
  font_metrics.cap_height = cap_height
@@ -586,7 +586,7 @@ describe HexaPDF::Type::AcroForm::AppearanceGenerator do
586
586
  @widget[:Rect].height = 5
587
587
  @generator.create_appearances
588
588
  assert_operators(@widget[:AP][:N].stream,
589
- [:set_text_matrix, [1, 0, 0, 1, 2, 3.07]],
589
+ [:move_text, [2, 3.07]],
590
590
  range: 7)
591
591
  end
592
592
  end
@@ -614,7 +614,7 @@ describe HexaPDF::Type::AcroForm::AppearanceGenerator do
614
614
  [:set_font_and_size, [:F1, 10]],
615
615
  [:set_device_rgb_non_stroking_color, [1.0, 0.0, 0.0]],
616
616
  [:begin_text],
617
- [:set_text_matrix, [1, 0, 0, 1, 2, 2.035]],
617
+ [:move_text, [2, 2.035]],
618
618
  [:show_text, ["Te "]],
619
619
  [:set_font_and_size, [:F2, 10]],
620
620
  [:move_text, [14.45, 0]],
@@ -645,7 +645,7 @@ describe HexaPDF::Type::AcroForm::AppearanceGenerator do
645
645
  @field.text_alignment(:left)
646
646
  @generator.create_appearances
647
647
  assert_operators(@widget[:AP][:N].stream,
648
- [:set_text_matrix, [1, 0, 0, 1, 2, 16.195]],
648
+ [:move_text, [2, 16.195]],
649
649
  range: 9)
650
650
  end
651
651
 
@@ -653,7 +653,7 @@ describe HexaPDF::Type::AcroForm::AppearanceGenerator do
653
653
  @field.text_alignment(:right)
654
654
  @generator.create_appearances
655
655
  assert_operators(@widget[:AP][:N].stream,
656
- [:set_text_matrix, [1, 0, 0, 1, 78.55, 16.195]],
656
+ [:move_text, [78.55, 16.195]],
657
657
  range: 9)
658
658
  end
659
659
 
@@ -661,7 +661,7 @@ describe HexaPDF::Type::AcroForm::AppearanceGenerator do
661
661
  @field.text_alignment(:center)
662
662
  @generator.create_appearances
663
663
  assert_operators(@widget[:AP][:N].stream,
664
- [:set_text_matrix, [1, 0, 0, 1, 40.275, 16.195]],
664
+ [:move_text, [40.275, 16.195]],
665
665
  range: 9)
666
666
  end
667
667
  end
@@ -681,7 +681,7 @@ describe HexaPDF::Type::AcroForm::AppearanceGenerator do
681
681
  [:set_font_and_size, [:F1, 10]],
682
682
  [:set_device_rgb_non_stroking_color, [1.0, 0.0, 0.0]],
683
683
  [:begin_text],
684
- [:set_text_matrix, [1, 0, 0, 1, 2, 16.195]],
684
+ [:move_text, [2, 16.195]],
685
685
  [:show_text, ['Test']],
686
686
  [:move_text_next_line],
687
687
  [:show_text, ['Value']],
@@ -703,7 +703,7 @@ describe HexaPDF::Type::AcroForm::AppearanceGenerator do
703
703
  [:set_leading, [9.25]],
704
704
  [:set_font_and_size, [:F1, 8]],
705
705
  [:begin_text],
706
- [:set_text_matrix, [1, 0, 0, 1, 2, 18.556]],
706
+ [:move_text, [2, 18.556]],
707
707
  [:show_text, ['Test']],
708
708
  [:move_text_next_line],
709
709
  [:show_text, ['Test']],
@@ -734,7 +734,7 @@ describe HexaPDF::Type::AcroForm::AppearanceGenerator do
734
734
  @field.text_alignment(:left)
735
735
  @generator.create_appearances
736
736
  assert_operators(@widget[:AP][:N].stream,
737
- [:set_text_matrix, [1, 0, 0, 1, 2.945, 6.41]],
737
+ [:move_text, [2.945, 6.41]],
738
738
  range: 7)
739
739
  end
740
740
 
@@ -742,7 +742,7 @@ describe HexaPDF::Type::AcroForm::AppearanceGenerator do
742
742
  @field.text_alignment(:right)
743
743
  @generator.create_appearances
744
744
  assert_operators(@widget[:AP][:N].stream,
745
- [:set_text_matrix, [1, 0, 0, 1, 62.945, 6.41]],
745
+ [:move_text, [62.945, 6.41]],
746
746
  range: 7)
747
747
  end
748
748
 
@@ -750,7 +750,7 @@ describe HexaPDF::Type::AcroForm::AppearanceGenerator do
750
750
  @field.text_alignment(:center)
751
751
  @generator.create_appearances
752
752
  assert_operators(@widget[:AP][:N].stream,
753
- [:set_text_matrix, [1, 0, 0, 1, 32.945, 6.41]],
753
+ [:move_text, [32.945, 6.41]],
754
754
  range: 7)
755
755
  end
756
756
 
@@ -759,7 +759,7 @@ describe HexaPDF::Type::AcroForm::AppearanceGenerator do
759
759
  @field.text_alignment(:center)
760
760
  @generator.create_appearances
761
761
  assert_operators(@widget[:AP][:N].stream,
762
- [:set_text_matrix, [1, 0, 0, 1, 22.39, 6.41]],
762
+ [:move_text, [22.39, 6.41]],
763
763
  range: 7)
764
764
  end
765
765
  end
@@ -777,7 +777,7 @@ describe HexaPDF::Type::AcroForm::AppearanceGenerator do
777
777
  [:set_font_and_size, [:F1, 10]],
778
778
  [:set_device_rgb_non_stroking_color, [1.0, 0.0, 0.0]],
779
779
  [:begin_text],
780
- [:set_text_matrix, [1, 0, 0, 1, 2.945, 6.41]],
780
+ [:move_text, [2.945, 6.41]],
781
781
  [:show_text_with_positioning, [['T', -416.5, 'e', -472, 'x', -611, 't']]],
782
782
  [:end_text],
783
783
  [:restore_graphics_state],
@@ -789,7 +789,7 @@ describe HexaPDF::Type::AcroForm::AppearanceGenerator do
789
789
  @generator.create_appearances
790
790
  assert_operators(@widget[:AP][:N].stream,
791
791
  [[:begin_text],
792
- [:set_text_matrix, [1, 0, 0, 1, 2, 6.41]],
792
+ [:move_text, [2, 6.41]],
793
793
  [:end_text]], range: 6..8)
794
794
  end
795
795
 
@@ -870,7 +870,7 @@ describe HexaPDF::Type::AcroForm::AppearanceGenerator do
870
870
  [:set_font_and_size, [:F1, 12]],
871
871
  [:set_device_rgb_non_stroking_color, [1.0, 0.0, 0.0]],
872
872
  [:begin_text],
873
- [:set_text_matrix, [1, 0, 0, 1, 2, 23.609]],
873
+ [:move_text, [2, 23.609]],
874
874
  [:show_text, ["a"]],
875
875
  [:move_text_next_line],
876
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")
@@ -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
@@ -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
@@ -12,10 +12,12 @@ describe HexaPDF::Utils::SortedTreeNode do
12
12
  end
13
13
 
14
14
  def add_multilevel_entries
15
- @kid11 = @doc.add({Limits: ['c', 'f'], Names: ['c', 1, 'f', 1]}, type: HexaPDF::NameTreeNode)
15
+ item = @doc.add(1)
16
+ @item_ref = HexaPDF::Reference.new(item.oid, item.gen)
17
+ @kid11 = @doc.add({Limits: ['c', 'f'], Names: ['c', @item_ref, 'f', 1]}, type: HexaPDF::NameTreeNode)
16
18
  @kid12 = @doc.add({Limits: ['i', 'm'], Names: ['i', 1, 'm', 1]}, type: HexaPDF::NameTreeNode)
17
19
  ref = HexaPDF::Reference.new(@kid11.oid, @kid11.gen)
18
- @kid1 = @doc.add({Limits: ['c', 'm'], Kids: [ref, @kid12]}, type: HexaPDF::NameTreeNode)
20
+ @kid1 = @doc.add({Limits: ['c', 'm'], Kids: [ref, @kid12]})
19
21
  @kid21 = @doc.add({Limits: ['o', 'q'], Names: ['o', 1, 'q', 1]}, type: HexaPDF::NameTreeNode)
20
22
  @kid221 = @doc.add({Limits: ['s', 'u'], Names: ['s', 1, 'u', 1]}, type: HexaPDF::NameTreeNode)
21
23
  @kid22 = @doc.add({Limits: ['s', 'u'], Kids: [@kid221]}, type: HexaPDF::NameTreeNode)
@@ -75,7 +77,7 @@ describe HexaPDF::Utils::SortedTreeNode do
75
77
  @root.add_entry('v', 1)
76
78
  assert_equal(['a', 'm'], @kid1[:Limits].value)
77
79
  assert_equal(['a', 'f'], @kid11[:Limits].value)
78
- assert_equal(['a', 1, 'c', 1, 'e', 1, 'f', 1], @kid11[:Names].value)
80
+ assert_equal(['a', 1, 'c', @item_ref, 'e', 1, 'f', 1], @kid11[:Names].value)
79
81
  assert_equal(['g', 'm'], @kid12[:Limits].value)
80
82
  assert_equal(['g', 1, 'i', 1, 'j', 1, 'm', 1], @kid12[:Names].value)
81
83
  assert_equal(['n', 'v'], @kid2[:Limits].value)
@@ -203,13 +205,12 @@ describe HexaPDF::Utils::SortedTreeNode do
203
205
  end
204
206
 
205
207
  it "checks that all kid objects are indirect objects" do
206
- @root[:Kids][0] = ref = HexaPDF::Reference.new(@kid1.oid, @kid1.gen)
207
208
  assert(@root.validate)
208
209
 
209
- @root[:Kids][0] = ref
210
+ @root[:Kids][0] = @kid1
210
211
  @kid1.oid = 0
211
212
  assert(@root.validate do |message, c|
212
- assert_match(/must be an indirect object/, message)
213
+ assert_match(/children.*must be indirect/i, message)
213
214
  assert(c)
214
215
  end)
215
216
  assert(@kid1.indirect?)
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.47.0
4
+ version: 1.0.1
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-09-07 00:00:00.000000000 Z
11
+ date: 2024-11-04 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
@@ -728,6 +729,7 @@ files:
728
729
  - test/hexapdf/layout/test_text_shaper.rb
729
730
  - test/hexapdf/layout/test_width_from_polygon.rb
730
731
  - test/hexapdf/task/test_dereference.rb
732
+ - test/hexapdf/task/test_merge_acro_form.rb
731
733
  - test/hexapdf/task/test_optimize.rb
732
734
  - test/hexapdf/task/test_pdfa.rb
733
735
  - test/hexapdf/test_composer.rb