hexapdf 0.27.0 → 0.29.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +100 -11
- data/examples/019-acro_form.rb +14 -3
- data/examples/023-images.rb +30 -0
- data/examples/024-digital-signatures.rb +23 -0
- data/lib/hexapdf/cli/info.rb +5 -1
- data/lib/hexapdf/cli/inspect.rb +2 -2
- data/lib/hexapdf/cli/split.rb +2 -2
- data/lib/hexapdf/configuration.rb +13 -14
- data/lib/hexapdf/content/canvas.rb +8 -3
- data/lib/hexapdf/dictionary.rb +1 -5
- data/lib/hexapdf/dictionary_fields.rb +6 -2
- data/lib/hexapdf/digital_signature/cms_handler.rb +137 -0
- data/lib/hexapdf/digital_signature/handler.rb +138 -0
- data/lib/hexapdf/digital_signature/pkcs1_handler.rb +96 -0
- data/lib/hexapdf/{type → digital_signature}/signature.rb +3 -8
- data/lib/hexapdf/digital_signature/signatures.rb +210 -0
- data/lib/hexapdf/digital_signature/signing/default_handler.rb +317 -0
- data/lib/hexapdf/digital_signature/signing/signed_data_creator.rb +308 -0
- data/lib/hexapdf/digital_signature/signing/timestamp_handler.rb +148 -0
- data/lib/hexapdf/digital_signature/signing.rb +101 -0
- data/lib/hexapdf/{type/signature → digital_signature}/verification_result.rb +37 -41
- data/lib/hexapdf/digital_signature.rb +56 -0
- data/lib/hexapdf/document.rb +27 -24
- data/lib/hexapdf/encryption/standard_security_handler.rb +2 -1
- data/lib/hexapdf/filter/ascii85_decode.rb +1 -1
- data/lib/hexapdf/importer.rb +32 -27
- data/lib/hexapdf/layout/list_box.rb +1 -5
- data/lib/hexapdf/object.rb +5 -0
- data/lib/hexapdf/parser.rb +13 -0
- data/lib/hexapdf/revision.rb +15 -12
- data/lib/hexapdf/revisions.rb +4 -0
- data/lib/hexapdf/tokenizer.rb +14 -8
- data/lib/hexapdf/type/acro_form/appearance_generator.rb +174 -128
- data/lib/hexapdf/type/acro_form/button_field.rb +5 -3
- data/lib/hexapdf/type/acro_form/choice_field.rb +2 -0
- data/lib/hexapdf/type/acro_form/field.rb +11 -5
- data/lib/hexapdf/type/acro_form/form.rb +33 -7
- data/lib/hexapdf/type/acro_form/signature_field.rb +2 -0
- data/lib/hexapdf/type/acro_form/text_field.rb +12 -2
- data/lib/hexapdf/type/annotations/widget.rb +3 -0
- data/lib/hexapdf/type/font_true_type.rb +14 -0
- data/lib/hexapdf/type/object_stream.rb +2 -2
- data/lib/hexapdf/type/outline.rb +1 -1
- data/lib/hexapdf/type/page.rb +56 -46
- data/lib/hexapdf/type.rb +0 -1
- data/lib/hexapdf/version.rb +1 -1
- data/lib/hexapdf/writer.rb +2 -3
- data/test/hexapdf/content/test_canvas.rb +5 -0
- data/test/hexapdf/{type/signature → digital_signature}/common.rb +34 -4
- data/test/hexapdf/digital_signature/signing/test_default_handler.rb +162 -0
- data/test/hexapdf/digital_signature/signing/test_signed_data_creator.rb +225 -0
- data/test/hexapdf/digital_signature/signing/test_timestamp_handler.rb +88 -0
- data/test/hexapdf/{type/signature/test_adbe_pkcs7_detached.rb → digital_signature/test_cms_handler.rb} +7 -7
- data/test/hexapdf/{type/signature → digital_signature}/test_handler.rb +4 -4
- data/test/hexapdf/{type/signature/test_adbe_x509_rsa_sha1.rb → digital_signature/test_pkcs1_handler.rb} +3 -3
- data/test/hexapdf/{type → digital_signature}/test_signature.rb +7 -7
- data/test/hexapdf/digital_signature/test_signatures.rb +137 -0
- data/test/hexapdf/digital_signature/test_signing.rb +53 -0
- data/test/hexapdf/{type/signature → digital_signature}/test_verification_result.rb +7 -7
- data/test/hexapdf/document/test_pages.rb +2 -2
- data/test/hexapdf/encryption/test_aes.rb +1 -1
- data/test/hexapdf/filter/test_predictor.rb +0 -1
- data/test/hexapdf/layout/test_box.rb +2 -1
- data/test/hexapdf/layout/test_column_box.rb +1 -1
- data/test/hexapdf/layout/test_list_box.rb +1 -1
- data/test/hexapdf/test_dictionary_fields.rb +2 -1
- data/test/hexapdf/test_document.rb +3 -9
- data/test/hexapdf/test_importer.rb +13 -6
- data/test/hexapdf/test_parser.rb +17 -0
- data/test/hexapdf/test_revision.rb +15 -14
- data/test/hexapdf/test_revisions.rb +43 -0
- data/test/hexapdf/test_stream.rb +1 -1
- data/test/hexapdf/test_tokenizer.rb +3 -4
- data/test/hexapdf/test_writer.rb +3 -3
- data/test/hexapdf/type/acro_form/test_appearance_generator.rb +135 -56
- data/test/hexapdf/type/acro_form/test_button_field.rb +6 -1
- data/test/hexapdf/type/acro_form/test_choice_field.rb +4 -0
- data/test/hexapdf/type/acro_form/test_field.rb +4 -4
- data/test/hexapdf/type/acro_form/test_form.rb +18 -0
- data/test/hexapdf/type/acro_form/test_signature_field.rb +4 -0
- data/test/hexapdf/type/acro_form/test_text_field.rb +13 -0
- data/test/hexapdf/type/test_font_true_type.rb +20 -0
- data/test/hexapdf/type/test_object_stream.rb +2 -1
- data/test/hexapdf/type/test_outline.rb +3 -0
- data/test/hexapdf/type/test_page.rb +67 -30
- data/test/hexapdf/type/test_page_tree_node.rb +4 -2
- metadata +69 -16
- data/lib/hexapdf/document/signatures.rb +0 -546
- data/lib/hexapdf/type/signature/adbe_pkcs7_detached.rb +0 -135
- data/lib/hexapdf/type/signature/adbe_x509_rsa_sha1.rb +0 -95
- data/lib/hexapdf/type/signature/handler.rb +0 -140
- data/test/hexapdf/document/test_signatures.rb +0 -352
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
|
2
|
+
|
|
3
|
+
require 'test_helper'
|
|
4
|
+
require 'stringio'
|
|
5
|
+
require 'tempfile'
|
|
6
|
+
require 'hexapdf/document'
|
|
7
|
+
require_relative 'common'
|
|
8
|
+
|
|
9
|
+
describe HexaPDF::DigitalSignature::Signatures do
|
|
10
|
+
before do
|
|
11
|
+
@doc = HexaPDF::Document.new
|
|
12
|
+
@form = @doc.acro_form(create: true)
|
|
13
|
+
@sig1 = @form.create_signature_field("test1")
|
|
14
|
+
@sig2 = @form.create_signature_field("test2")
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
it "iterates over all signature dictionaries" do
|
|
18
|
+
assert_equal([], @doc.signatures.to_a)
|
|
19
|
+
@sig1.field_value = :sig1
|
|
20
|
+
@sig2.field_value = :sig2
|
|
21
|
+
assert_equal([:sig1, :sig2], @doc.signatures.to_a)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
it "returns the number of signature dictionaries" do
|
|
25
|
+
@sig1.field_value = :sig1
|
|
26
|
+
assert_equal(1, @doc.signatures.count)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
describe "signing_handler" do
|
|
30
|
+
it "return the initialized handler" do
|
|
31
|
+
handler = @doc.signatures.signing_handler(certificate: 'cert', reason: 'reason')
|
|
32
|
+
assert_equal('cert', handler.certificate)
|
|
33
|
+
assert_equal('reason', handler.reason)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
it "fails if the given task is not available" do
|
|
37
|
+
assert_raises(HexaPDF::Error) { @doc.signatures.signing_handler(name: :unknown) }
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
describe "add" do
|
|
42
|
+
before do
|
|
43
|
+
@doc = HexaPDF::Document.new(io: StringIO.new(MINIMAL_PDF))
|
|
44
|
+
@io = StringIO.new(''.b)
|
|
45
|
+
@handler = @doc.signatures.signing_handler(
|
|
46
|
+
certificate: CERTIFICATES.signer_certificate,
|
|
47
|
+
key: CERTIFICATES.signer_key,
|
|
48
|
+
certificate_chain: [CERTIFICATES.ca_certificate]
|
|
49
|
+
)
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
it "uses the provided signature dictionary" do
|
|
53
|
+
sig = @doc.add({Type: :Sig, Key: :value})
|
|
54
|
+
@doc.signatures.add(@io, @handler, signature: sig)
|
|
55
|
+
assert_equal(1, @doc.signatures.to_a.compact.size)
|
|
56
|
+
assert_equal(:value, @doc.signatures.to_a[0][:Key])
|
|
57
|
+
refute_equal(:value, @doc.acro_form.each_field.first[:Key])
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
it "creates the signature dictionary if none is provided" do
|
|
61
|
+
@doc.signatures.add(@io, @handler)
|
|
62
|
+
assert_equal(1, @doc.signatures.to_a.compact.size)
|
|
63
|
+
refute(@doc.acro_form.each_field.first.key?(:Contents))
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
it "sets the needed information on the signature dictionary" do
|
|
67
|
+
def @handler.finalize_objects(sigfield, sig)
|
|
68
|
+
sig[:key] = :sig
|
|
69
|
+
sigfield[:key] = :sig_field
|
|
70
|
+
end
|
|
71
|
+
@doc.signatures.add(@io, @handler, write_options: {update_fields: false})
|
|
72
|
+
sig = @doc.signatures.first
|
|
73
|
+
assert_equal([0, 925, 925 + sig[:Contents].size * 2 + 2, 2501], sig[:ByteRange].value)
|
|
74
|
+
assert_equal(:sig, sig[:key])
|
|
75
|
+
assert_equal(:sig_field, @doc.acro_form.each_field.first[:key])
|
|
76
|
+
assert(sig.key?(:Contents))
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
it "creates the main form dictionary if necessary" do
|
|
80
|
+
@doc.signatures.add(@io, @handler)
|
|
81
|
+
assert(@doc.acro_form)
|
|
82
|
+
assert_equal([:signatures_exist, :append_only], @doc.acro_form.signature_flags)
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
it "uses the provided signature field" do
|
|
86
|
+
field = @doc.acro_form(create: true).create_signature_field('Signature2')
|
|
87
|
+
@doc.signatures.add(@io, @handler, signature: field)
|
|
88
|
+
assert_nil(@doc.acro_form.field_by_name("Signature3"))
|
|
89
|
+
refute_nil(field.field_value)
|
|
90
|
+
assert_nil(@doc.signatures.first[:T])
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
it "uses an existing signature field if possible" do
|
|
94
|
+
field = @doc.acro_form(create: true).create_signature_field('Signature2')
|
|
95
|
+
field.field_value = sig = @doc.add({Type: :Sig, key: :value})
|
|
96
|
+
@doc.signatures.add(@io, @handler, signature: sig)
|
|
97
|
+
assert_nil(@doc.acro_form.field_by_name("Signature3"))
|
|
98
|
+
assert_same(sig, @doc.signatures.first)
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
it "creates the signature field if necessary" do
|
|
102
|
+
@doc.acro_form(create: true).create_text_field('Signature2')
|
|
103
|
+
@doc.signatures.add(@io, @handler)
|
|
104
|
+
field = @doc.acro_form.field_by_name("Signature3")
|
|
105
|
+
assert_equal(:Sig, field.field_type)
|
|
106
|
+
refute_nil(field.field_value)
|
|
107
|
+
assert_equal(1, field.each_widget.count)
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
it "handles different xref section types correctly when determing the offsets" do
|
|
111
|
+
@doc.delete(7)
|
|
112
|
+
sig = @doc.signatures.add(@io, @handler, write_options: {update_fields: false})
|
|
113
|
+
assert_equal([0, 1036, 1036 + sig[:Contents].size * 2 + 2, 2483], sig[:ByteRange].value)
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
it "works if the signature object is the last object of the xref section" do
|
|
117
|
+
field = @doc.acro_form(create: true).create_signature_field('Signature2')
|
|
118
|
+
field.create_widget(@doc.pages[0], Rect: [0, 0, 0, 0])
|
|
119
|
+
sig = @doc.signatures.add(@io, @handler, signature: field, write_options: {update_fields: false})
|
|
120
|
+
assert_equal([0, 3143, 3143 + sig[:Contents].size * 2 + 2, 380], sig[:ByteRange].value)
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
it "allows writing to a file in addition to writing to an IO" do
|
|
124
|
+
tempfile = Tempfile.new('hexapdf-signature')
|
|
125
|
+
tempfile.close
|
|
126
|
+
@doc.signatures.add(tempfile.path, @handler)
|
|
127
|
+
doc = HexaPDF::Document.open(tempfile.path)
|
|
128
|
+
assert(doc.signatures.first.verify(allow_self_signed: true).success?)
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
it "adds a new revision with the signature" do
|
|
132
|
+
@doc.signatures.add(@io, @handler)
|
|
133
|
+
signed_doc = HexaPDF::Document.new(io: @io)
|
|
134
|
+
assert(signed_doc.signatures.first.verify)
|
|
135
|
+
end
|
|
136
|
+
end
|
|
137
|
+
end
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
|
2
|
+
|
|
3
|
+
require 'test_helper'
|
|
4
|
+
require 'hexapdf/document'
|
|
5
|
+
require 'hexapdf/digital_signature'
|
|
6
|
+
require_relative 'common'
|
|
7
|
+
|
|
8
|
+
describe HexaPDF::DigitalSignature::Signing do
|
|
9
|
+
before do
|
|
10
|
+
@handler = HexaPDF::DigitalSignature::Signing::DefaultHandler.new(
|
|
11
|
+
certificate: CERTIFICATES.signer_certificate,
|
|
12
|
+
key: CERTIFICATES.signer_key,
|
|
13
|
+
certificate_chain: [CERTIFICATES.ca_certificate]
|
|
14
|
+
)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
it "allows embedding an external signature value" do
|
|
18
|
+
# Create first signature normally for testing the signature-finding code later
|
|
19
|
+
doc = HexaPDF::Document.new(io: StringIO.new(MINIMAL_PDF))
|
|
20
|
+
io = StringIO.new(''.b)
|
|
21
|
+
doc.signatures.add(io, @handler)
|
|
22
|
+
doc = HexaPDF::Document.new(io: io)
|
|
23
|
+
io = StringIO.new(''.b)
|
|
24
|
+
|
|
25
|
+
byte_range = nil
|
|
26
|
+
@handler.signature_size = 5000
|
|
27
|
+
@handler.certificate = nil
|
|
28
|
+
@handler.external_signing = proc {|_, br| byte_range = br; "" }
|
|
29
|
+
doc.signatures.add(io, @handler)
|
|
30
|
+
|
|
31
|
+
io.pos = byte_range[0]
|
|
32
|
+
data = io.read(byte_range[1])
|
|
33
|
+
io.pos = byte_range[2]
|
|
34
|
+
data << io.read(byte_range[3])
|
|
35
|
+
contents = OpenSSL::PKCS7.sign(CERTIFICATES.signer_certificate, @handler.key, data,
|
|
36
|
+
@handler.certificate_chain,
|
|
37
|
+
OpenSSL::PKCS7::DETACHED | OpenSSL::PKCS7::BINARY).to_der
|
|
38
|
+
HexaPDF::DigitalSignature::Signing.embed_signature(io, contents)
|
|
39
|
+
doc = HexaPDF::Document.new(io: io)
|
|
40
|
+
assert_equal(2, doc.signatures.each.count)
|
|
41
|
+
doc.signatures.each do |signature|
|
|
42
|
+
assert(signature.verify(allow_self_signed: true).messages.find {|m| m.content == 'Signature valid' })
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
it "fails if the reserved signature space is too small" do
|
|
47
|
+
doc = HexaPDF::Document.new(io: StringIO.new(MINIMAL_PDF))
|
|
48
|
+
io = StringIO.new(''.b)
|
|
49
|
+
def @handler.signature_size; 200; end
|
|
50
|
+
msg = assert_raises(HexaPDF::Error) { doc.signatures.add(io, @handler) }
|
|
51
|
+
assert_match(/space.*too small.*200 vs/, msg.message)
|
|
52
|
+
end
|
|
53
|
+
end
|
|
@@ -1,26 +1,26 @@
|
|
|
1
1
|
# -*- encoding: utf-8 -*-
|
|
2
2
|
|
|
3
3
|
require 'test_helper'
|
|
4
|
-
require 'hexapdf/
|
|
4
|
+
require 'hexapdf/digital_signature'
|
|
5
5
|
|
|
6
|
-
describe HexaPDF::
|
|
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::
|
|
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::
|
|
16
|
-
warning = HexaPDF::
|
|
17
|
-
error = HexaPDF::
|
|
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::
|
|
23
|
+
@result = HexaPDF::DigitalSignature::VerificationResult.new
|
|
24
24
|
end
|
|
25
25
|
|
|
26
26
|
it "can add new messages" do
|
|
@@ -166,7 +166,7 @@ describe HexaPDF::Document::Pages do
|
|
|
166
166
|
it "works for a single page label entry" do
|
|
167
167
|
@doc.catalog[:PageLabels] = {Nums: [0, {S: :r}]}
|
|
168
168
|
result = @doc.pages.each_labelling_range.to_a
|
|
169
|
-
assert_equal([[0, 10, {S: :r}]], result.map {|s, c, l| [s, c, l.value]})
|
|
169
|
+
assert_equal([[0, 10, {S: :r}]], result.map {|s, c, l| [s, c, l.value] })
|
|
170
170
|
assert_equal(:lowercase_roman, result[0].last.numbering_style)
|
|
171
171
|
end
|
|
172
172
|
|
|
@@ -174,7 +174,7 @@ describe HexaPDF::Document::Pages do
|
|
|
174
174
|
@doc.catalog[:PageLabels] = {Nums: [0, {S: :r}, 2, {S: :d}, 7, {S: :A}]}
|
|
175
175
|
result = @doc.pages.each_labelling_range.to_a
|
|
176
176
|
assert_equal([[0, 2, {S: :r}], [2, 5, {S: :d}], [7, 3, {S: :A}]],
|
|
177
|
-
result.map {|s, c, l| [s, c, l.value]})
|
|
177
|
+
result.map {|s, c, l| [s, c, l.value] })
|
|
178
178
|
end
|
|
179
179
|
|
|
180
180
|
it "returns a zero or negative count for the last range if there aren't enough pages" do
|
|
@@ -122,7 +122,7 @@ describe HexaPDF::Encryption::AES do
|
|
|
122
122
|
[4, 20, 40].each do |length|
|
|
123
123
|
assert_raises(HexaPDF::EncryptionError) do
|
|
124
124
|
collector(@algorithm_class.decryption_fiber('some' * 4,
|
|
125
|
-
|
|
125
|
+
Fiber.new { 'a' * length }))
|
|
126
126
|
end
|
|
127
127
|
end
|
|
128
128
|
end
|
|
@@ -131,7 +131,8 @@ describe HexaPDF::Layout::Box do
|
|
|
131
131
|
assert_equal([nil, box], box.split(150, 150, nil))
|
|
132
132
|
end
|
|
133
133
|
|
|
134
|
-
it "can't be split if it doesn't (completely) fit as the default implementation
|
|
134
|
+
it "can't be split if it doesn't (completely) fit as the default implementation " \
|
|
135
|
+
"knows nothing about the content" do
|
|
135
136
|
@box.style.position = :flow # make sure we would generally be splitable
|
|
136
137
|
@box.fit(90, 100, nil)
|
|
137
138
|
assert_equal([nil, @box], @box.split(150, 150, nil))
|
|
@@ -11,7 +11,7 @@ describe HexaPDF::Layout::ColumnBox do
|
|
|
11
11
|
@text_boxes = 5.times.map do
|
|
12
12
|
HexaPDF::Layout::TextBox.new(items: [inline_box] * 15, style: {position: :default})
|
|
13
13
|
end
|
|
14
|
-
draw_block = lambda do |canvas,
|
|
14
|
+
draw_block = lambda do |canvas, _box|
|
|
15
15
|
canvas.move_to(0, 0).end_path
|
|
16
16
|
end
|
|
17
17
|
@fixed_size_boxes = 15.times.map { HexaPDF::Layout::Box.new(width: 20, height: 10, &draw_block) }
|
|
@@ -222,7 +222,7 @@ describe HexaPDF::Layout::ListBox do
|
|
|
222
222
|
end
|
|
223
223
|
|
|
224
224
|
it "allows drawing custom markers" do
|
|
225
|
-
marker = lambda do |
|
|
225
|
+
marker = lambda do |_doc, _list_box, _index|
|
|
226
226
|
HexaPDF::Layout::Box.create(width: 10, height: 10) {}
|
|
227
227
|
end
|
|
228
228
|
box = create_box(children: @fixed_size_boxes[0, 1], item_type: marker)
|
|
@@ -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"]],
|
|
@@ -191,6 +190,8 @@ describe HexaPDF::DictionaryFields do
|
|
|
191
190
|
["D:1998-08'00'", [1998, 01, 01, 00, 00, 00, "-08:00"]],
|
|
192
191
|
["D:19981223195210-08", [1998, 12, 23, 19, 52, 10, "-08:00"]], # non-standard, missing '
|
|
193
192
|
["D:19981223195210-08'00", [1998, 12, 23, 19, 52, 10, "-08:00"]], # non-standard, missing '
|
|
193
|
+
["D:19981223195210-54'00", [1998, 12, 23, 19, 52, 10, "-23:59:59"]], # non-standard, TZ hour to large
|
|
194
|
+
["D:19981223195210+10'65", [1998, 12, 23, 19, 52, 10, "+11:05"]], # non-standard, TZ min to large
|
|
194
195
|
].each do |str, data|
|
|
195
196
|
obj = @field.convert(str, self)
|
|
196
197
|
assert_equal(Time.new(*data), obj, "date str used: #{str}")
|
|
@@ -163,14 +163,8 @@ describe HexaPDF::Document do
|
|
|
163
163
|
refute_equal(0, obj.oid)
|
|
164
164
|
end
|
|
165
165
|
|
|
166
|
-
it "
|
|
167
|
-
|
|
168
|
-
end
|
|
169
|
-
|
|
170
|
-
it "fails if the given object is associated with no or the destination document" do
|
|
171
|
-
assert_raises(ArgumentError) { @doc.import(HexaPDF::Object.new(5)) }
|
|
172
|
-
obj = @doc.add(5)
|
|
173
|
-
assert_raises(ArgumentError) { @doc.import(obj) }
|
|
166
|
+
it "works if the given object is not a PDF object" do
|
|
167
|
+
assert_equal(5, @doc.import(5))
|
|
174
168
|
end
|
|
175
169
|
end
|
|
176
170
|
|
|
@@ -510,7 +504,7 @@ describe HexaPDF::Document do
|
|
|
510
504
|
|
|
511
505
|
it "allows to conveniently sign a document" do
|
|
512
506
|
mock = Minitest::Mock.new
|
|
513
|
-
mock.expect(:
|
|
507
|
+
mock.expect(:signing_handler, :handler, name: :handler, opt: :key)
|
|
514
508
|
mock.expect(:add, :added, [:io, :handler], signature: :sig, write_options: :write_options)
|
|
515
509
|
@doc.instance_variable_set(:@signatures, mock)
|
|
516
510
|
result = @doc.sign(:io, handler: :handler, write_options: :write_options, signature: :sig, opt: :key)
|
|
@@ -31,12 +31,12 @@ describe HexaPDF::Importer do
|
|
|
31
31
|
@source.pages.add
|
|
32
32
|
@source.pages.root[:Rotate] = 90
|
|
33
33
|
@dest = HexaPDF::Document.new
|
|
34
|
-
@importer = HexaPDF::Importer.for(
|
|
34
|
+
@importer = HexaPDF::Importer.for(@dest)
|
|
35
35
|
end
|
|
36
36
|
|
|
37
37
|
describe "::for" do
|
|
38
38
|
it "caches the importer" do
|
|
39
|
-
assert_same(@importer, HexaPDF::Importer.for(
|
|
39
|
+
assert_same(@importer, HexaPDF::Importer.for(@dest))
|
|
40
40
|
end
|
|
41
41
|
end
|
|
42
42
|
|
|
@@ -61,8 +61,14 @@ describe HexaPDF::Importer do
|
|
|
61
61
|
end
|
|
62
62
|
|
|
63
63
|
it "can import a direct object" do
|
|
64
|
-
|
|
65
|
-
|
|
64
|
+
assert_nil(@importer.import(nil))
|
|
65
|
+
assert_equal(5, @importer.import(5))
|
|
66
|
+
assert(@dest.object?(@importer.import({key: @obj})[:key]))
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
it "determines the source document dynamically" do
|
|
70
|
+
obj = @importer.import(@obj.value)
|
|
71
|
+
assert_equal("test", obj[:ref].value)
|
|
66
72
|
end
|
|
67
73
|
|
|
68
74
|
it "copies the data of the imported objects" do
|
|
@@ -120,10 +126,11 @@ describe HexaPDF::Importer do
|
|
|
120
126
|
assert_equal(90, page[:Rotate])
|
|
121
127
|
end
|
|
122
128
|
|
|
123
|
-
it "
|
|
129
|
+
it "works for importing objects from different documents" do
|
|
124
130
|
other_doc = HexaPDF::Document.new
|
|
125
131
|
other_obj = other_doc.add("test")
|
|
126
|
-
|
|
132
|
+
imported = @importer.import(other_obj)
|
|
133
|
+
assert_equal("test", imported.value)
|
|
127
134
|
end
|
|
128
135
|
end
|
|
129
136
|
end
|
data/test/hexapdf/test_parser.rb
CHANGED
|
@@ -54,6 +54,23 @@ describe HexaPDF::Parser do
|
|
|
54
54
|
@parser = HexaPDF::Parser.new(@parse_io, @document)
|
|
55
55
|
end
|
|
56
56
|
|
|
57
|
+
describe "linearized?" do
|
|
58
|
+
it "can determine whether a document is linearized" do
|
|
59
|
+
create_parser("%PDF-1.7\n%abcdefgh\n1 0 obj\n<</Linearized 1/H [2 4]/O 1/E 1/N 1/T 1>>\nendobj")
|
|
60
|
+
assert(@parser.linearized?)
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
it "returns false if the first object is not a linearization dictionary" do
|
|
64
|
+
create_parser("%PDF-1.7\n%abcdefgh\n1 0 obj\n<</Length 2 0 R>>\nstream\nhallo\nendstream\nendobj")
|
|
65
|
+
refute(@parser.linearized?)
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
it "returns false if there is a parse error" do
|
|
69
|
+
create_parser("%PDF-1.7\n%abcdefgh\n1 a obj thing")
|
|
70
|
+
refute(@parser.linearized?)
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
|
|
57
74
|
describe "parse_indirect_object" do
|
|
58
75
|
it "reads indirect objects sequentially" do
|
|
59
76
|
object, oid, gen, stream = @parser.parse_indirect_object
|
|
@@ -199,6 +199,14 @@ describe HexaPDF::Revision do
|
|
|
199
199
|
deleted = @rev.object(6)
|
|
200
200
|
@rev.delete(6)
|
|
201
201
|
assert_equal([obj, @obj, deleted], @rev.each_modified_object.to_a)
|
|
202
|
+
assert_same(obj, @rev.object(3))
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
it "optionally deletes the modified objects from the revision" do
|
|
206
|
+
obj = @rev.object(3)
|
|
207
|
+
obj.value = :other
|
|
208
|
+
assert_equal([obj], @rev.each_modified_object(delete: true).to_a)
|
|
209
|
+
refute_same(obj, @rev.object(3))
|
|
202
210
|
end
|
|
203
211
|
|
|
204
212
|
it "ignores object and xref streams that were deleted" do
|
|
@@ -207,6 +215,13 @@ describe HexaPDF::Revision do
|
|
|
207
215
|
assert_equal([], @rev.each_modified_object.to_a)
|
|
208
216
|
end
|
|
209
217
|
|
|
218
|
+
it "handles object and xref streams that were added appropriately depending on the 'all' arg" do
|
|
219
|
+
xref = @rev.add(HexaPDF::Dictionary.new({Type: :XRef}, oid: 8))
|
|
220
|
+
objstm = @rev.add(HexaPDF::Dictionary.new({Type: :ObjStm}, oid: 9))
|
|
221
|
+
assert_equal([], @rev.each_modified_object.to_a)
|
|
222
|
+
assert_equal([xref, objstm], @rev.each_modified_object(all: true).to_a)
|
|
223
|
+
end
|
|
224
|
+
|
|
210
225
|
it "doesn't return non-modified objects" do
|
|
211
226
|
@rev.object(2)
|
|
212
227
|
assert_equal([], @rev.each_modified_object.to_a)
|
|
@@ -230,18 +245,4 @@ describe HexaPDF::Revision do
|
|
|
230
245
|
assert_equal([], @rev.each_modified_object.to_a)
|
|
231
246
|
end
|
|
232
247
|
end
|
|
233
|
-
|
|
234
|
-
describe "reset_objects" do
|
|
235
|
-
it "deletes loaded objects" do
|
|
236
|
-
@rev.object(2)
|
|
237
|
-
@rev.reset_objects
|
|
238
|
-
assert(@rev.instance_variable_get(:@objects).oids.empty?)
|
|
239
|
-
end
|
|
240
|
-
|
|
241
|
-
it "deletes added objects" do
|
|
242
|
-
@rev.add(@obj)
|
|
243
|
-
@rev.reset_objects
|
|
244
|
-
assert(@rev.instance_variable_get(:@objects).oids.empty?)
|
|
245
|
-
end
|
|
246
|
-
end
|
|
247
248
|
end
|
|
@@ -356,4 +356,47 @@ describe HexaPDF::Revisions do
|
|
|
356
356
|
HexaPDF::Document.new(io: io, config: {'parser.try_xref_reconstruction' => false})
|
|
357
357
|
end
|
|
358
358
|
end
|
|
359
|
+
|
|
360
|
+
it "merges the two revisions of a linearized PDF into one" do
|
|
361
|
+
io = StringIO.new(<<~EOF)
|
|
362
|
+
%PDF-1.2
|
|
363
|
+
5 0 obj
|
|
364
|
+
<</Linearized 1>>
|
|
365
|
+
endobj
|
|
366
|
+
xref
|
|
367
|
+
5 1
|
|
368
|
+
0000000009 00000 n
|
|
369
|
+
trailer
|
|
370
|
+
<</ID[(a)(b)]/Info 1 0 R/Root 2 0 R/Size 6/Prev 394>>
|
|
371
|
+
%
|
|
372
|
+
1 0 obj
|
|
373
|
+
<</ModDate(D:20221205233910+01'00')/Producer(HexaPDF version 0.27.0)>>
|
|
374
|
+
endobj
|
|
375
|
+
2 0 obj
|
|
376
|
+
<</Type/Catalog/Pages 3 0 R>>
|
|
377
|
+
endobj
|
|
378
|
+
3 0 obj
|
|
379
|
+
<</Type/Pages/Kids[4 0 R]/Count 1>>
|
|
380
|
+
endobj
|
|
381
|
+
4 0 obj
|
|
382
|
+
<</Type/Page/MediaBox[0 0 595 842]/Parent 3 0 R/Resources<<>>>>
|
|
383
|
+
endobj
|
|
384
|
+
xref
|
|
385
|
+
0 5
|
|
386
|
+
0000000000 65535 f
|
|
387
|
+
0000000133 00000 n
|
|
388
|
+
0000000219 00000 n
|
|
389
|
+
0000000264 00000 n
|
|
390
|
+
0000000315 00000 n
|
|
391
|
+
trailer
|
|
392
|
+
<</ID[(a)(b)]/Info 1 0 R/Root 2 0 R/Size 5>>
|
|
393
|
+
startxref
|
|
394
|
+
41
|
|
395
|
+
%%EOF
|
|
396
|
+
EOF
|
|
397
|
+
doc = HexaPDF::Document.new(io: io, config: {'parser.try_xref_reconstruction' => false})
|
|
398
|
+
assert(doc.revisions.parser.linearized?)
|
|
399
|
+
assert_equal(1, doc.revisions.count)
|
|
400
|
+
assert_same(5, doc.revisions.current.xref_section.max_oid)
|
|
401
|
+
end
|
|
359
402
|
end
|
data/test/hexapdf/test_stream.rb
CHANGED
|
@@ -142,7 +142,7 @@ describe HexaPDF::Stream do
|
|
|
142
142
|
def encoded_data(str, encoders = [])
|
|
143
143
|
map = @document.config['filter.map']
|
|
144
144
|
tmp = feeder(str)
|
|
145
|
-
encoders.each {|e| tmp =
|
|
145
|
+
encoders.each {|e| tmp = Object.const_get(map[e]).encoder(tmp) }
|
|
146
146
|
collector(tmp)
|
|
147
147
|
end
|
|
148
148
|
|
|
@@ -14,15 +14,14 @@ describe HexaPDF::Tokenizer do
|
|
|
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
|
+
create_tokenizer("1 0 R +2 +15 R 2 -1 R 0 0 R 0 10 R -1 0 R")
|
|
18
18
|
assert_equal(HexaPDF::Reference.new(1, 0), @tokenizer.next_token)
|
|
19
19
|
assert_equal(HexaPDF::Reference.new(2, 15), @tokenizer.next_token)
|
|
20
20
|
assert_equal(2, @tokenizer.next_token)
|
|
21
21
|
assert_equal(-1, @tokenizer.next_token)
|
|
22
22
|
assert_equal('R', @tokenizer.next_token)
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
assert_equal('R', @tokenizer.next_token)
|
|
23
|
+
assert_nil(@tokenizer.next_token)
|
|
24
|
+
assert_nil(@tokenizer.next_token)
|
|
26
25
|
assert_equal(-1, @tokenizer.next_token)
|
|
27
26
|
assert_equal(0, @tokenizer.next_token)
|
|
28
27
|
assert_equal('R', @tokenizer.next_token)
|
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.29.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.29.0)>>
|
|
76
76
|
endobj
|
|
77
77
|
2 0 obj
|
|
78
78
|
<</Length 10>>stream
|
|
@@ -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.
|
|
217
|
+
<</Producer(HexaPDF version 0.29.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
|