hexapdf 0.12.3 → 0.14.3

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 (103) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +132 -0
  3. data/examples/019-acro_form.rb +41 -4
  4. data/lib/hexapdf/cli/command.rb +4 -2
  5. data/lib/hexapdf/cli/image2pdf.rb +2 -1
  6. data/lib/hexapdf/cli/info.rb +51 -2
  7. data/lib/hexapdf/cli/inspect.rb +30 -8
  8. data/lib/hexapdf/cli/merge.rb +1 -1
  9. data/lib/hexapdf/cli/split.rb +74 -14
  10. data/lib/hexapdf/configuration.rb +15 -0
  11. data/lib/hexapdf/content/graphic_object/arc.rb +3 -3
  12. data/lib/hexapdf/dictionary.rb +12 -6
  13. data/lib/hexapdf/dictionary_fields.rb +2 -10
  14. data/lib/hexapdf/document.rb +41 -16
  15. data/lib/hexapdf/document/files.rb +0 -1
  16. data/lib/hexapdf/encryption/fast_arc4.rb +1 -1
  17. data/lib/hexapdf/encryption/security_handler.rb +1 -0
  18. data/lib/hexapdf/encryption/standard_security_handler.rb +1 -0
  19. data/lib/hexapdf/font/cmap.rb +1 -4
  20. data/lib/hexapdf/font/true_type/subsetter.rb +16 -3
  21. data/lib/hexapdf/font/true_type/table/head.rb +1 -0
  22. data/lib/hexapdf/font/true_type/table/os2.rb +2 -0
  23. data/lib/hexapdf/font/true_type/table/post.rb +15 -10
  24. data/lib/hexapdf/font_loader/from_configuration.rb +2 -2
  25. data/lib/hexapdf/font_loader/from_file.rb +18 -8
  26. data/lib/hexapdf/image_loader/png.rb +3 -2
  27. data/lib/hexapdf/importer.rb +3 -2
  28. data/lib/hexapdf/layout/line.rb +1 -1
  29. data/lib/hexapdf/layout/style.rb +23 -23
  30. data/lib/hexapdf/layout/text_layouter.rb +2 -2
  31. data/lib/hexapdf/layout/text_shaper.rb +3 -2
  32. data/lib/hexapdf/object.rb +52 -25
  33. data/lib/hexapdf/parser.rb +107 -7
  34. data/lib/hexapdf/pdf_array.rb +15 -5
  35. data/lib/hexapdf/revisions.rb +29 -21
  36. data/lib/hexapdf/serializer.rb +37 -10
  37. data/lib/hexapdf/task/optimize.rb +6 -4
  38. data/lib/hexapdf/tokenizer.rb +22 -0
  39. data/lib/hexapdf/type/acro_form/appearance_generator.rb +130 -27
  40. data/lib/hexapdf/type/acro_form/button_field.rb +5 -2
  41. data/lib/hexapdf/type/acro_form/choice_field.rb +68 -14
  42. data/lib/hexapdf/type/acro_form/field.rb +35 -5
  43. data/lib/hexapdf/type/acro_form/form.rb +139 -14
  44. data/lib/hexapdf/type/acro_form/text_field.rb +70 -4
  45. data/lib/hexapdf/type/actions/uri.rb +3 -2
  46. data/lib/hexapdf/type/annotations/widget.rb +3 -4
  47. data/lib/hexapdf/type/catalog.rb +2 -2
  48. data/lib/hexapdf/type/cid_font.rb +1 -1
  49. data/lib/hexapdf/type/file_specification.rb +1 -1
  50. data/lib/hexapdf/type/font.rb +1 -1
  51. data/lib/hexapdf/type/font_simple.rb +4 -2
  52. data/lib/hexapdf/type/font_true_type.rb +6 -2
  53. data/lib/hexapdf/type/font_type0.rb +4 -4
  54. data/lib/hexapdf/type/form.rb +6 -2
  55. data/lib/hexapdf/type/image.rb +2 -2
  56. data/lib/hexapdf/type/page.rb +21 -12
  57. data/lib/hexapdf/type/page_tree_node.rb +29 -5
  58. data/lib/hexapdf/type/resources.rb +5 -0
  59. data/lib/hexapdf/type/trailer.rb +2 -3
  60. data/lib/hexapdf/utils/object_hash.rb +0 -1
  61. data/lib/hexapdf/utils/sorted_tree_node.rb +18 -15
  62. data/lib/hexapdf/version.rb +1 -1
  63. data/test/hexapdf/common_tokenizer_tests.rb +2 -2
  64. data/test/hexapdf/content/graphic_object/test_arc.rb +4 -4
  65. data/test/hexapdf/content/test_canvas.rb +3 -3
  66. data/test/hexapdf/content/test_color_space.rb +1 -1
  67. data/test/hexapdf/encryption/test_aes.rb +4 -4
  68. data/test/hexapdf/encryption/test_standard_security_handler.rb +11 -11
  69. data/test/hexapdf/filter/test_ascii85_decode.rb +1 -1
  70. data/test/hexapdf/filter/test_ascii_hex_decode.rb +1 -1
  71. data/test/hexapdf/font/true_type/table/test_post.rb +1 -1
  72. data/test/hexapdf/font/true_type/test_subsetter.rb +10 -0
  73. data/test/hexapdf/font_loader/test_from_configuration.rb +7 -3
  74. data/test/hexapdf/font_loader/test_from_file.rb +7 -0
  75. data/test/hexapdf/layout/test_text_layouter.rb +12 -5
  76. data/test/hexapdf/test_configuration.rb +2 -2
  77. data/test/hexapdf/test_dictionary.rb +8 -1
  78. data/test/hexapdf/test_dictionary_fields.rb +9 -2
  79. data/test/hexapdf/test_document.rb +18 -10
  80. data/test/hexapdf/test_object.rb +71 -26
  81. data/test/hexapdf/test_parser.rb +205 -51
  82. data/test/hexapdf/test_pdf_array.rb +8 -1
  83. data/test/hexapdf/test_revisions.rb +35 -0
  84. data/test/hexapdf/test_serializer.rb +7 -0
  85. data/test/hexapdf/test_tokenizer.rb +28 -0
  86. data/test/hexapdf/test_writer.rb +2 -2
  87. data/test/hexapdf/type/acro_form/test_appearance_generator.rb +288 -35
  88. data/test/hexapdf/type/acro_form/test_button_field.rb +15 -0
  89. data/test/hexapdf/type/acro_form/test_choice_field.rb +92 -9
  90. data/test/hexapdf/type/acro_form/test_field.rb +39 -0
  91. data/test/hexapdf/type/acro_form/test_form.rb +87 -15
  92. data/test/hexapdf/type/acro_form/test_text_field.rb +77 -1
  93. data/test/hexapdf/type/test_font_simple.rb +2 -1
  94. data/test/hexapdf/type/test_font_true_type.rb +6 -0
  95. data/test/hexapdf/type/test_form.rb +8 -1
  96. data/test/hexapdf/type/test_page.rb +8 -1
  97. data/test/hexapdf/type/test_page_tree_node.rb +42 -0
  98. data/test/hexapdf/type/test_resources.rb +6 -0
  99. data/test/hexapdf/utils/test_bit_field.rb +2 -0
  100. data/test/hexapdf/utils/test_object_hash.rb +5 -0
  101. data/test/hexapdf/utils/test_sorted_tree_node.rb +10 -9
  102. data/test/test_helper.rb +2 -0
  103. metadata +6 -12
@@ -37,6 +37,6 @@
37
37
  module HexaPDF
38
38
 
39
39
  # The version of HexaPDF.
40
- VERSION = '0.12.3'
40
+ VERSION = '0.14.3'
41
41
 
42
42
  end
@@ -122,7 +122,7 @@ module CommonTokenizerTests
122
122
  end
123
123
 
124
124
  it "next_token: should not fail when reading super long numbers" do
125
- create_tokenizer("1" + "0" * 10_000)
125
+ create_tokenizer("1" << "0" * 10_000)
126
126
  assert_equal(10**10_000, @tokenizer.next_token)
127
127
  end
128
128
 
@@ -162,7 +162,7 @@ module CommonTokenizerTests
162
162
  end
163
163
 
164
164
  it "returns the correct position on operations" do
165
- create_tokenizer("hallo du" + " " * 50000 + "hallo du")
165
+ create_tokenizer("hallo du" << " " * 50000 << "hallo du")
166
166
  @tokenizer.next_token
167
167
  assert_equal(5, @tokenizer.pos)
168
168
 
@@ -68,14 +68,14 @@ describe HexaPDF::Content::GraphicObject::Arc do
68
68
  arc.max_curves = 4
69
69
  curves = arc.curves
70
70
  assert_equal(2, curves.size)
71
- assert_curve_values([0, 1, p1: [1, 0.548584], p2: [0.548584, 1]], curves[0])
72
- assert_curve_values([-1, 0, p1: [-0.548584, 1], p2: [-1, 0.548584]], curves[1])
71
+ assert_curve_values([0, 1, {p1: [1, 0.548584], p2: [0.548584, 1]}], curves[0])
72
+ assert_curve_values([-1, 0, {p1: [-0.548584, 1], p2: [-1, 0.548584]}], curves[1])
73
73
 
74
74
  arc.configure(clockwise: true)
75
75
  curves = arc.curves
76
76
  assert_equal(2, curves.size)
77
- assert_curve_values([0, -1, p1: [1, -0.548584], p2: [0.548584, -1]], curves[0])
78
- assert_curve_values([-1, 0, p1: [-0.548584, -1], p2: [-1, -0.548584]], curves[1])
77
+ assert_curve_values([0, -1, {p1: [1, -0.548584], p2: [0.548584, -1]}], curves[0])
78
+ assert_curve_values([-1, 0, {p1: [-0.548584, -1], p2: [-1, -0.548584]}], curves[1])
79
79
  end
80
80
  end
81
81
 
@@ -531,7 +531,7 @@ describe HexaPDF::Content::Canvas do
531
531
  end
532
532
 
533
533
  it "invokes the polygon method when radius != 0" do
534
- args = [0, 0, 10, 0, 10, 10, 0, 10, radius: 5]
534
+ args = [0, 0, 10, 0, 10, 10, 0, 10, {radius: 5}]
535
535
  assert_method_invoked(@canvas, :polygon, args) do
536
536
  @canvas.rectangle(0, 0, 10, 10, radius: 5)
537
537
  end
@@ -631,7 +631,7 @@ describe HexaPDF::Content::Canvas do
631
631
 
632
632
  describe "circle" do
633
633
  it "uses arc for the hard work" do
634
- assert_method_invoked(@canvas, :arc, [5, 6, a: 7]) do
634
+ assert_method_invoked(@canvas, :arc, [5, 6, {a: 7}]) do
635
635
  @canvas.graphics_object = :path
636
636
  @canvas.circle(5, 6, 7)
637
637
  end
@@ -651,7 +651,7 @@ describe HexaPDF::Content::Canvas do
651
651
 
652
652
  describe "ellipse" do
653
653
  it "uses arc for the hard work" do
654
- assert_method_invoked(@canvas, :ellipse, [5, 6, a: 7, b: 5, inclination: 10]) do
654
+ assert_method_invoked(@canvas, :ellipse, [5, 6, {a: 7, b: 5, inclination: 10}]) do
655
655
  @canvas.ellipse(5, 6, a: 7, b: 5, inclination: 10)
656
656
  end
657
657
  end
@@ -155,7 +155,7 @@ describe HexaPDF::Content::ColorSpace::DeviceGray do
155
155
 
156
156
  before do
157
157
  @color_space = HexaPDF::Content::ColorSpace::DeviceGray.new
158
- @color_space_family = @color_space_definition = :DeviceGray
158
+ @color_space_family = @color_space_definition = :DeviceGray
159
159
  @color = @color_space.default_color
160
160
  @other_color = @color_space.color(128)
161
161
  @colors = [128]
@@ -101,13 +101,13 @@ describe HexaPDF::Encryption::AES do
101
101
  result = TestHelper.collector(@algorithm_class.decryption_fiber('some' * 4, f))
102
102
  assert_equal('a' * 16, result)
103
103
 
104
- f = Fiber.new { 'a' * 31 + "\x00" }
104
+ f = Fiber.new { 'a' * 31 << "\x00" }
105
105
  result = TestHelper.collector(@algorithm_class.decryption_fiber('some' * 4, f))
106
- assert_equal('a' * 15 + "\x00", result)
106
+ assert_equal('a' * 15 << "\x00", result)
107
107
 
108
- f = Fiber.new { 'a' * 29 + "\x00\x01\x03" }
108
+ f = Fiber.new { 'a' * 29 << "\x00\x01\x03" }
109
109
  result = TestHelper.collector(@algorithm_class.decryption_fiber('some' * 4, f))
110
- assert_equal('a' * 13 + "\x00\x01\x03", result)
110
+ assert_equal('a' * 13 << "\x00\x01\x03", result)
111
111
  end
112
112
 
113
113
  it "fails on decryption if not enough bytes are provided" do
@@ -53,24 +53,24 @@ describe HexaPDF::Encryption::StandardEncryptionDictionary do
53
53
  end
54
54
 
55
55
  describe HexaPDF::Encryption::StandardSecurityHandler do
56
- TEST_FILES = Dir[File.join(TEST_DATA_DIR, 'standard-security-handler', '*.pdf')].sort
57
- USER_PASSWORD = 'uhexapdf'
58
- OWNER_PASSWORD = 'ohexapdf'
56
+ test_files = Dir[File.join(TEST_DATA_DIR, 'standard-security-handler', '*.pdf')].sort
57
+ user_password = 'uhexapdf'
58
+ owner_password = 'ohexapdf'
59
59
 
60
- MINIMAL_DOC = HexaPDF::Document.new(io: StringIO.new(MINIMAL_PDF))
60
+ minimal_doc = HexaPDF::Document.new(io: StringIO.new(MINIMAL_PDF))
61
61
 
62
- TEST_FILES.each do |file|
62
+ test_files.each do |file|
63
63
  basename = File.basename(file)
64
64
  it "can decrypt, encrypt and decrypt the encrypted file #{basename} with the user password" do
65
65
  begin
66
66
  doc = HexaPDF::Document.new(io: StringIO.new(File.binread(file)),
67
- decryption_opts: {password: USER_PASSWORD})
68
- assert_equal(MINIMAL_DOC.trailer[:Info][:ModDate], doc.trailer[:Info][:ModDate])
67
+ decryption_opts: {password: user_password})
68
+ assert_equal(minimal_doc.trailer[:Info][:ModDate], doc.trailer[:Info][:ModDate])
69
69
 
70
70
  out = StringIO.new(''.b)
71
71
  HexaPDF::Writer.new(doc, out).write
72
- doc = HexaPDF::Document.new(io: out, decryption_opts: {password: USER_PASSWORD})
73
- assert_equal(MINIMAL_DOC.trailer[:Info][:ModDate], doc.trailer[:Info][:ModDate])
72
+ doc = HexaPDF::Document.new(io: out, decryption_opts: {password: user_password})
73
+ assert_equal(minimal_doc.trailer[:Info][:ModDate], doc.trailer[:Info][:ModDate])
74
74
  rescue HexaPDF::EncryptionError => e
75
75
  flunk("Error processing #{basename}: #{e}")
76
76
  end
@@ -80,8 +80,8 @@ describe HexaPDF::Encryption::StandardSecurityHandler do
80
80
  it "can decrypt the encrypted file #{basename} with the owner password" do
81
81
  begin
82
82
  doc = HexaPDF::Document.new(io: StringIO.new(File.binread(file)),
83
- decryption_opts: {password: OWNER_PASSWORD})
84
- assert_equal(MINIMAL_DOC.trailer[:Info][:ModDate], doc.trailer[:Info][:ModDate])
83
+ decryption_opts: {password: owner_password})
84
+ assert_equal(minimal_doc.trailer[:Info][:ModDate], doc.trailer[:Info][:ModDate])
85
85
  rescue HexaPDF::EncryptionError => e
86
86
  flunk("Error processing #{basename}: #{e}")
87
87
  end
@@ -33,7 +33,7 @@ describe HexaPDF::Filter::ASCII85Decode do
33
33
  end
34
34
 
35
35
  it "ignores data after the EOD marker" do
36
- assert_equal(@decoded, collector(@obj.decoder(feeder(@encoded.dup + "~>abcdefg"))))
36
+ assert_equal(@decoded, collector(@obj.decoder(feeder(@encoded << "~>abcdefg"))))
37
37
  end
38
38
 
39
39
  it "fails if the input contains invalid characters" do
@@ -24,7 +24,7 @@ describe HexaPDF::Filter::ASCIIHexDecode do
24
24
  end
25
25
 
26
26
  it "ignores data after the EOD marker" do
27
- assert_equal(@decoded, collector(@obj.decoder(feeder(@encoded + '4e6f7gzz'))))
27
+ assert_equal(@decoded, collector(@obj.decoder(feeder(@encoded << '4e6f7gzz'))))
28
28
  end
29
29
 
30
30
  it "assumes the missing char is '0' if the input length is odd" do
@@ -72,7 +72,7 @@ describe HexaPDF::Font::TrueType::Table::Post do
72
72
  assert_equal('.notdef', table[0])
73
73
 
74
74
  @font.config['font.true_type.unknown_format'] = :raise
75
- assert_raises(HexaPDF::Error) { create_table(:Post) }
75
+ assert_raises(HexaPDF::Error) { create_table(:Post)[0] }
76
76
  end
77
77
  end
78
78
  end
@@ -27,6 +27,16 @@ describe HexaPDF::Font::TrueType::Subsetter do
27
27
  assert_equal(value, @subsetter.subset_glyph_id(5))
28
28
  end
29
29
 
30
+ it "doesn't use certain subset glyph IDs for performance reasons" do
31
+ 1.upto(93) {|i| @subsetter.use_glyph(i) }
32
+ # glyph 0, 93 used glyph, 4 special glyphs
33
+ assert_equal(1 + 93 + 4, @subsetter.instance_variable_get(:@glyph_map).size)
34
+ 1.upto(12) {|i| assert_equal(i, @subsetter.subset_glyph_id(i), "id=#{i}") }
35
+ 13.upto(38) {|i| assert_equal(i + 1, @subsetter.subset_glyph_id(i), "id=#{i}") }
36
+ 39.upto(88) {|i| assert_equal(i + 3, @subsetter.subset_glyph_id(i), "id=#{i}") }
37
+ 89.upto(93) {|i| assert_equal(i + 4, @subsetter.subset_glyph_id(i), "id=#{i}") }
38
+ end
39
+
30
40
  it "creates the subset font file" do
31
41
  gid = @font[:cmap].preferred_table[0x41]
32
42
  @subsetter.use_glyph(gid)
@@ -8,13 +8,17 @@ describe HexaPDF::FontLoader::FromConfiguration do
8
8
  before do
9
9
  @doc = HexaPDF::Document.new
10
10
  font_file = File.join(TEST_DATA_DIR, "fonts", "Ubuntu-Title.ttf")
11
- @doc.config['font.map'] = {'font' => {none: font_file}}
11
+ @font_obj = HexaPDF::Font::TrueType::Font.new(File.open(font_file, 'rb'))
12
+ @doc.config['font.map'] = {'font' => {none: font_file}, 'font1' => {none: @font_obj}}
12
13
  @klass = HexaPDF::FontLoader::FromConfiguration
13
14
  end
14
15
 
15
16
  it "loads the configured font" do
16
17
  wrapper = @klass.call(@doc, "font")
17
18
  assert_equal("Ubuntu-Title", wrapper.wrapped_font.font_name)
19
+ wrapper = @klass.call(@doc, "font1")
20
+ assert_equal("Ubuntu-Title", wrapper.wrapped_font.font_name)
21
+ assert_same(@font_obj, wrapper.wrapped_font)
18
22
  end
19
23
 
20
24
  it "passes the subset value to the wrapper" do
@@ -24,7 +28,7 @@ describe HexaPDF::FontLoader::FromConfiguration do
24
28
  refute(wrapper.subset?)
25
29
  end
26
30
 
27
- it "fails if the font file cannot be read" do
31
+ it "fails if the provided font is invalid" do
28
32
  @doc.config['font.map']['font'][:none] << "unknown"
29
33
  assert_raises(HexaPDF::Error) { @klass.call(@doc, "font") }
30
34
  end
@@ -34,6 +38,6 @@ describe HexaPDF::FontLoader::FromConfiguration do
34
38
  end
35
39
 
36
40
  it "returns a hash with all configured fonts" do
37
- assert_equal({'font' => [:none]}, @klass.available_fonts(@doc))
41
+ assert_equal({'font' => [:none], 'font1' => [:none]}, @klass.available_fonts(@doc))
38
42
  end
39
43
  end
@@ -16,6 +16,13 @@ describe HexaPDF::FontLoader::FromFile do
16
16
  assert_equal("Ubuntu-Title", wrapper.wrapped_font.font_name)
17
17
  end
18
18
 
19
+ it "loads the specified font object" do
20
+ font = HexaPDF::Font::TrueType::Font.new(File.open(@font_file, 'rb'))
21
+ wrapper = @klass.call(@doc, font)
22
+ assert_equal("Ubuntu-Title", wrapper.wrapped_font.font_name)
23
+ assert_same(font, wrapper.wrapped_font)
24
+ end
25
+
19
26
  it "passes the subset value to the wrapper" do
20
27
  wrapper = @klass.call(@doc, @font_file)
21
28
  assert(wrapper.subset?)
@@ -591,25 +591,33 @@ describe HexaPDF::Layout::TextLayouter do
591
591
 
592
592
  describe "horizontal alignment" do
593
593
  before do
594
- @items = boxes(*[[20, 20]] * 4)
594
+ @items = boxes(*[[20, 20]] * 4) + [glue(10), penalty(-5000, boxes(0).first.item)]
595
595
  end
596
596
 
597
597
  it "aligns the contents to the left" do
598
598
  @style.align = :left
599
599
  result = @layouter.fit(@items, 100, 100)
600
600
  assert_equal(0, result.lines[0].x_offset)
601
+ assert_equal(80, result.lines[0].width)
602
+ result = @layouter.fit(@items, proc { 100 }, 100)
603
+ assert_equal(0, result.lines[0].x_offset)
604
+ assert_equal(80, result.lines[0].width)
601
605
  end
602
606
 
603
607
  it "aligns the contents to the center" do
604
608
  @style.align = :center
605
609
  result = @layouter.fit(@items, 100, 100)
606
610
  assert_equal(10, result.lines[0].x_offset)
611
+ result = @layouter.fit(@items, proc { 100 }, 100)
612
+ assert_equal(10, result.lines[0].x_offset)
607
613
  end
608
614
 
609
615
  it "aligns the contents to the right" do
610
616
  @style.align = :right
611
617
  result = @layouter.fit(@items, 100, 100)
612
618
  assert_equal(20, result.lines[0].x_offset)
619
+ result = @layouter.fit(@items, proc { 100 }, 100)
620
+ assert_equal(20, result.lines[0].x_offset)
613
621
  end
614
622
  end
615
623
 
@@ -674,10 +682,9 @@ describe HexaPDF::Layout::TextLayouter do
674
682
  pos = [0, 0]
675
683
  result.select! {|name, _| name == :set_text_matrix || name == :move_text_next_line }.
676
684
  map! do |name, ops|
677
- if name == :set_text_matrix
678
- pos = ops[-2, 2]
679
- elsif name == :move_text_next_line
680
- pos[1] -= leading
685
+ case name
686
+ when :set_text_matrix then pos = ops[-2, 2]
687
+ when :move_text_next_line then pos[1] -= leading
681
688
  end
682
689
  pos.dup
683
690
  end
@@ -66,8 +66,8 @@ describe HexaPDF::Configuration do
66
66
  assert_equal(HexaPDF, @config.constantize('test', 1))
67
67
  end
68
68
 
69
- def assert_constantize_error # :nodoc:
70
- exp = assert_raises(HexaPDF::Error) { yield }
69
+ def assert_constantize_error(&block) # :nodoc:
70
+ exp = assert_raises(HexaPDF::Error, &block)
71
71
  assert_match(/Error getting constant for configuration option/, exp.message)
72
72
  end
73
73
 
@@ -14,7 +14,9 @@ describe HexaPDF::Dictionary do
14
14
  end
15
15
 
16
16
  def add(obj)
17
- HexaPDF::Object.new(obj, oid: 1)
17
+ klass = HexaPDF::Object
18
+ klass = HexaPDF::Dictionary if obj.kind_of?(HexaPDF::Dictionary) || obj.kind_of?(Hash)
19
+ klass.new(obj, oid: 1)
18
20
  end
19
21
 
20
22
  def delete(_obj)
@@ -281,6 +283,11 @@ describe HexaPDF::Dictionary do
281
283
  @obj[:TestClass][:Nested][:Nested][:TestClass][:Inherited] = :symbol
282
284
  assert(@obj.validate)
283
285
  end
286
+
287
+ it "makes sure validation works in special case where the dictionary is modified" do
288
+ @dict[:Array] = 5
289
+ refute(@dict.validate {|_, _, object| object[:Boolean] })
290
+ end
284
291
  end
285
292
 
286
293
  describe "delete" do
@@ -222,7 +222,7 @@ describe HexaPDF::DictionaryFields do
222
222
 
223
223
  it "allows conversion to a Rectangle from an Array" do
224
224
  doc = Minitest::Mock.new
225
- doc.expect(:wrap, :data, [[0, 1, 2, 3], type: HexaPDF::Rectangle])
225
+ doc.expect(:wrap, :data, [[0, 1, 2, 3], {type: HexaPDF::Rectangle}])
226
226
  @field.convert([0, 1, 2, 3], doc)
227
227
  doc.verify
228
228
  end
@@ -230,9 +230,16 @@ describe HexaPDF::DictionaryFields do
230
230
  it "allows conversion to a Rectangle from a HexaPDF::PDFArray" do
231
231
  data = HexaPDF::PDFArray.new([0, 1, 2, 3])
232
232
  doc = Minitest::Mock.new
233
- doc.expect(:wrap, :data, [data, type: HexaPDF::Rectangle])
233
+ doc.expect(:wrap, :data, [data, {type: HexaPDF::Rectangle}])
234
234
  @field.convert(data, doc)
235
235
  doc.verify
236
236
  end
237
+
238
+ it "converts to a null value if an (invalid) empty array is given" do
239
+ doc = Minitest::Mock.new
240
+ doc.expect(:wrap, :data, [nil])
241
+ @field.convert([], doc)
242
+ doc.verify
243
+ end
237
244
  end
238
245
  end
@@ -441,21 +441,21 @@ describe HexaPDF::Document do
441
441
 
442
442
  describe "validate" do
443
443
  before do
444
- @doc.trailer.validate # to create a valid document
444
+ @doc.validate # to create a valid document
445
445
  end
446
446
 
447
447
  it "validates indirect objects" do
448
- obj = @doc.add({Type: :Catalog})
448
+ obj = @doc.add({Type: :Page, MediaBox: [1, 1, 1, 1], Parent: @doc.pages.root})
449
449
  refute(@doc.validate(auto_correct: false))
450
450
 
451
451
  called = false
452
- assert(@doc.validate {|o| assert_same(obj, o); called = true })
452
+ assert(@doc.validate {|_, _, o| assert_same(obj, o); called = true })
453
453
  assert(called)
454
454
  end
455
455
 
456
456
  it "validates the trailer object" do
457
457
  @doc.trailer[:ID] = :Symbol
458
- refute(@doc.validate {|obj| assert_same(@doc.trailer, obj) })
458
+ refute(@doc.validate {|_, _, obj| assert_same(@doc.trailer, obj) })
459
459
  end
460
460
 
461
461
  it "validates only loaded objects" do
@@ -609,16 +609,24 @@ describe HexaPDF::Document do
609
609
 
610
610
  describe "caching interface" do
611
611
  it "allows setting and retrieving values" do
612
- assert_equal(:test, @doc.cache(:a, :b, :test))
613
- assert_equal(:test, @doc.cache(:a, :b, :other))
614
- assert_equal(:other, @doc.cache(:a, :c) { :other })
612
+ assert_equal(:test, @doc.cache(:a, :b, :test) { :notused })
613
+ assert_equal(:test, @doc.cache(:a, :b) { :other })
614
+ assert_equal(:test, @doc.cache(:a, :b))
615
+ assert_nil(@doc.cache(:a, :c, nil))
616
+ assert_nil(@doc.cache(:a, :c) { :other })
617
+ assert_nil(@doc.cache(:a, :c))
615
618
  assert(@doc.cached?(:a, :b))
616
619
  assert(@doc.cached?(:a, :c))
617
620
  end
618
621
 
622
+ it "allows updating a value" do
623
+ @doc.cache(:a, :b) { :test }
624
+ assert_equal(:new, @doc.cache(:a, :b, update: true) { :new })
625
+ end
626
+
619
627
  it "allows clearing cached values" do
620
- @doc.cache(:a, :b, :c)
621
- @doc.cache(:b, :c, :d)
628
+ @doc.cache(:a, :b) { :c }
629
+ @doc.cache(:b, :c) { :d }
622
630
  @doc.clear_cache(:a)
623
631
  refute(@doc.cached?(:a, :b))
624
632
  assert(@doc.cached?(:b, :c))
@@ -626,7 +634,7 @@ describe HexaPDF::Document do
626
634
  refute(@doc.cached?(:a, :c))
627
635
  end
628
636
 
629
- it "fails if no cached value exists and neither a value nor a block is given" do
637
+ it "fails if no cached value exists and no block is given" do
630
638
  assert_raises(LocalJumpError) { @doc.cache(:a, :b) }
631
639
  end
632
640
  end
@@ -3,18 +3,10 @@
3
3
  require 'test_helper'
4
4
  require 'hexapdf/object'
5
5
  require 'hexapdf/reference'
6
+ require 'hexapdf/document'
6
7
 
7
8
  describe HexaPDF::Object do
8
9
  describe "class.deep_copy" do
9
- it "handles not-duplicatable classes" do
10
- assert_equal(5, HexaPDF::Object.deep_copy(5))
11
- assert_equal(5.5, HexaPDF::Object.deep_copy(5.5))
12
- assert_nil(HexaPDF::Object.deep_copy(nil))
13
- assert_equal(true, HexaPDF::Object.deep_copy(true))
14
- assert_equal(false, HexaPDF::Object.deep_copy(false))
15
- assert_equal(:Name, HexaPDF::Object.deep_copy(:Name))
16
- end
17
-
18
10
  it "handles general, duplicatable classes" do
19
11
  x = "test"
20
12
  assert_equal("test", HexaPDF::Object.deep_copy(x))
@@ -103,30 +95,57 @@ describe HexaPDF::Object do
103
95
  end
104
96
 
105
97
  describe "validate" do
106
- it "invokes perform_validation correctly via #validate" do
107
- obj = HexaPDF::Object.new(5)
108
- invoked = {}
109
- obj.define_singleton_method(:perform_validation) do |&block|
110
- invoked[:method] = true
98
+ before do
99
+ @obj = HexaPDF::Object.new(5)
100
+ end
101
+
102
+ it "invokes perform_validation correctly" do
103
+ invoked = false
104
+ @obj.define_singleton_method(:perform_validation) { invoked = true }
105
+ assert(@obj.validate)
106
+ assert(invoked)
107
+ end
108
+
109
+ it "yields all arguments yieled by perform_validation" do
110
+ invoked = []
111
+ @obj.define_singleton_method(:perform_validation) do |&block|
112
+ block.call("error", true, :object)
113
+ end
114
+ assert(@obj.validate {|*a| invoked << a })
115
+ assert_equal([["error", true, :object]], invoked)
116
+ end
117
+
118
+ it "provides self as third argument if none is yielded by perform_validation" do
119
+ invoked = []
120
+ @obj.define_singleton_method(:perform_validation) do |&block|
111
121
  block.call("error", true)
112
122
  end
113
- assert(obj.validate {|*a| invoked[:block] = a })
114
- assert_equal([:method, :block], invoked.keys)
115
- assert_equal(["error", true], invoked[:block])
123
+ assert(@obj.validate {|*a| invoked << a })
124
+ assert_equal([["error", true, @obj]], invoked)
125
+ end
116
126
 
117
- refute(obj.validate(auto_correct: false))
127
+ it "yields all problems when auto_correct is true" do
128
+ invoked = []
129
+ @obj.define_singleton_method(:perform_validation) do |&block|
130
+ invoked << :before
131
+ block.call("error", false)
132
+ invoked << :after
133
+ block.call("error2", true)
134
+ invoked << :last
135
+ end
136
+ refute(@obj.validate)
137
+ assert_equal([:before, :after, :last], invoked)
118
138
  end
119
139
 
120
- it "stops validating on an uncorrectable problem" do
121
- obj = HexaPDF::Object.new(5)
122
- invoked = {}
123
- obj.define_singleton_method(:perform_validation) do |&block|
124
- invoked[:before] = true
140
+ it "stops at the first uncorrectable problem if auto_correct is false" do
141
+ invoked = []
142
+ @obj.define_singleton_method(:perform_validation) do |&block|
143
+ invoked << :before
125
144
  block.call("error", false)
126
- invoked[:after] = true
145
+ invoked << :after
127
146
  end
128
- refute(obj.validate {|*a| invoked[:block] = a })
129
- refute(invoked.key?(:after))
147
+ refute(@obj.validate(auto_correct: false))
148
+ assert_equal([:before], invoked)
130
149
  end
131
150
  end
132
151
 
@@ -181,6 +200,32 @@ describe HexaPDF::Object do
181
200
  end
182
201
  end
183
202
 
203
+ describe "caching" do
204
+ before do
205
+ @obj = HexaPDF::Object.new({}, document: HexaPDF::Document.new)
206
+ end
207
+
208
+ it "can set and return a cached value" do
209
+ assert_equal(:value, @obj.cache(:data, :value))
210
+ assert_equal(:value, @obj.cache(:data, :other))
211
+ assert_equal(:value, @obj.cache(:block) { :value })
212
+ assert_equal(:other, @obj.cache(:data, :other, update: true))
213
+ end
214
+
215
+ it "can check for the existence of a cached value" do
216
+ refute(@obj.cached?(:data))
217
+ @obj.cache(:data, :value)
218
+ assert(@obj.cached?(:data))
219
+ end
220
+
221
+ it "can clear all cached values" do
222
+ @obj.cache(:data, :value)
223
+ assert(@obj.cached?(:data))
224
+ @obj.clear_cache
225
+ refute(@obj.cached?(:data))
226
+ end
227
+ end
228
+
184
229
  describe "validation" do
185
230
  before do
186
231
  @doc = Object.new