hexapdf 0.26.2 → 0.27.0

Sign up to get free protection for your applications and to get access to all the features.
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
  - - ">="