hexapdf 0.28.0 → 0.31.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (68) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +86 -10
  3. data/examples/024-digital-signatures.rb +23 -0
  4. data/lib/hexapdf/cli/command.rb +16 -1
  5. data/lib/hexapdf/cli/info.rb +9 -1
  6. data/lib/hexapdf/cli/inspect.rb +2 -2
  7. data/lib/hexapdf/composer.rb +76 -28
  8. data/lib/hexapdf/configuration.rb +29 -16
  9. data/lib/hexapdf/dictionary_fields.rb +13 -4
  10. data/lib/hexapdf/digital_signature/cms_handler.rb +137 -0
  11. data/lib/hexapdf/digital_signature/handler.rb +138 -0
  12. data/lib/hexapdf/digital_signature/pkcs1_handler.rb +96 -0
  13. data/lib/hexapdf/{type → digital_signature}/signature.rb +3 -8
  14. data/lib/hexapdf/digital_signature/signatures.rb +210 -0
  15. data/lib/hexapdf/digital_signature/signing/default_handler.rb +317 -0
  16. data/lib/hexapdf/digital_signature/signing/signed_data_creator.rb +308 -0
  17. data/lib/hexapdf/digital_signature/signing/timestamp_handler.rb +148 -0
  18. data/lib/hexapdf/digital_signature/signing.rb +101 -0
  19. data/lib/hexapdf/{type/signature → digital_signature}/verification_result.rb +37 -41
  20. data/lib/hexapdf/digital_signature.rb +56 -0
  21. data/lib/hexapdf/document/pages.rb +31 -18
  22. data/lib/hexapdf/document.rb +29 -15
  23. data/lib/hexapdf/encryption/standard_security_handler.rb +4 -3
  24. data/lib/hexapdf/filter/flate_decode.rb +20 -8
  25. data/lib/hexapdf/layout/page_style.rb +144 -0
  26. data/lib/hexapdf/layout.rb +1 -0
  27. data/lib/hexapdf/task/optimize.rb +8 -6
  28. data/lib/hexapdf/type/font_simple.rb +14 -2
  29. data/lib/hexapdf/type/object_stream.rb +7 -2
  30. data/lib/hexapdf/type/outline.rb +1 -1
  31. data/lib/hexapdf/type/outline_item.rb +1 -1
  32. data/lib/hexapdf/type/page.rb +29 -8
  33. data/lib/hexapdf/type/xref_stream.rb +11 -4
  34. data/lib/hexapdf/type.rb +0 -1
  35. data/lib/hexapdf/version.rb +1 -1
  36. data/lib/hexapdf/writer.rb +1 -1
  37. data/test/hexapdf/{type/signature → digital_signature}/common.rb +31 -3
  38. data/test/hexapdf/digital_signature/signing/test_default_handler.rb +162 -0
  39. data/test/hexapdf/digital_signature/signing/test_signed_data_creator.rb +225 -0
  40. data/test/hexapdf/digital_signature/signing/test_timestamp_handler.rb +88 -0
  41. data/test/hexapdf/{type/signature/test_adbe_pkcs7_detached.rb → digital_signature/test_cms_handler.rb} +7 -7
  42. data/test/hexapdf/{type/signature → digital_signature}/test_handler.rb +4 -4
  43. data/test/hexapdf/{type/signature/test_adbe_x509_rsa_sha1.rb → digital_signature/test_pkcs1_handler.rb} +3 -3
  44. data/test/hexapdf/{type → digital_signature}/test_signature.rb +7 -7
  45. data/test/hexapdf/digital_signature/test_signatures.rb +137 -0
  46. data/test/hexapdf/digital_signature/test_signing.rb +53 -0
  47. data/test/hexapdf/{type/signature → digital_signature}/test_verification_result.rb +7 -7
  48. data/test/hexapdf/document/test_pages.rb +25 -0
  49. data/test/hexapdf/encryption/test_standard_security_handler.rb +2 -2
  50. data/test/hexapdf/filter/test_flate_decode.rb +19 -5
  51. data/test/hexapdf/layout/test_page_style.rb +70 -0
  52. data/test/hexapdf/task/test_optimize.rb +11 -9
  53. data/test/hexapdf/test_composer.rb +35 -10
  54. data/test/hexapdf/test_dictionary_fields.rb +9 -3
  55. data/test/hexapdf/test_document.rb +1 -1
  56. data/test/hexapdf/test_writer.rb +8 -8
  57. data/test/hexapdf/type/test_font_simple.rb +18 -6
  58. data/test/hexapdf/type/test_object_stream.rb +16 -7
  59. data/test/hexapdf/type/test_outline.rb +3 -1
  60. data/test/hexapdf/type/test_outline_item.rb +3 -1
  61. data/test/hexapdf/type/test_page.rb +42 -11
  62. data/test/hexapdf/type/test_xref_stream.rb +6 -1
  63. metadata +27 -15
  64. data/lib/hexapdf/document/signatures.rb +0 -546
  65. data/lib/hexapdf/type/signature/adbe_pkcs7_detached.rb +0 -135
  66. data/lib/hexapdf/type/signature/adbe_x509_rsa_sha1.rb +0 -95
  67. data/lib/hexapdf/type/signature/handler.rb +0 -140
  68. data/test/hexapdf/document/test_signatures.rb +0 -352
@@ -1,26 +1,26 @@
1
1
  # -*- encoding: utf-8 -*-
2
2
 
3
3
  require 'test_helper'
4
- require 'hexapdf/type/signature'
4
+ require 'hexapdf/digital_signature'
5
5
 
6
- describe HexaPDF::Type::Signature::VerificationResult do
6
+ describe HexaPDF::DigitalSignature::VerificationResult do
7
7
  describe "Message" do
8
8
  it "accepts a type and a content argument on creation" do
9
- m = HexaPDF::Type::Signature::VerificationResult::Message.new(:type, 'content')
9
+ m = HexaPDF::DigitalSignature::VerificationResult::Message.new(:type, 'content')
10
10
  assert_equal(:type, m.type)
11
11
  assert_equal('content', m.content)
12
12
  end
13
13
 
14
14
  it "allows sorting by type" do
15
- info = HexaPDF::Type::Signature::VerificationResult::Message.new(:info, 'c')
16
- warning = HexaPDF::Type::Signature::VerificationResult::Message.new(:warning, 'c')
17
- error = HexaPDF::Type::Signature::VerificationResult::Message.new(:error, 'c')
15
+ info = HexaPDF::DigitalSignature::VerificationResult::Message.new(:info, 'c')
16
+ warning = HexaPDF::DigitalSignature::VerificationResult::Message.new(:warning, 'c')
17
+ error = HexaPDF::DigitalSignature::VerificationResult::Message.new(:error, 'c')
18
18
  assert_equal([error, warning, info], [info, error, warning].sort)
19
19
  end
20
20
  end
21
21
 
22
22
  before do
23
- @result = HexaPDF::Type::Signature::VerificationResult.new
23
+ @result = HexaPDF::DigitalSignature::VerificationResult.new
24
24
  end
25
25
 
26
26
  it "can add new messages" do
@@ -14,6 +14,31 @@ describe HexaPDF::Document::Pages do
14
14
  end
15
15
  end
16
16
 
17
+ describe "create" do
18
+ it "uses the defaults from the configuration for missing arguments" do
19
+ page = @doc.pages.create
20
+ assert_equal([0, 0, 595, 842], page.box(:media).value)
21
+ end
22
+
23
+ it "allows specifying a reference to a predefined page size" do
24
+ page = @doc.pages.create(media_box: :A3)
25
+ assert_equal([0, 0, 842, 1191], page.box(:media).value)
26
+ end
27
+
28
+ it "allows specifying the orientation for a predefined page size" do
29
+ page = @doc.pages.create(media_box: :A4, orientation: :landscape)
30
+ assert_equal([0, 0, 842, 595], page.box(:media).value)
31
+
32
+ page = @doc.pages.create(orientation: :landscape)
33
+ assert_equal([0, 0, 842, 595], page.box(:media).value)
34
+ end
35
+
36
+ it "allows using a media box array" do
37
+ page = @doc.pages.create(media_box: [0, 0, 12, 24], orientation: :landscape)
38
+ assert_equal([0, 0, 12, 24], page.box(:media).value)
39
+ end
40
+ end
41
+
17
42
  describe "add" do
18
43
  it "adds a new empty page when no page is given" do
19
44
  page = @doc.pages.add
@@ -213,14 +213,14 @@ describe HexaPDF::Encryption::StandardSecurityHandler do
213
213
  exp = assert_raises(HexaPDF::UnsupportedEncryptionError) do
214
214
  @handler.set_up_decryption({Filter: :NonStandard, V: 2})
215
215
  end
216
- assert_match(/Invalid \/Filter/i, exp.message)
216
+ assert_match(/Invalid \/Filter value NonStandard/i, exp.message)
217
217
  end
218
218
 
219
219
  it "fails if the /R value is incorrect" do
220
220
  exp = assert_raises(HexaPDF::UnsupportedEncryptionError) do
221
221
  @handler.set_up_decryption({Filter: :Standard, V: 2, R: 5})
222
222
  end
223
- assert_match(/Invalid \/R/i, exp.message)
223
+ assert_match(/Invalid \/R value 5/i, exp.message)
224
224
  end
225
225
 
226
226
  it "fails if the supplied password is invalid" do
@@ -26,12 +26,26 @@ describe HexaPDF::Filter::FlateDecode do
26
26
  assert_equal(@decoded, collector(@obj.decoder(feeder(@encoded_predictor), @predictor_opts)))
27
27
  end
28
28
 
29
- it "fails on invalid input" do
30
- assert_raises(HexaPDF::FilterError) do
31
- collector(@obj.decoder(feeder(@encoded[0..-2], @encoded.length - 3)))
29
+ describe "invalid input is handled as good as possible" do
30
+ def strict_mode
31
+ HexaPDF::GlobalConfiguration['filter.flate.on_error'] = proc { true }
32
+ yield
33
+ ensure
34
+ HexaPDF::GlobalConfiguration['filter.flate.on_error'] = proc { false }
32
35
  end
33
- assert_raises(HexaPDF::FilterError) do
34
- collector(@obj.decoder(feeder("some data")))
36
+
37
+ it "handles completely invalid data" do
38
+ assert_equal('', collector(@obj.decoder(feeder("some data"))))
39
+ assert_raises(HexaPDF::FilterError) do
40
+ strict_mode { collector(@obj.decoder(feeder("some data"))) }
41
+ end
42
+ end
43
+
44
+ it "handles missing data" do
45
+ assert_equal('abcdefg', collector(@obj.decoder(feeder(@encoded[0..-2]))))
46
+ assert_raises(HexaPDF::FilterError) do
47
+ strict_mode { collector(@obj.decoder(feeder(@encoded[0..-2]))) }
48
+ end
35
49
  end
36
50
  end
37
51
  end
@@ -0,0 +1,70 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ require 'test_helper'
4
+ require 'hexapdf/layout/page_style'
5
+ require 'hexapdf/document'
6
+
7
+ describe HexaPDF::Layout::PageStyle do
8
+ it "allows assigning the page size, orientation and template on initialization" do
9
+ block = lambda {}
10
+ style = HexaPDF::Layout::PageStyle.new(page_size: :A3, orientation: :landscape, &block)
11
+ assert_equal(:A3, style.page_size)
12
+ assert_equal(:landscape, style.orientation)
13
+ assert_same(block, style.template)
14
+ end
15
+
16
+ it "uses defaults for all values" do
17
+ style = HexaPDF::Layout::PageStyle.new
18
+ assert_equal(:A4, style.page_size)
19
+ assert_equal(:portrait, style.orientation)
20
+ assert_nil(style.template)
21
+ assert_nil(style.frame)
22
+ assert_nil(style.next_style)
23
+ end
24
+
25
+ describe "create_page" do
26
+ before do
27
+ @doc = HexaPDF::Document.new
28
+ end
29
+
30
+ it "creates a new page object" do
31
+ style = HexaPDF::Layout::PageStyle.new do |canvas, istyle|
32
+ canvas.rectangle(0, 0, 10, 10).stroke
33
+ istyle.frame = :frame
34
+ istyle.next_style = :other
35
+ end
36
+ page = style.create_page(@doc)
37
+ assert_equal([0, 0, 595, 842], page.box(:media))
38
+ assert_equal("0 0 10 10 re\nS\n", page.contents)
39
+ assert_equal(:frame, style.frame)
40
+ assert_equal(:other, style.next_style)
41
+ assert_equal(0, @doc.pages.count)
42
+ end
43
+
44
+ it "works when no template is set" do
45
+ style = HexaPDF::Layout::PageStyle.new
46
+ page = style.create_page(@doc)
47
+ assert_equal("", page.contents)
48
+ end
49
+
50
+ it "creates a default frame if none is set beforehand or during template execution" do
51
+ style = HexaPDF::Layout::PageStyle.new
52
+ style.create_page(@doc)
53
+ assert_kind_of(HexaPDF::Layout::Frame, style.frame)
54
+ assert_equal(36, style.frame.left)
55
+ assert_equal(36, style.frame.bottom)
56
+ assert_equal(523, style.frame.width)
57
+ assert_equal(770, style.frame.height)
58
+ end
59
+ end
60
+
61
+ it "creates new frame objects given a page and a margin specification" do
62
+ doc = HexaPDF::Document.new
63
+ style = HexaPDF::Layout::PageStyle.new
64
+ frame = style.create_frame(style.create_page(doc), [15, 10])
65
+ assert_equal(10, frame.left)
66
+ assert_equal(15, frame.bottom)
67
+ assert_equal(575, frame.width)
68
+ assert_equal(812, frame.height)
69
+ end
70
+ end
@@ -81,8 +81,10 @@ describe HexaPDF::Task::Optimize do
81
81
  end
82
82
 
83
83
  it "compacts and deletes xref streams" do
84
- @doc.revisions.all[0].add(@doc.wrap({Type: :XRef}, oid: @doc.revisions.next_oid))
85
- @doc.revisions.all[1].add(@doc.wrap({Type: :XRef}, oid: @doc.revisions.next_oid))
84
+ @doc.revisions.all[0].add(@doc.wrap({}, type: HexaPDF::Type::XRefStream,
85
+ oid: @doc.revisions.next_oid))
86
+ @doc.revisions.all[1].add(@doc.wrap({}, type: HexaPDF::Type::XRefStream,
87
+ oid: @doc.revisions.next_oid))
86
88
  @doc.task(:optimize, compact: true, xref_streams: :delete)
87
89
  assert_no_xrefstms
88
90
  assert_default_deleted
@@ -92,8 +94,8 @@ describe HexaPDF::Task::Optimize do
92
94
  describe "object_streams" do
93
95
  def reload_document_with_objstm_from_io
94
96
  io = StringIO.new
95
- objstm = @doc.add({Type: :ObjStm})
96
- @doc.add({Type: :XRef})
97
+ objstm = @doc.add({}, type: HexaPDF::Type::ObjectStream)
98
+ @doc.add({}, type: HexaPDF::Type::XRefStream)
97
99
  objstm.add_object(@doc.add({Type: :Test}))
98
100
  @doc.write(io)
99
101
  io.rewind
@@ -102,7 +104,7 @@ describe HexaPDF::Task::Optimize do
102
104
 
103
105
  it "generates object streams" do
104
106
  210.times { @doc.add(5) }
105
- objstm = @doc.add({Type: :ObjStm})
107
+ objstm = @doc.add({}, type: HexaPDF::Type::ObjectStream)
106
108
  reload_document_with_objstm_from_io
107
109
  @doc.task(:optimize, object_streams: :generate)
108
110
  assert_objstms_generated
@@ -122,8 +124,8 @@ describe HexaPDF::Task::Optimize do
122
124
  end
123
125
 
124
126
  it "deletes object and generates xref streams" do
125
- @doc.add({Type: :ObjStm})
126
- xref = @doc.add({Type: :XRef})
127
+ @doc.add({}, type: HexaPDF::Type::ObjectStream)
128
+ xref = @doc.add({}, type: HexaPDF::Type::XRefStream)
127
129
  @doc.task(:optimize, object_streams: :delete, xref_streams: :generate)
128
130
  assert_no_objstms
129
131
  assert_xrefstms_generated
@@ -140,13 +142,13 @@ describe HexaPDF::Task::Optimize do
140
142
  end
141
143
 
142
144
  it "reuses an xref stream in generatation mode" do
143
- @doc.add({Type: :XRef})
145
+ @doc.add({}, type: HexaPDF::Type::XRefStream)
144
146
  @doc.task(:optimize, xref_streams: :generate)
145
147
  assert_xrefstms_generated
146
148
  end
147
149
 
148
150
  it "deletes xref streams" do
149
- @doc.add({Type: :XRef})
151
+ @doc.add({}, type: HexaPDF::Type::XRefStream)
150
152
  @doc.task(:optimize, xref_streams: :delete)
151
153
  assert_no_xrefstms
152
154
  assert_default_deleted
@@ -39,6 +39,14 @@ describe HexaPDF::Composer do
39
39
  assert_equal(682, composer.frame.height)
40
40
  end
41
41
 
42
+ it "allows skipping the initial page creation" do
43
+ composer = HexaPDF::Composer.new(skip_page_creation: true)
44
+ assert_nil(composer.page)
45
+ assert_nil(composer.canvas)
46
+ assert_nil(composer.frame)
47
+ assert_nil(composer.page_style(:default))
48
+ end
49
+
42
50
  it "yields itself" do
43
51
  yielded = nil
44
52
  composer = HexaPDF::Composer.new {|c| yielded = c }
@@ -56,7 +64,7 @@ describe HexaPDF::Composer do
56
64
  end
57
65
 
58
66
  describe "new_page" do
59
- it "creates a new page with the stored information" do
67
+ it "creates a new page" do
60
68
  c = HexaPDF::Composer.new(page_size: [0, 0, 50, 100], margin: 10)
61
69
  c.new_page
62
70
  assert_equal([0, 0, 50, 100], c.page.box.value)
@@ -64,16 +72,33 @@ describe HexaPDF::Composer do
64
72
  assert_equal(10, c.frame.bottom)
65
73
  end
66
74
 
67
- it "uses the provided information for the new and all following pages" do
68
- @composer.new_page(page_size: [0, 0, 50, 100], margin: 10)
69
- assert_equal([0, 0, 50, 100], @composer.page.box.value)
70
- assert_equal(10, @composer.frame.left)
71
- assert_equal(10, @composer.frame.bottom)
75
+ it "uses the named page style for the new page" do
76
+ @composer.page_style(:other, page_size: [0, 0, 100, 100])
77
+ @composer.new_page(:other)
78
+ assert_equal([0, 0, 100, 100], @composer.page.box.value)
79
+ end
80
+
81
+ it "sets the next page's style to the next_style value of the used page style" do
82
+ @composer.page_style(:one, page_size: [0, 0, 1, 1]).next_style = :two
83
+ @composer.page_style(:two, page_size: [0, 0, 2, 2]).next_style = :one
84
+ @composer.new_page(:one)
85
+ assert_equal([0, 0, 1, 1], @composer.page.box.value)
86
+ @composer.new_page
87
+ assert_equal([0, 0, 2, 2], @composer.page.box.value)
88
+ @composer.new_page
89
+ assert_equal([0, 0, 1, 1], @composer.page.box.value)
90
+ end
91
+
92
+ it "uses the current page style for new pages if no next_style value is set" do
93
+ @composer.page_style(:one, page_size: [0, 0, 1, 1])
94
+ @composer.new_page(:one)
95
+ assert_equal([0, 0, 1, 1], @composer.page.box.value)
72
96
  @composer.new_page
73
- assert_same(@composer.document.pages[2], @composer.page)
74
- assert_equal([0, 0, 50, 100], @composer.page.box.value)
75
- assert_equal(10, @composer.frame.left)
76
- assert_equal(10, @composer.frame.bottom)
97
+ assert_equal([0, 0, 1, 1], @composer.page.box.value)
98
+ end
99
+
100
+ it "fails if the specified page style has not been defined" do
101
+ assert_raises(ArgumentError) { @composer.new_page(:unknown) }
77
102
  end
78
103
  end
79
104
 
@@ -175,7 +175,6 @@ describe HexaPDF::DictionaryFields do
175
175
 
176
176
  it "allows conversion to a Time object from a binary string" do
177
177
  refute(@field.convert('test'.b, self))
178
- refute(@field.convert('D:01211016165909+00\'64'.b, self))
179
178
 
180
179
  [
181
180
  ["D:1998", [1998, 01, 01, 00, 00, 00, "-00:00"]],
@@ -189,8 +188,15 @@ describe HexaPDF::DictionaryFields do
189
188
  ["D:19981223-08'00'", [1998, 12, 23, 00, 00, 00, "-08:00"]],
190
189
  ["D:199812-08'00'", [1998, 12, 01, 00, 00, 00, "-08:00"]],
191
190
  ["D:1998-08'00'", [1998, 01, 01, 00, 00, 00, "-08:00"]],
192
- ["D:19981223195210-08", [1998, 12, 23, 19, 52, 10, "-08:00"]], # non-standard, missing '
193
- ["D:19981223195210-08'00", [1998, 12, 23, 19, 52, 10, "-08:00"]], # non-standard, missing '
191
+ ["D:19981223195210-08", [1998, 12, 23, 19, 52, 10, "-08:00"]], # missing '
192
+ ["D:19981223195210-08'00", [1998, 12, 23, 19, 52, 10, "-08:00"]], # missing '
193
+ ["D:19981223195210-54'00", [1998, 12, 23, 19, 52, 10, "-23:59:59"]], # TZ hour too large
194
+ ["D:19981223195210+10'65", [1998, 12, 23, 19, 52, 10, "+11:05"]], # TZ min too large
195
+ ["D:19982423195210-08'00'", [1998, 12, 23, 19, 52, 10, "-08:00"]], # months too large
196
+ ["D:19981273195210-08'00'", [1998, 12, 31, 19, 52, 10, "-08:00"]], # day too large
197
+ ["D:19981223275210-08'00'", [1998, 12, 23, 23, 52, 10, "-08:00"]], # hour too large
198
+ ["D:19981223197710-08'00'", [1998, 12, 23, 19, 59, 10, "-08:00"]], # minute too large
199
+ ["D:19981223195280-08'00'", [1998, 12, 23, 19, 52, 59, "-08:00"]], # seconds too large
194
200
  ].each do |str, data|
195
201
  obj = @field.convert(str, self)
196
202
  assert_equal(Time.new(*data), obj, "date str used: #{str}")
@@ -504,7 +504,7 @@ describe HexaPDF::Document do
504
504
 
505
505
  it "allows to conveniently sign a document" do
506
506
  mock = Minitest::Mock.new
507
- mock.expect(:handler, :handler, name: :handler, opt: :key)
507
+ mock.expect(:signing_handler, :handler, name: :handler, opt: :key)
508
508
  mock.expect(:add, :added, [:io, :handler], signature: :sig, write_options: :write_options)
509
509
  @doc.instance_variable_set(:@signatures, mock)
510
510
  result = @doc.sign(:io, handler: :handler, write_options: :write_options, signature: :sig, opt: :key)
@@ -40,7 +40,7 @@ describe HexaPDF::Writer do
40
40
  219
41
41
  %%EOF
42
42
  3 0 obj
43
- <</Producer(HexaPDF version 0.28.0)>>
43
+ <</Producer(HexaPDF version 0.31.0)>>
44
44
  endobj
45
45
  xref
46
46
  3 1
@@ -72,7 +72,7 @@ describe HexaPDF::Writer do
72
72
  141
73
73
  %%EOF
74
74
  6 0 obj
75
- <</Producer(HexaPDF version 0.28.0)>>
75
+ <</Producer(HexaPDF version 0.31.0)>>
76
76
  endobj
77
77
  2 0 obj
78
78
  <</Length 10>>stream
@@ -164,14 +164,14 @@ describe HexaPDF::Writer do
164
164
 
165
165
  document = HexaPDF::Document.new(io: io)
166
166
  assert_equal(3, document.revisions.count)
167
- assert_equal(1, document.revisions.all[0].object(2)[:Kids].length)
168
- assert_equal(2, document.revisions.all[1].object(2)[:Kids].length)
169
- assert_equal(3, document.revisions.all[2].object(2)[:Kids].length)
167
+ assert_equal(1, document.revisions.all[0].object(3)[:Kids].length)
168
+ assert_equal(2, document.revisions.all[1].object(3)[:Kids].length)
169
+ assert_equal(3, document.revisions.all[2].object(3)[:Kids].length)
170
170
  end
171
171
 
172
172
  it "creates an xref stream if no xref stream is in a revision but object streams are" do
173
173
  document = HexaPDF::Document.new
174
- document.add({Type: :ObjStm})
174
+ document.add({}, type: HexaPDF::Type::ObjectStream)
175
175
  HexaPDF::Writer.new(document, StringIO.new).write
176
176
  assert_equal(:XRef, document.object(4).type)
177
177
  end
@@ -184,7 +184,7 @@ describe HexaPDF::Writer do
184
184
 
185
185
  document = HexaPDF::Document.new(io: io)
186
186
  document.pages.add
187
- document.add({Type: :ObjStm})
187
+ document.add({}, type: HexaPDF::Type::ObjectStream)
188
188
  io2 = StringIO.new
189
189
  HexaPDF::Writer.new(document, io2).write_incremental
190
190
 
@@ -214,7 +214,7 @@ describe HexaPDF::Writer do
214
214
  <</Type/Page/MediaBox[0 0 595 842]/Parent 2 0 R/Resources<<>>>>
215
215
  endobj
216
216
  5 0 obj
217
- <</Producer(HexaPDF version 0.28.0)>>
217
+ <</Producer(HexaPDF version 0.31.0)>>
218
218
  endobj
219
219
  4 0 obj
220
220
  <</Root 1 0 R/Info 5 0 R/Size 6/Type/XRef/W[1 1 2]/Index[0 6]/Filter/FlateDecode/DecodeParms<</Columns 4/Predictor 12>>/Length 33>>stream
@@ -15,7 +15,7 @@ describe HexaPDF::Type::FontSimple do
15
15
  EOF
16
16
  font_descriptor = @doc.add({Type: :FontDescriptor, FontName: :Embedded, Flags: 0b100,
17
17
  FontBBox: [0, 1, 2, 3], ItalicAngle: 0, Ascent: 900,
18
- Descent: -100, CapHeight: 800, StemV: 20})
18
+ MissingWidth: 500, Descent: -100, CapHeight: 800, StemV: 20})
19
19
  @font = @doc.add({Type: :Font, Encoding: :WinAnsiEncoding,
20
20
  BaseFont: :Embedded, FontDescriptor: font_descriptor, ToUnicode: cmap,
21
21
  FirstChar: 32, LastChar: 34, Widths: [600, 0, 700]},
@@ -130,9 +130,7 @@ describe HexaPDF::Type::FontSimple do
130
130
  end
131
131
 
132
132
  it "returns the /MissingWidth of a /FontDescriptor if available and the width was not found" do
133
- assert_equal(0, @font.width(0))
134
- @font[:FontDescriptor][:MissingWidth] = 99
135
- assert_equal(99, @font.width(0))
133
+ assert_equal(500, @font.width(0))
136
134
  end
137
135
 
138
136
  it "returns 0 for a missing code point when FontDescriptor is not available" do
@@ -169,8 +167,22 @@ describe HexaPDF::Type::FontSimple do
169
167
  end
170
168
 
171
169
  it "validates the lengths of the /Widths field" do
172
- @font[:Widths] = [65]
173
- refute(@font.validate)
170
+ @font[:Widths] = [65, 65]
171
+ assert(@font.validate)
172
+ assert_equal([65, 65, 65], @font[:Widths])
173
+
174
+ @font[:Widths] = [65, 70]
175
+ assert(@font.validate)
176
+ assert_equal([65, 70, 500], @font[:Widths])
177
+
178
+ @font[:Widths] = [65, 70]
179
+ @font.delete(:FontDescriptor)
180
+ assert(@font.validate)
181
+ assert_equal([65, 70, 0], @font[:Widths])
182
+
183
+ @font[:Widths] = [65, 65, 70, 90, 100]
184
+ assert(@font.validate)
185
+ assert_equal([65, 65, 70], @font[:Widths])
174
186
  end
175
187
  end
176
188
  end
@@ -123,12 +123,21 @@ describe HexaPDF::Type::ObjectStream do
123
123
  end
124
124
  end
125
125
 
126
- it "fails validation if gen != 0" do
127
- assert(@obj.validate(auto_correct: false))
128
- @obj.gen = 1
129
- refute(@obj.validate(auto_correct: false) do |msg, correctable|
130
- assert_match(/invalid generation/, msg)
131
- refute(correctable)
132
- end)
126
+ describe "perform_validation" do
127
+ it "fails validation if gen != 0" do
128
+ assert(@obj.validate(auto_correct: false))
129
+ @obj.gen = 1
130
+ refute(@obj.validate(auto_correct: false) do |msg, correctable|
131
+ assert_match(/invalid generation/, msg)
132
+ refute(correctable)
133
+ end)
134
+ end
135
+
136
+ it "sets the /N and /First entries to dummy values so that validation works" do
137
+ @obj = HexaPDF::Type::ObjectStream.new({}, oid: 1, document: @doc)
138
+ assert(@obj.validate(auto_correct: false))
139
+ assert_equal(0, @obj[:N])
140
+ assert_equal(0, @obj[:First])
141
+ end
133
142
  end
134
143
  end
@@ -30,11 +30,12 @@ describe HexaPDF::Type::Outline do
30
30
 
31
31
  describe "perform_validation" do
32
32
  before do
33
- 5.times { @outline.add_item("Test1") }
33
+ @outline_items = 5.times.map { @outline.add_item("Test1") }
34
34
  end
35
35
 
36
36
  it "fixes a missing /First entry" do
37
37
  @outline.delete(:First)
38
+ @outline_items[0][:Prev] = HexaPDF::Reference.new(100)
38
39
  called = false
39
40
  @outline.validate do |msg, correctable, _|
40
41
  called = true
@@ -46,6 +47,7 @@ describe HexaPDF::Type::Outline do
46
47
 
47
48
  it "fixes a missing /Last entry" do
48
49
  @outline.delete(:Last)
50
+ @outline_items[4][:Next] = HexaPDF::Reference.new(100)
49
51
  called = false
50
52
  @outline.validate do |msg, correctable, _|
51
53
  called = true
@@ -276,12 +276,13 @@ describe HexaPDF::Type::OutlineItem do
276
276
 
277
277
  describe "perform_validation" do
278
278
  before do
279
- 5.times { @item.add_item("Test1") }
279
+ @outline_items = 5.times.map { @item.add_item("Test1") }
280
280
  @item[:Parent] = @doc.add({})
281
281
  end
282
282
 
283
283
  it "fixes a missing /First entry" do
284
284
  @item.delete(:First)
285
+ @outline_items[0][:Prev] = HexaPDF::Reference.new(100)
285
286
  called = false
286
287
  @item.validate do |msg, correctable, _|
287
288
  called = true
@@ -293,6 +294,7 @@ describe HexaPDF::Type::OutlineItem do
293
294
 
294
295
  it "fixes a missing /Last entry" do
295
296
  @item.delete(:Last)
297
+ @outline_items[4][:Next] = HexaPDF::Reference.new(100)
296
298
  called = false
297
299
  @item.validate do |msg, correctable, _|
298
300
  called = true
@@ -19,9 +19,21 @@ describe HexaPDF::Type::Page do
19
19
  assert_equal([0, 0, 842, 595], HexaPDF::Type::Page.media_box(:A4, orientation: :landscape))
20
20
  end
21
21
 
22
+ it "works with a paper size array" do
23
+ assert_equal([0, 0, 842, 595], HexaPDF::Type::Page.media_box([0, 0, 842, 595]))
24
+ end
25
+
22
26
  it "fails if the paper size is unknown" do
23
27
  assert_raises(HexaPDF::Error) { HexaPDF::Type::Page.media_box(:Unknown) }
24
28
  end
29
+
30
+ it "fails if the array doesn't contain four numbers" do
31
+ assert_raises(HexaPDF::Error) { HexaPDF::Type::Page.media_box([0, 1, 2]) }
32
+ end
33
+
34
+ it "fails if the array doesn't contain only numbers" do
35
+ assert_raises(HexaPDF::Error) { HexaPDF::Type::Page.media_box([0, 1, 2, 'a']) }
36
+ end
25
37
  end
26
38
 
27
39
  # Asserts that the page's contents contains the operators.
@@ -70,22 +82,41 @@ describe HexaPDF::Type::Page do
70
82
  end
71
83
  end
72
84
 
73
- describe "validation" do
85
+ describe "perform_validation" do
74
86
  it "only does validation if the page is in the document's page tree" do
75
87
  page = @doc.add({Type: :Page})
88
+ assert(page.validate(auto_correct: false))
89
+ page[:Parent] = @doc.add({Type: :Pages, Kids: [page], Count: 1})
90
+ assert(page.validate(auto_correct: false))
91
+ @doc.pages.add(page)
92
+ refute(page.validate(auto_correct: false))
93
+ end
94
+
95
+ it "validates that the required inheritable field /Resources is set" do
96
+ page = @doc.pages.add
97
+ page.delete(:Resources)
98
+ refute(page.validate(auto_correct: false))
76
99
  assert(page.validate)
77
- page[:Parent] = @doc.add({Type: :Pages, Kids: [page]})
78
- assert(page.validate)
79
- page[:Parent] = @doc.catalog.pages
80
- refute(page.validate)
100
+ assert_kind_of(HexaPDF::Dictionary, page[:Resources])
81
101
  end
82
102
 
83
- it "fails if a required inheritable field is not set" do
84
- root = @doc.catalog[:Pages] = @doc.add({Type: :Pages})
85
- page = @doc.add({Type: :Page, Parent: root})
86
- message = ''
87
- refute(page.validate {|m, _| message = m })
88
- assert_match(/inheritable.*MediaBox/i, message)
103
+ it "validates that the required inheritable field /MediaBox is set" do
104
+ page1 = @doc.pages.add(:Letter)
105
+ page2 = @doc.pages.add(:Letter)
106
+ page3 = @doc.pages.add(:Letter)
107
+
108
+ [page1, page2, page3].each do |page|
109
+ page.delete(:MediaBox)
110
+ refute(page.validate(auto_correct: false))
111
+ assert(page.validate)
112
+ assert_equal([0, 0, 612, 792], page[:MediaBox])
113
+ end
114
+
115
+ page2.delete(:MediaBox)
116
+ page1[:MediaBox] = [0, 0, 1, 1]
117
+ refute(page2.validate(auto_correct: false))
118
+ assert(page2.validate)
119
+ assert_equal([0, 0, 595, 842], page2[:MediaBox])
89
120
  end
90
121
  end
91
122
 
@@ -6,9 +6,10 @@ require 'hexapdf/type/xref_stream'
6
6
  describe HexaPDF::Type::XRefStream do
7
7
  before do
8
8
  @doc = Object.new
9
+ @doc.instance_variable_set(:@version, '1.5')
9
10
  def (@doc).deref(obj); obj; end
10
11
  def (@doc).wrap(obj, **); obj; end
11
- @obj = HexaPDF::Type::XRefStream.new({}, oid: 1, document: @doc)
12
+ @obj = HexaPDF::Type::XRefStream.new({}, oid: 1, document: @doc, stream: '')
12
13
  end
13
14
 
14
15
  describe "xref_section" do
@@ -141,4 +142,8 @@ describe HexaPDF::Type::XRefStream do
141
142
  assert_raises(HexaPDF::Error) { @obj.update_with_xref_section_and_trailer(@section, {}) }
142
143
  end
143
144
  end
145
+
146
+ it "sets /Size and /W to dummy values to make validation work" do
147
+ assert(@obj.validate(auto_correct: false))
148
+ end
144
149
  end