hexapdf 0.47.0 → 1.0.1

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 (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