hexapdf 0.26.2 → 0.27.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +56 -0
- data/README.md +1 -1
- data/examples/013-text_layouter_shapes.rb +8 -8
- data/examples/016-frame_automatic_box_placement.rb +3 -3
- data/examples/017-frame_text_flow.rb +3 -3
- data/examples/020-column_box.rb +3 -3
- data/lib/hexapdf/cli/split.rb +7 -7
- data/lib/hexapdf/cli/watermark.rb +2 -2
- data/lib/hexapdf/configuration.rb +2 -0
- data/lib/hexapdf/dictionary.rb +3 -12
- data/lib/hexapdf/document/destinations.rb +42 -5
- data/lib/hexapdf/document/signatures.rb +265 -48
- data/lib/hexapdf/importer.rb +3 -0
- data/lib/hexapdf/parser.rb +1 -0
- data/lib/hexapdf/revisions.rb +3 -1
- data/lib/hexapdf/tokenizer.rb +2 -2
- data/lib/hexapdf/type/acro_form/form.rb +28 -1
- data/lib/hexapdf/type/catalog.rb +1 -1
- data/lib/hexapdf/type/outline.rb +18 -0
- data/lib/hexapdf/type/outline_item.rb +72 -14
- data/lib/hexapdf/type/page.rb +56 -35
- data/lib/hexapdf/type/resources.rb +13 -17
- data/lib/hexapdf/type/signature/adbe_pkcs7_detached.rb +16 -2
- data/lib/hexapdf/type/signature.rb +10 -0
- data/lib/hexapdf/version.rb +1 -1
- data/lib/hexapdf/writer.rb +3 -0
- data/test/hexapdf/document/test_destinations.rb +41 -0
- data/test/hexapdf/document/test_signatures.rb +139 -19
- data/test/hexapdf/test_importer.rb +14 -0
- data/test/hexapdf/test_parser.rb +2 -2
- data/test/hexapdf/test_revisions.rb +20 -12
- data/test/hexapdf/test_tokenizer.rb +11 -1
- data/test/hexapdf/test_writer.rb +11 -3
- data/test/hexapdf/type/acro_form/test_form.rb +47 -0
- data/test/hexapdf/type/signature/common.rb +52 -0
- data/test/hexapdf/type/signature/test_adbe_pkcs7_detached.rb +21 -0
- data/test/hexapdf/type/test_catalog.rb +5 -2
- data/test/hexapdf/type/test_outline.rb +1 -1
- data/test/hexapdf/type/test_outline_item.rb +62 -1
- data/test/hexapdf/type/test_page.rb +41 -20
- data/test/hexapdf/type/test_resources.rb +0 -5
- data/test/hexapdf/type/test_signature.rb +8 -0
- data/test/test_helper.rb +1 -1
- 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)
|
data/test/hexapdf/test_parser.rb
CHANGED
@@ -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
|
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
|
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
|
291
|
+
<< /Type /XRef /Size 4 /Index [2 1] /W [1 1 1] /Filter /ASCIIHexDecode /Length 6
|
283
292
|
>>stream
|
284
|
-
|
293
|
+
017600
|
285
294
|
endstream
|
286
295
|
endobj
|
287
296
|
|
288
297
|
xref
|
289
|
-
|
290
|
-
0000000000 65535 f
|
291
|
-
0000000009 00000 n
|
298
|
+
2 2
|
292
299
|
0000000000 65535 f
|
293
|
-
|
300
|
+
0000000137 00000 n
|
294
301
|
trailer
|
295
|
-
<< /Size
|
302
|
+
<< /Size 4 /Prev 27>>
|
296
303
|
startxref
|
297
|
-
|
304
|
+
260
|
298
305
|
%%EOF
|
299
306
|
|
300
307
|
xref
|
301
308
|
0 0
|
302
309
|
trailer
|
303
|
-
<< /Size
|
310
|
+
<< /Size 4 /Prev 260 /XRefStm 137>>
|
304
311
|
startxref
|
305
|
-
|
312
|
+
360
|
306
313
|
%%EOF
|
307
314
|
EOF
|
308
|
-
doc = HexaPDF::Document.new(io: io)
|
309
|
-
assert_equal(
|
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
|
-
|
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)
|
data/test/hexapdf/test_writer.rb
CHANGED
@@ -40,7 +40,7 @@ describe HexaPDF::Writer do
|
|
40
40
|
219
|
41
41
|
%%EOF
|
42
42
|
3 0 obj
|
43
|
-
<</Producer(HexaPDF version 0.
|
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.
|
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.
|
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
|
-
|
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
|
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(
|
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 "
|
154
|
-
@page
|
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(:
|
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(:
|
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(:
|
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,
|
208
|
-
@page.box(:bleed,
|
209
|
-
@page.box(:trim,
|
210
|
-
@page.box(:art,
|
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
|
-
|
214
|
-
assert_equal(
|
215
|
-
assert_equal(
|
216
|
-
assert_equal(
|
217
|
-
assert_equal(
|
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(:
|
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
|
558
|
-
@
|
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:
|
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
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.
|
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-
|
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.
|
716
|
+
version: '2.6'
|
703
717
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
704
718
|
requirements:
|
705
719
|
- - ">="
|