hexapdf 0.26.2 → 0.27.0

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 (45) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +56 -0
  3. data/README.md +1 -1
  4. data/examples/013-text_layouter_shapes.rb +8 -8
  5. data/examples/016-frame_automatic_box_placement.rb +3 -3
  6. data/examples/017-frame_text_flow.rb +3 -3
  7. data/examples/020-column_box.rb +3 -3
  8. data/lib/hexapdf/cli/split.rb +7 -7
  9. data/lib/hexapdf/cli/watermark.rb +2 -2
  10. data/lib/hexapdf/configuration.rb +2 -0
  11. data/lib/hexapdf/dictionary.rb +3 -12
  12. data/lib/hexapdf/document/destinations.rb +42 -5
  13. data/lib/hexapdf/document/signatures.rb +265 -48
  14. data/lib/hexapdf/importer.rb +3 -0
  15. data/lib/hexapdf/parser.rb +1 -0
  16. data/lib/hexapdf/revisions.rb +3 -1
  17. data/lib/hexapdf/tokenizer.rb +2 -2
  18. data/lib/hexapdf/type/acro_form/form.rb +28 -1
  19. data/lib/hexapdf/type/catalog.rb +1 -1
  20. data/lib/hexapdf/type/outline.rb +18 -0
  21. data/lib/hexapdf/type/outline_item.rb +72 -14
  22. data/lib/hexapdf/type/page.rb +56 -35
  23. data/lib/hexapdf/type/resources.rb +13 -17
  24. data/lib/hexapdf/type/signature/adbe_pkcs7_detached.rb +16 -2
  25. data/lib/hexapdf/type/signature.rb +10 -0
  26. data/lib/hexapdf/version.rb +1 -1
  27. data/lib/hexapdf/writer.rb +3 -0
  28. data/test/hexapdf/document/test_destinations.rb +41 -0
  29. data/test/hexapdf/document/test_signatures.rb +139 -19
  30. data/test/hexapdf/test_importer.rb +14 -0
  31. data/test/hexapdf/test_parser.rb +2 -2
  32. data/test/hexapdf/test_revisions.rb +20 -12
  33. data/test/hexapdf/test_tokenizer.rb +11 -1
  34. data/test/hexapdf/test_writer.rb +11 -3
  35. data/test/hexapdf/type/acro_form/test_form.rb +47 -0
  36. data/test/hexapdf/type/signature/common.rb +52 -0
  37. data/test/hexapdf/type/signature/test_adbe_pkcs7_detached.rb +21 -0
  38. data/test/hexapdf/type/test_catalog.rb +5 -2
  39. data/test/hexapdf/type/test_outline.rb +1 -1
  40. data/test/hexapdf/type/test_outline_item.rb +62 -1
  41. data/test/hexapdf/type/test_page.rb +41 -20
  42. data/test/hexapdf/type/test_resources.rb +0 -5
  43. data/test/hexapdf/type/test_signature.rb +8 -0
  44. data/test/test_helper.rb +1 -1
  45. metadata +17 -3
@@ -19,6 +19,7 @@ describe HexaPDF::Importer::NullableWeakRef do
19
19
  end
20
20
 
21
21
  describe HexaPDF::Importer do
22
+ class TestClass < HexaPDF::Dictionary; end
22
23
  before do
23
24
  @source = HexaPDF::Document.new
24
25
  obj = @source.add("test")
@@ -86,6 +87,19 @@ describe HexaPDF::Importer do
86
87
  assert_same(hash, obj[:hash])
87
88
  end
88
89
 
90
+ it "uses the class of the argument when directly importing a HexaPDF::Object" do
91
+ src_obj = @source.wrap(@hash, type: TestClass)
92
+ dest_obj = @importer.import(src_obj)
93
+ assert_instance_of(TestClass, dest_obj)
94
+ end
95
+
96
+ it "uses the class of the argument when importing an already mapped HexaPDF::Object" do
97
+ @importer.import(@obj) # also maps @hash
98
+ src_obj = @source.wrap(@hash, type: TestClass)
99
+ dest_obj = @importer.import(src_obj)
100
+ assert_instance_of(TestClass, dest_obj)
101
+ end
102
+
89
103
  it "duplicates the stream if it is a string" do
90
104
  src_obj = @source.add({}, stream: 'data')
91
105
  dst_obj = @importer.import(src_obj)
@@ -108,7 +108,7 @@ describe HexaPDF::Parser do
108
108
  end
109
109
 
110
110
  it "treats indirect objects with invalid values as null objects" do
111
- create_parser("1 0 obj <</test ( /other (end)>> endobj")
111
+ create_parser("1 0 obj <</test <end)> > endobj")
112
112
  object, * = @parser.parse_indirect_object
113
113
  assert_nil(object)
114
114
  end
@@ -210,7 +210,7 @@ describe HexaPDF::Parser do
210
210
  end
211
211
 
212
212
  it "fails for invalid values" do
213
- create_parser("1 0 obj <</test ( /other (end)>> endobj")
213
+ create_parser("1 0 obj <</test <end)> >endobj")
214
214
  exp = assert_raises(HexaPDF::MalformedPDFError) { @parser.parse_indirect_object }
215
215
  assert_match(/Invalid value after '1 0 obj'/, exp.message)
216
216
  end
@@ -273,40 +273,48 @@ describe HexaPDF::Revisions do
273
273
  1 0 obj
274
274
  10
275
275
  endobj
276
+ xref
277
+ 0 2
278
+ 0000000000 65535 f
279
+ 0000000009 00000 n
280
+ trailer
281
+ << /Size 2 >>
282
+ startxref
283
+ 27
284
+ %%EOF
276
285
 
277
286
  2 0 obj
278
287
  20
279
288
  endobj
280
289
 
281
290
  3 0 obj
282
- << /Type /XRef /Size 3 /Index [2 1] /W [1 1 1] /Filter /ASCIIHexDecode /Length 6
291
+ << /Type /XRef /Size 4 /Index [2 1] /W [1 1 1] /Filter /ASCIIHexDecode /Length 6
283
292
  >>stream
284
- 011C00
293
+ 017600
285
294
  endstream
286
295
  endobj
287
296
 
288
297
  xref
289
- 0 4
290
- 0000000000 65535 f
291
- 0000000009 00000 n
298
+ 2 2
292
299
  0000000000 65535 f
293
- 0000000047 00000 n
300
+ 0000000137 00000 n
294
301
  trailer
295
- << /Size 3 >>
302
+ << /Size 4 /Prev 27>>
296
303
  startxref
297
- 170
304
+ 260
298
305
  %%EOF
299
306
 
300
307
  xref
301
308
  0 0
302
309
  trailer
303
- << /Size 3 /Prev 170 /XRefStm 47>>
310
+ << /Size 4 /Prev 260 /XRefStm 137>>
304
311
  startxref
305
- 302
312
+ 360
306
313
  %%EOF
307
314
  EOF
308
- doc = HexaPDF::Document.new(io: io)
309
- assert_equal(1, doc.revisions.count)
315
+ doc = HexaPDF::Document.new(io: io, config: {'parser.try_xref_reconstruction' => false})
316
+ assert_equal(2, doc.revisions.count)
317
+ assert_equal(10, doc.object(1).value)
310
318
  assert_equal(20, doc.object(2).value)
311
319
  end
312
320
 
@@ -13,9 +13,19 @@ describe HexaPDF::Tokenizer do
13
13
  end
14
14
 
15
15
  it "handles object references" do
16
- create_tokenizer("1 0 R 2 15 R ")
16
+ #HexaPDF::Reference.new(1, 0), HexaPDF::Reference.new(1, 2), 2, -1, 'R', 0, 0, 'R', -1, 0, 'R',
17
+ create_tokenizer("1 0 R +2 +15 R 2 -1 R 0 0 R -1 0 R")
17
18
  assert_equal(HexaPDF::Reference.new(1, 0), @tokenizer.next_token)
18
19
  assert_equal(HexaPDF::Reference.new(2, 15), @tokenizer.next_token)
20
+ assert_equal(2, @tokenizer.next_token)
21
+ assert_equal(-1, @tokenizer.next_token)
22
+ assert_equal('R', @tokenizer.next_token)
23
+ assert_equal(0, @tokenizer.next_token)
24
+ assert_equal(0, @tokenizer.next_token)
25
+ assert_equal('R', @tokenizer.next_token)
26
+ assert_equal(-1, @tokenizer.next_token)
27
+ assert_equal(0, @tokenizer.next_token)
28
+ assert_equal('R', @tokenizer.next_token)
19
29
  @tokenizer.pos = 0
20
30
  assert_equal(HexaPDF::Reference.new(1, 0), @tokenizer.next_object)
21
31
  assert_equal(HexaPDF::Reference.new(2, 15), @tokenizer.next_object)
@@ -40,7 +40,7 @@ describe HexaPDF::Writer do
40
40
  219
41
41
  %%EOF
42
42
  3 0 obj
43
- <</Producer(HexaPDF version 0.26.2)>>
43
+ <</Producer(HexaPDF version 0.27.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.26.2)>>
75
+ <</Producer(HexaPDF version 0.27.0)>>
76
76
  endobj
77
77
  2 0 obj
78
78
  <</Length 10>>stream
@@ -125,6 +125,14 @@ describe HexaPDF::Writer do
125
125
  refute_match(/^trailer/, output_io.string)
126
126
  end
127
127
 
128
+ it "updates the PDF version using the catalog's /Version entry if necessary" do
129
+ doc = HexaPDF::Document.new(io: @std_input_io)
130
+ doc.version = '2.0'
131
+ output_io = StringIO.new
132
+ HexaPDF::Writer.write(doc, output_io, incremental: true)
133
+ assert_equal('2.0', HexaPDF::Document.new(io: output_io).version)
134
+ end
135
+
128
136
  it "raises an error if the used encryption was changed" do
129
137
  io = StringIO.new
130
138
  doc = HexaPDF::Document.new
@@ -206,7 +214,7 @@ describe HexaPDF::Writer do
206
214
  <</Type/Page/MediaBox[0 0 595 842]/Parent 2 0 R/Resources<<>>>>
207
215
  endobj
208
216
  5 0 obj
209
- <</Producer(HexaPDF version 0.26.2)>>
217
+ <</Producer(HexaPDF version 0.27.0)>>
210
218
  endobj
211
219
  4 0 obj
212
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
@@ -327,6 +327,12 @@ describe HexaPDF::Type::AcroForm::Form do
327
327
  assert_equal(1, result.size)
328
328
  assert(@doc.catalog.key?(:AcroForm))
329
329
  end
330
+
331
+ it "returns the fields that could not be flattened" do
332
+ @cb.create_appearances
333
+ result = @acro_form.flatten(create_appearances: false)
334
+ assert_equal([@tf], result)
335
+ end
330
336
  end
331
337
 
332
338
  describe "perform_validation" do
@@ -349,6 +355,47 @@ describe HexaPDF::Type::AcroForm::Form do
349
355
  assert_equal("0.0 g /F1 0 Tf", @acro_form[:DA])
350
356
  end
351
357
 
358
+ describe "field hierarchy validation" do
359
+ before do
360
+ @acro_form[:Fields] = [
361
+ nil,
362
+ HexaPDF::Object.new(nil),
363
+ 5,
364
+ HexaPDF::Object.new(5),
365
+ @doc.add({T: :Tx1}),
366
+ @doc.add({T: :Tx2, Kids: [nil, @doc.add({Subtype: :Widget})]}),
367
+ @doc.add({T: :Tx3, FT: :Tx, Kids: [@doc.add({T: :Tx4}),
368
+ [:nothing],
369
+ @doc.add({T: :Tx5, Kids: [@doc.add({T: :Tx6})]})]}),
370
+ ]
371
+ @acro_form[:Fields][6][:Kids][0][:Parent] = @acro_form[:Fields][6]
372
+ @acro_form[:Fields][6][:Kids][2][:Parent] = @acro_form[:Fields][6]
373
+ @acro_form[:Fields][6][:Kids][2][:Kids][0][:Parent] = @acro_form[:Fields][6][:Kids][2]
374
+ end
375
+
376
+ it "removes invalid objects from the field hierarchy" do
377
+ assert(@acro_form.validate)
378
+ assert_equal([:Tx1, :Tx2, :Tx3, :Tx4, :Tx5, :Tx6],
379
+ @acro_form.each_field(terminal_only: false).map {|f| f[:T] })
380
+ end
381
+
382
+ it "handles missing /Parent fields" do
383
+ @acro_form[:Fields][6][:Kids][0].delete(:Parent)
384
+ assert(@acro_form.validate)
385
+ assert_equal(1, @acro_form[:Fields][2][:Kids].size)
386
+ assert_equal(:Tx5, @acro_form[:Fields][2][:Kids][0][:T])
387
+ assert_equal(:Tx4, @acro_form[:Fields][3][:T])
388
+ end
389
+
390
+ it "handles /Parent field pointing to somewhere else" do
391
+ @acro_form[:Fields][6][:Kids][0][:Parent] = @acro_form[:Fields][4]
392
+ assert(@acro_form.validate)
393
+ assert_equal(2, @acro_form[:Fields][2][:Kids].size)
394
+ assert_equal(:Tx4, @acro_form[:Fields][2][:Kids][0][:T])
395
+ assert_equal(@acro_form[:Fields][2], @acro_form[:Fields][2][:Kids][0][:Parent])
396
+ end
397
+ end
398
+
352
399
  describe "automatically creates the terminal fields; appearances" do
353
400
  before do
354
401
  @cb = @acro_form.create_check_box('test2')
@@ -65,6 +65,58 @@ module HexaPDF
65
65
  end
66
66
  end
67
67
 
68
+ def timestamp_certificate
69
+ @timestamp_certificate ||=
70
+ begin
71
+ name = OpenSSL::X509::Name.parse('/CN=timestamp/DC=gettalong')
72
+
73
+ signer_cert = OpenSSL::X509::Certificate.new
74
+ signer_cert.serial = 3
75
+ signer_cert.version = 2
76
+ signer_cert.not_before = Time.now - 86400
77
+ signer_cert.not_after = Time.now + 86400
78
+ signer_cert.public_key = signer_key.public_key
79
+ signer_cert.subject = name
80
+ signer_cert.issuer = ca_certificate.subject
81
+
82
+ extension_factory = OpenSSL::X509::ExtensionFactory.new
83
+ extension_factory.subject_certificate = signer_cert
84
+ extension_factory.issuer_certificate = ca_certificate
85
+ signer_cert.add_extension(extension_factory.create_extension('subjectKeyIdentifier', 'hash'))
86
+ signer_cert.add_extension(extension_factory.create_extension('basicConstraints', 'CA:FALSE'))
87
+ signer_cert.add_extension(extension_factory.create_extension('keyUsage', 'digitalSignature'))
88
+ signer_cert.add_extension(extension_factory.create_extension('extendedKeyUsage', 'timeStamping', true))
89
+ signer_cert.sign(ca_key, OpenSSL::Digest.new('SHA1'))
90
+
91
+ signer_cert
92
+ end
93
+ end
94
+
95
+ def start_tsa_server
96
+ return if defined?(@tsa_server)
97
+ require 'webrick'
98
+ port = 34567
99
+ @tsa_server = WEBrick::HTTPServer.new(Port: port, BindAddress: '127.0.0.1',
100
+ Logger: WEBrick::Log.new(StringIO.new), AccessLog: [])
101
+ @tsa_server.mount_proc('/') do |request, response|
102
+ @tsr = OpenSSL::Timestamp::Request.new(request.body)
103
+ case (@tsr.policy_id || '1.2.3.4.0')
104
+ when '1.2.3.4.0', '1.2.3.4.2'
105
+ fac = OpenSSL::Timestamp::Factory.new
106
+ fac.gen_time = Time.now
107
+ fac.serial_number = 1
108
+ fac.default_policy_id = '1.2.3.4.5'
109
+ fac.allowed_digests = ["sha256", "sha512"]
110
+ tsr = fac.create_timestamp(CERTIFICATES.signer_key, CERTIFICATES.timestamp_certificate,
111
+ @tsr)
112
+ response.body = tsr.to_der
113
+ when '1.2.3.4.1'
114
+ response.status = 403
115
+ response.body = "Invalid"
116
+ end
117
+ end
118
+ Thread.new { @tsa_server.start }
119
+ end
68
120
  end
69
121
 
70
122
  end
@@ -1,5 +1,6 @@
1
1
  # -*- encoding: utf-8 -*-
2
2
 
3
+ require 'digest'
3
4
  require 'test_helper'
4
5
  require_relative 'common'
5
6
  require 'hexapdf/type/signature'
@@ -95,5 +96,25 @@ describe HexaPDF::Type::Signature::AdbePkcs7Detached do
95
96
  assert_equal(:error, result.messages.last.type)
96
97
  assert_match(/Signature verification failed/, result.messages.last.content)
97
98
  end
99
+
100
+ it "verifies a timestamp signature" do
101
+ req = OpenSSL::Timestamp::Request.new
102
+ req.algorithm = 'SHA256'
103
+ req.message_imprint = Digest::SHA256.digest(@data)
104
+ req.policy_id = "1.2.3.4.5"
105
+ req.nonce = 42
106
+ fac = OpenSSL::Timestamp::Factory.new
107
+ fac.gen_time = Time.now
108
+ fac.serial_number = 1
109
+ fac.allowed_digests = ["sha256", "sha512"]
110
+ res = fac.create_timestamp(CERTIFICATES.signer_key, CERTIFICATES.timestamp_certificate, req)
111
+ @dict.contents = res.token.to_der
112
+ @dict.signature_type = 'ETSI.RFC3161'
113
+ @handler = HexaPDF::Type::Signature::AdbePkcs7Detached.new(@dict)
114
+
115
+ result = @handler.verify(@store)
116
+ assert_equal(:info, result.messages.last.type)
117
+ assert_match(/Signature valid/, result.messages.last.content)
118
+ end
98
119
  end
99
120
  end
@@ -29,8 +29,11 @@ describe HexaPDF::Type::Catalog do
29
29
  assert_same(other, names)
30
30
  end
31
31
 
32
- it "creates the document outline on access" do
33
- assert_nil(@catalog[:Outlines])
32
+ it "uses or creates the document outline on access" do
33
+ @catalog[:Outlines] = {}
34
+ assert_equal(:Outlines, @catalog.outline.type)
35
+
36
+ @catalog.delete(:Outlines)
34
37
  outline = @catalog.outline
35
38
  assert_equal(:Outlines, outline.type)
36
39
  assert_same(outline, @catalog.outline)
@@ -25,7 +25,7 @@ describe HexaPDF::Type::Outline do
25
25
  end
26
26
  item1.add_item("Item5")
27
27
  end
28
- assert_equal(%w[Item1 Item2 Item3 Item4 Item5], @outline.each_item.map(&:title))
28
+ assert_equal(%w[Item1 Item2 Item3 Item4 Item5], @outline.each_item.map {|i, _| i.title })
29
29
  end
30
30
 
31
31
  describe "perform_validation" do
@@ -10,6 +10,10 @@ describe HexaPDF::Type::OutlineItem do
10
10
  @item = @doc.add({Title: "root", Count: 0}, type: :XXOutlineItem)
11
11
  end
12
12
 
13
+ it "must be an indirect object" do
14
+ assert(@item.must_be_indirect?)
15
+ end
16
+
13
17
  describe "title" do
14
18
  it "returns the set title" do
15
19
  @item[:Title] = 'Test'
@@ -78,6 +82,43 @@ describe HexaPDF::Type::OutlineItem do
78
82
  end
79
83
  end
80
84
 
85
+ describe "level" do
86
+ it "returns 0 for the outline dictionary when treated as an item" do
87
+ assert_equal(0, @item.level)
88
+ end
89
+
90
+ it "returns 1 for the root level items" do
91
+ @item[:Parent] = {Type: :Outlines}
92
+ assert_equal(1, @item.level)
93
+ end
94
+
95
+ it "returns the correct level for items in the hierarchy" do
96
+ @item[:Parent] = {Title: 'Root elem', Parent: {Type: :Outlines}}
97
+ assert_equal(2, @item.level)
98
+ end
99
+ end
100
+
101
+ describe "destination_page" do
102
+ it "returns the page of a set destination" do
103
+ @item[:Dest] = [5, :Fit]
104
+ assert_equal(5, @item.destination_page)
105
+ end
106
+
107
+ it "returns the page of a set GoTO action" do
108
+ @item[:A] = {S: :GoTo, D: [5, :Fit]}
109
+ assert_equal(5, @item.destination_page)
110
+ end
111
+
112
+ it "returns nil if no destination or action is set" do
113
+ assert_nil(@item.destination_page)
114
+ end
115
+
116
+ it "returns nil if an action besides GoTo is set" do
117
+ @item[:A] = {S: :GoToR}
118
+ assert_nil(@item.destination_page)
119
+ end
120
+ end
121
+
81
122
  describe "add" do
82
123
  it "returns the created item" do
83
124
  new_item = @item.add_item("Test")
@@ -118,6 +159,24 @@ describe HexaPDF::Type::OutlineItem do
118
159
  assert_same(new_item, yielded_item)
119
160
  end
120
161
 
162
+ it "uses the provided outline item instead of creating a new one" do
163
+ item = @doc.wrap({Dest: [1, :Fit], flags: 1, First: 5, Count: 2}, type: :XXOutlineItem)
164
+ new_item = @item.add_item(item, destination: [2, :Fit])
165
+ assert_same(item, new_item)
166
+ assert_equal([1, :Fit], new_item.destination)
167
+ assert_same(@item, new_item[:Parent])
168
+ refute(new_item.key?(:First))
169
+ assert_equal(0, new_item[:Count])
170
+
171
+ item = @doc.wrap({Count: nil}, type: :XXOutlineItem)
172
+ new_item = @item.add_item(item)
173
+ refute(new_item.key?(:Count))
174
+
175
+ item = @doc.wrap({Count: -1}, type: :XXOutlineItem)
176
+ new_item = @item.add_item(item)
177
+ refute(new_item.key?(:Count))
178
+ end
179
+
121
180
  describe "position" do
122
181
  it "works for an empty item" do
123
182
  new_item = @item.add_item("Test")
@@ -211,7 +270,8 @@ describe HexaPDF::Type::OutlineItem do
211
270
  end
212
271
  item1.add_item("Item5")
213
272
  end
214
- assert_equal(%w[Item1 Item2 Item3 Item4 Item5], @item.each_item.map(&:title))
273
+ assert_equal(['Item1', 1, 'Item2', 2, 'Item3', 2, 'Item4', 3, 'Item5', 2],
274
+ @item.each_item.map {|i, l| [i.title, l] }.flatten)
215
275
  end
216
276
 
217
277
  describe "perform_validation" do
@@ -251,6 +311,7 @@ describe HexaPDF::Type::OutlineItem do
251
311
  assert(correctable)
252
312
  end
253
313
  refute(@item.key?(:Count))
314
+ assert(@item.validate(auto_correct: false))
254
315
  end
255
316
 
256
317
  it "fails validation if the previous item's /Next points somewhere else" do
@@ -94,6 +94,11 @@ describe HexaPDF::Type::Page do
94
94
  @page = @doc.pages.add
95
95
  end
96
96
 
97
+ it "returns the crop box by default" do
98
+ @page[:CropBox] = [0, 0, 100, 200]
99
+ assert_equal([0, 0, 100, 200], @page.box)
100
+ end
101
+
97
102
  it "returns the correct media box" do
98
103
  @page[:MediaBox] = :media
99
104
  assert_equal(:media, @page.box(:media))
@@ -150,15 +155,25 @@ describe HexaPDF::Type::Page do
150
155
  @page = @doc.pages.add
151
156
  end
152
157
 
153
- it "returns :portrait for appropriate media boxes and rotation values" do
154
- @page.box(:media, [0, 0, 100, 300])
158
+ it "uses the crop box by default" do
159
+ @page[:CropBox] = [0, 0, 200, 100]
160
+ assert_equal(:landscape, @page.orientation)
161
+ end
162
+
163
+ it "uses the specified box for determining the orientation" do
164
+ @page[:ArtBox] = [0, 0, 200, 100]
165
+ assert_equal(:landscape, @page.orientation(:art))
166
+ end
167
+
168
+ it "returns :portrait for appropriate boxes and rotation values" do
169
+ @page.box(:crop, [0, 0, 100, 300])
155
170
  assert_equal(:portrait, @page.orientation)
156
171
  @page[:Rotate] = 0
157
172
  assert_equal(:portrait, @page.orientation)
158
173
  @page[:Rotate] = 180
159
174
  assert_equal(:portrait, @page.orientation)
160
175
 
161
- @page.box(:media, [0, 0, 300, 100])
176
+ @page.box(:crop, [0, 0, 300, 100])
162
177
  @page[:Rotate] = 90
163
178
  assert_equal(:portrait, @page.orientation)
164
179
  @page[:Rotate] = 270
@@ -166,14 +181,14 @@ describe HexaPDF::Type::Page do
166
181
  end
167
182
 
168
183
  it "returns :landscape for appropriate media boxes and rotation values" do
169
- @page.box(:media, [0, 0, 300, 100])
184
+ @page.box(:crop, [0, 0, 300, 100])
170
185
  assert_equal(:landscape, @page.orientation)
171
186
  @page[:Rotate] = 0
172
187
  assert_equal(:landscape, @page.orientation)
173
188
  @page[:Rotate] = 180
174
189
  assert_equal(:landscape, @page.orientation)
175
190
 
176
- @page.box(:media, [0, 0, 100, 300])
191
+ @page.box(:crop, [0, 0, 100, 300])
177
192
  @page[:Rotate] = 90
178
193
  assert_equal(:landscape, @page.orientation)
179
194
  @page[:Rotate] = 270
@@ -204,18 +219,17 @@ describe HexaPDF::Type::Page do
204
219
 
205
220
  describe "flatten" do
206
221
  it "adjust all page boxes" do
207
- @page.box(:crop, @page.box)
208
- @page.box(:bleed, @page.box)
209
- @page.box(:trim, @page.box)
210
- @page.box(:art, @page.box)
222
+ @page.box(:crop, [0, 0, 1, 2])
223
+ @page.box(:bleed, [0, 0, 2, 3])
224
+ @page.box(:trim, [0, 0, 3, 4])
225
+ @page.box(:art, [0, 0, 4, 5])
211
226
 
212
227
  @page.rotate(90, flatten: true)
213
- box = [-300, 50, -100, 200]
214
- assert_equal(box, @page.box(:media).value)
215
- assert_equal(box, @page.box(:crop).value)
216
- assert_equal(box, @page.box(:bleed).value)
217
- assert_equal(box, @page.box(:trim).value)
218
- assert_equal(box, @page.box(:art).value)
228
+ assert_equal([-300, 50, -100, 200], @page.box(:media).value)
229
+ assert_equal([-2, 0, 0, 1], @page.box(:crop).value)
230
+ assert_equal([-3, 0, 0, 2], @page.box(:bleed).value)
231
+ assert_equal([-4, 0, 0, 3], @page.box(:trim).value)
232
+ assert_equal([-5, 0, 0, 4], @page.box(:art).value)
219
233
  end
220
234
 
221
235
  it "works correctly for 90 degrees" do
@@ -405,8 +419,8 @@ describe HexaPDF::Type::Page do
405
419
  [:restore_graphics_state]])
406
420
  end
407
421
 
408
- it "works correctly if the page has its origin not at (0,0)" do
409
- @page.box(:media, [-10, -5, 100, 300])
422
+ it "works correctly if the page has its crop box origin not at (0,0)" do
423
+ @page.box(:crop, [-10, -5, 100, 300])
410
424
  @page.canvas(type: :underlay).line_width = 2
411
425
  @page.canvas(type: :page).line_width = 2
412
426
  @page.canvas(type: :overlay).line_width = 2
@@ -427,6 +441,13 @@ describe HexaPDF::Type::Page do
427
441
  [:restore_graphics_state]])
428
442
  end
429
443
 
444
+ it "allows disabling the origin translation" do
445
+ @page.box(:crop, [-10, -5, 100, 300])
446
+ @page.canvas(translate_origin: false).line_width = 2
447
+
448
+ assert_page_operators(@page, [[:set_line_width, [2]]])
449
+ end
450
+
430
451
  it "fails if the page canvas is requested for a page with existing contents" do
431
452
  @page.contents = "q Q"
432
453
  assert_raises(HexaPDF::Error) { @page.canvas }
@@ -554,10 +575,10 @@ describe HexaPDF::Type::Page do
554
575
  [:restore_graphics_state]])
555
576
  end
556
577
 
557
- it "adjusts the position to counter the translation in #canvas based on the page's media box" do
558
- @page[:MediaBox] = [-15, -15, 100, 100]
578
+ it "potentially adjusts the origin so that it is always in (0,0)" do
579
+ @canvas.translate(-15, -15)
559
580
  @page.flatten_annotations
560
- assert_operators(@canvas.contents, [:concatenate_matrix, [1, 0, 0, 1, 15, 15]], range: 1)
581
+ assert_operators(@canvas.contents, [:concatenate_matrix, [1, 0, 0, 1, 15, 15]], range: 2)
561
582
  end
562
583
 
563
584
  it "adjusts the position in case the form /Matrix has an offset" do
@@ -189,11 +189,6 @@ describe HexaPDF::Type::Resources do
189
189
  end
190
190
 
191
191
  describe "validation" do
192
- it "assigns the default value if ProcSet is not set" do
193
- @res.validate
194
- assert_equal([:PDF, :Text, :ImageB, :ImageC, :ImageI], @res[:ProcSet].value)
195
- end
196
-
197
192
  it "handles an invalid ProcSet containing a single value instead of an array" do
198
193
  @res[:ProcSet] = :PDF
199
194
  @res.validate
@@ -128,4 +128,12 @@ describe HexaPDF::Type::Signature do
128
128
  assert_kind_of(OpenSSL::X509::Store, store)
129
129
  assert(kwargs[:allow_self_signed])
130
130
  end
131
+
132
+ describe "perform_validation" do
133
+ it "upgrades the version to 2.0 if the /SubFilter needs it" do
134
+ refute(@sig.validate(auto_correct: false))
135
+ assert(@sig.validate(auto_correct: true))
136
+ assert_equal('2.0', @doc.version)
137
+ end
138
+ end
131
139
  end
data/test/test_helper.rb CHANGED
@@ -3,7 +3,7 @@
3
3
  begin
4
4
  require 'simplecov'
5
5
  SimpleCov.start do
6
- minimum_coverage line: 100
6
+ minimum_coverage line: 100 unless ENV['NO_SIMPLECOV']
7
7
  add_filter '/test/'
8
8
  add_filter '/fast_arc4.rb'
9
9
  end
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.26.2
4
+ version: 0.27.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Thomas Leitner
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-10-21 00:00:00.000000000 Z
11
+ date: 2022-11-18 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: cmdparse
@@ -44,6 +44,20 @@ dependencies:
44
44
  - - "~>"
45
45
  - !ruby/object:Gem::Version
46
46
  version: '0.3'
47
+ - !ruby/object:Gem::Dependency
48
+ name: openssl
49
+ requirement: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - ">="
52
+ - !ruby/object:Gem::Version
53
+ version: 2.2.1
54
+ type: :runtime
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - ">="
59
+ - !ruby/object:Gem::Version
60
+ version: 2.2.1
47
61
  - !ruby/object:Gem::Dependency
48
62
  name: kramdown
49
63
  requirement: !ruby/object:Gem::Requirement
@@ -699,7 +713,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
699
713
  requirements:
700
714
  - - ">="
701
715
  - !ruby/object:Gem::Version
702
- version: '2.5'
716
+ version: '2.6'
703
717
  required_rubygems_version: !ruby/object:Gem::Requirement
704
718
  requirements:
705
719
  - - ">="