hexapdf 0.44.0 → 0.46.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 +106 -47
- data/examples/019-acro_form.rb +5 -0
- data/examples/027-composer_optional_content.rb +6 -4
- data/examples/030-pdfa.rb +12 -11
- data/lib/hexapdf/cli/inspect.rb +5 -0
- data/lib/hexapdf/composer.rb +23 -1
- data/lib/hexapdf/configuration.rb +8 -0
- data/lib/hexapdf/content/canvas.rb +3 -3
- data/lib/hexapdf/content/canvas_composer.rb +1 -0
- data/lib/hexapdf/digital_signature/cms_handler.rb +31 -3
- data/lib/hexapdf/digital_signature/signing/default_handler.rb +9 -1
- data/lib/hexapdf/digital_signature/signing/signed_data_creator.rb +5 -1
- data/lib/hexapdf/document/layout.rb +63 -30
- data/lib/hexapdf/document.rb +24 -2
- data/lib/hexapdf/font/type1/character_metrics.rb +1 -1
- data/lib/hexapdf/font/type1/font_metrics.rb +1 -1
- data/lib/hexapdf/importer.rb +15 -5
- data/lib/hexapdf/layout/box.rb +48 -36
- data/lib/hexapdf/layout/column_box.rb +3 -11
- data/lib/hexapdf/layout/container_box.rb +4 -4
- data/lib/hexapdf/layout/frame.rb +7 -6
- data/lib/hexapdf/layout/inline_box.rb +17 -23
- data/lib/hexapdf/layout/list_box.rb +27 -42
- data/lib/hexapdf/layout/page_style.rb +23 -16
- data/lib/hexapdf/layout/style.rb +5 -5
- data/lib/hexapdf/layout/table_box.rb +14 -10
- data/lib/hexapdf/layout/text_box.rb +60 -36
- data/lib/hexapdf/layout/text_fragment.rb +1 -1
- data/lib/hexapdf/layout/text_layouter.rb +7 -8
- data/lib/hexapdf/parser.rb +5 -1
- data/lib/hexapdf/rectangle.rb +4 -4
- data/lib/hexapdf/revisions.rb +1 -1
- data/lib/hexapdf/stream.rb +3 -3
- data/lib/hexapdf/tokenizer.rb +3 -2
- data/lib/hexapdf/type/acro_form/button_field.rb +2 -0
- data/lib/hexapdf/type/acro_form/choice_field.rb +2 -0
- data/lib/hexapdf/type/acro_form/field.rb +8 -0
- data/lib/hexapdf/type/acro_form/form.rb +2 -1
- data/lib/hexapdf/type/acro_form/text_field.rb +2 -0
- data/lib/hexapdf/type/form.rb +2 -2
- data/lib/hexapdf/version.rb +1 -1
- data/test/hexapdf/content/test_canvas_composer.rb +13 -8
- data/test/hexapdf/digital_signature/common.rb +66 -84
- data/test/hexapdf/digital_signature/signing/test_default_handler.rb +7 -0
- data/test/hexapdf/digital_signature/signing/test_signed_data_creator.rb +9 -0
- data/test/hexapdf/digital_signature/test_cms_handler.rb +41 -1
- data/test/hexapdf/digital_signature/test_handler.rb +2 -1
- data/test/hexapdf/document/test_layout.rb +44 -5
- data/test/hexapdf/layout/test_box.rb +23 -5
- data/test/hexapdf/layout/test_frame.rb +21 -2
- data/test/hexapdf/layout/test_inline_box.rb +17 -28
- data/test/hexapdf/layout/test_list_box.rb +8 -8
- data/test/hexapdf/layout/test_page_style.rb +7 -2
- data/test/hexapdf/layout/test_table_box.rb +8 -1
- data/test/hexapdf/layout/test_text_box.rb +51 -29
- data/test/hexapdf/layout/test_text_layouter.rb +0 -3
- data/test/hexapdf/test_composer.rb +14 -5
- data/test/hexapdf/test_document.rb +27 -0
- data/test/hexapdf/test_importer.rb +17 -0
- data/test/hexapdf/test_revisions.rb +54 -41
- data/test/hexapdf/test_serializer.rb +1 -0
- data/test/hexapdf/type/acro_form/test_form.rb +9 -0
- metadata +2 -2
@@ -76,6 +76,8 @@ module HexaPDF
|
|
76
76
|
#
|
77
77
|
# :no_export:: The field should *not* be exported by a submit-form action.
|
78
78
|
#
|
79
|
+
# Also see the class description of the subclasses for additional, type specific field flags.
|
80
|
+
#
|
79
81
|
# == Field Type Implementation Notes
|
80
82
|
#
|
81
83
|
# If an AcroForm field type adds additional inheritable dictionary fields, it has to set the
|
@@ -124,6 +126,8 @@ module HexaPDF
|
|
124
126
|
#
|
125
127
|
# Returns an array of flag names representing the set bit flags.
|
126
128
|
#
|
129
|
+
# See the class description for a list of available flags.
|
130
|
+
#
|
127
131
|
|
128
132
|
##
|
129
133
|
# :method: flagged?
|
@@ -133,6 +137,8 @@ module HexaPDF
|
|
133
137
|
# Returns +true+ if the given flag is set. The argument can either be the flag name or the
|
134
138
|
# bit index.
|
135
139
|
#
|
140
|
+
# See the class description for a list of available flags.
|
141
|
+
#
|
136
142
|
|
137
143
|
##
|
138
144
|
# :method: flag
|
@@ -142,6 +148,8 @@ module HexaPDF
|
|
142
148
|
# Sets the given flags, given as flag names or bit indices. If +clear_existing+ is +true+,
|
143
149
|
# all prior flags will be cleared.
|
144
150
|
#
|
151
|
+
# See the class description for a list of available flags.
|
152
|
+
#
|
145
153
|
bit_field(:flags, {read_only: 0, required: 1, no_export: 2},
|
146
154
|
lister: "flags", getter: "flagged?", setter: "flag", unsetter: "unflag",
|
147
155
|
value_getter: "self[:Ff]", value_setter: "self[:Ff]")
|
@@ -388,13 +388,14 @@ module HexaPDF
|
|
388
388
|
page_annots = page[:Annots].to_a - to_delete
|
389
389
|
page[:Annots].value.replace(page_annots)
|
390
390
|
end
|
391
|
-
to_delete.each {|widget| document.delete(widget) }
|
392
391
|
|
393
392
|
if field[:Parent]
|
394
393
|
field[:Parent][:Kids].delete(field)
|
395
394
|
else
|
396
395
|
self[:Fields].delete(field)
|
397
396
|
end
|
397
|
+
|
398
|
+
to_delete.each {|widget| document.delete(widget) }
|
398
399
|
document.delete(field)
|
399
400
|
end
|
400
401
|
|
@@ -50,6 +50,8 @@ module HexaPDF
|
|
50
50
|
#
|
51
51
|
# == Type Specific Field Flags
|
52
52
|
#
|
53
|
+
# See the class description for Field for the general field flags.
|
54
|
+
#
|
53
55
|
# :multiline:: If set, the text field may contain multiple lines.
|
54
56
|
#
|
55
57
|
# :password:: The field is a password field. This changes the behaviour of the PDF reader
|
data/lib/hexapdf/type/form.rb
CHANGED
@@ -159,8 +159,8 @@ module HexaPDF
|
|
159
159
|
# retained without the need for parsing its contents.
|
160
160
|
#
|
161
161
|
# If the bounding box of the form XObject doesn't have its origin at (0, 0), the canvas origin
|
162
|
-
# is translated into the bottom
|
163
|
-
# canvas. This means that the canvas' origin is always at the bottom
|
162
|
+
# is translated into the bottom-left corner so that this detail doesn't matter when using the
|
163
|
+
# canvas. This means that the canvas' origin is always at the bottom-left corner of the
|
164
164
|
# bounding box.
|
165
165
|
#
|
166
166
|
# *Note* that a canvas can only be retrieved for initially empty form XObjects!
|
data/lib/hexapdf/version.rb
CHANGED
@@ -47,21 +47,20 @@ describe HexaPDF::Content::CanvasComposer do
|
|
47
47
|
end
|
48
48
|
|
49
49
|
it "splits the box if possible" do
|
50
|
-
@composer.draw_box(create_box(width:
|
51
|
-
box = create_box(
|
52
|
-
box.define_singleton_method(:
|
53
|
-
|
54
|
-
end
|
50
|
+
@composer.draw_box(create_box(width: 300, height: 300, style: {position: :float}))
|
51
|
+
box = create_box(style: {mask_mode: :box})
|
52
|
+
box.define_singleton_method(:fit_content) {|*| fit_result.overflow! }
|
53
|
+
box.define_singleton_method(:split_content) { [box, HexaPDF::Layout::Box.new(height: 100) {}] }
|
55
54
|
@composer.draw_box(box)
|
56
55
|
assert_operators(@composer.canvas.contents,
|
57
56
|
[[:save_graphics_state],
|
58
|
-
[:concatenate_matrix, [1, 0, 0, 1, 0,
|
57
|
+
[:concatenate_matrix, [1, 0, 0, 1, 0, 541.889764]],
|
59
58
|
[:restore_graphics_state],
|
60
59
|
[:save_graphics_state],
|
61
|
-
[:concatenate_matrix, [1, 0, 0, 1,
|
60
|
+
[:concatenate_matrix, [1, 0, 0, 1, 300, 0]],
|
62
61
|
[:restore_graphics_state],
|
63
62
|
[:save_graphics_state],
|
64
|
-
[:concatenate_matrix, [1, 0, 0, 1,
|
63
|
+
[:concatenate_matrix, [1, 0, 0, 1, 0, 441.889764]],
|
65
64
|
[:restore_graphics_state]])
|
66
65
|
end
|
67
66
|
|
@@ -77,6 +76,12 @@ describe HexaPDF::Content::CanvasComposer do
|
|
77
76
|
[:restore_graphics_state]])
|
78
77
|
end
|
79
78
|
|
79
|
+
it "handles truncated boxes correctly" do
|
80
|
+
box = create_box(height: 400, style: {overflow: :truncate})
|
81
|
+
box.define_singleton_method(:fit_content) {|*| fit_result.overflow! }
|
82
|
+
assert_same(box, @composer.draw_box(box))
|
83
|
+
end
|
84
|
+
|
80
85
|
it "returns the last drawn box" do
|
81
86
|
box = create_box(height: 400)
|
82
87
|
assert_same(box, @composer.draw_box(box))
|
@@ -12,26 +12,10 @@ module HexaPDF
|
|
12
12
|
def ca_certificate
|
13
13
|
@ca_certificate ||=
|
14
14
|
begin
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
ca_cert.version = 2
|
20
|
-
ca_cert.not_before = Time.now - 86400
|
21
|
-
ca_cert.not_after = Time.now + 86400
|
22
|
-
ca_cert.public_key = ca_key.public_key
|
23
|
-
ca_cert.subject = ca_name
|
24
|
-
ca_cert.issuer = ca_name
|
25
|
-
|
26
|
-
extension_factory = OpenSSL::X509::ExtensionFactory.new
|
27
|
-
extension_factory.subject_certificate = ca_cert
|
28
|
-
extension_factory.issuer_certificate = ca_cert
|
29
|
-
ca_cert.add_extension(extension_factory.create_extension('subjectKeyIdentifier', 'hash'))
|
30
|
-
ca_cert.add_extension(extension_factory.create_extension('basicConstraints', 'CA:TRUE', true))
|
31
|
-
ca_cert.add_extension(extension_factory.create_extension('keyUsage', 'cRLSign,keyCertSign', true))
|
32
|
-
ca_cert.sign(ca_key, OpenSSL::Digest.new('SHA1'))
|
33
|
-
|
34
|
-
ca_cert
|
15
|
+
cert = create_cert(name: '/C=AT/O=HexaPDF/CN=HexaPDF Test Root CA', serial: 0,
|
16
|
+
public_key: ca_key.public_key)
|
17
|
+
add_extensions(cert, cert, ca_key, is_ca: true, key_usage: 'cRLSign,keyCertSign')
|
18
|
+
cert
|
35
19
|
end
|
36
20
|
end
|
37
21
|
|
@@ -39,88 +23,86 @@ module HexaPDF
|
|
39
23
|
@signer_key ||= OpenSSL::PKey::RSA.new(2048)
|
40
24
|
end
|
41
25
|
|
42
|
-
def dsa_signer_key
|
43
|
-
@dsa_signer_key ||= OpenSSL::PKey::DSA.new(2048)
|
44
|
-
end
|
45
|
-
|
46
26
|
def signer_certificate
|
47
27
|
@signer_certificate ||=
|
48
28
|
begin
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
signer_cert.add_extension(extension_factory.create_extension('subjectKeyIdentifier', 'hash'))
|
64
|
-
signer_cert.add_extension(extension_factory.create_extension('basicConstraints', 'CA:FALSE'))
|
65
|
-
signer_cert.add_extension(extension_factory.create_extension('keyUsage', 'digitalSignature'))
|
66
|
-
signer_cert.sign(ca_key, OpenSSL::Digest.new('SHA1'))
|
67
|
-
|
68
|
-
signer_cert
|
29
|
+
cert = create_cert(name: '/CN=RSA signer/DC=gettalong', serial: 2,
|
30
|
+
public_key: signer_key.public_key, issuer: ca_certificate)
|
31
|
+
add_extensions(cert, ca_certificate, ca_key, key_usage: 'digitalSignature')
|
32
|
+
cert
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def non_repudiation_signer_certificate
|
37
|
+
@non_repudiation_signer_certificate ||=
|
38
|
+
begin
|
39
|
+
cert = create_cert(name: '/CN=Non repudiation signer/DC=gettalong', serial: 2,
|
40
|
+
public_key: signer_key.public_key, issuer: ca_certificate)
|
41
|
+
add_extensions(cert, ca_certificate, ca_key, key_usage: 'nonRepudiation')
|
42
|
+
cert
|
69
43
|
end
|
70
44
|
end
|
71
45
|
|
46
|
+
def dsa_signer_key
|
47
|
+
@dsa_signer_key ||= OpenSSL::PKey::DSA.new(2048)
|
48
|
+
end
|
49
|
+
|
72
50
|
def dsa_signer_certificate
|
73
51
|
@dsa_signer_certificate ||=
|
74
52
|
begin
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
signer_cert.not_after = Time.now + 86400
|
80
|
-
signer_cert.public_key = dsa_signer_key.public_key
|
81
|
-
signer_cert.subject = OpenSSL::X509::Name.parse('/CN=DSA signer/DC=gettalong')
|
82
|
-
signer_cert.issuer = ca_certificate.subject
|
83
|
-
|
84
|
-
extension_factory = OpenSSL::X509::ExtensionFactory.new
|
85
|
-
extension_factory.subject_certificate = signer_cert
|
86
|
-
extension_factory.issuer_certificate = ca_certificate
|
87
|
-
signer_cert.add_extension(extension_factory.create_extension('subjectKeyIdentifier', 'hash'))
|
88
|
-
signer_cert.add_extension(extension_factory.create_extension('basicConstraints', 'CA:FALSE'))
|
89
|
-
signer_cert.add_extension(extension_factory.create_extension('keyUsage', 'digitalSignature'))
|
90
|
-
signer_cert.sign(ca_key, OpenSSL::Digest.new('SHA1'))
|
91
|
-
|
92
|
-
signer_cert
|
53
|
+
cert = create_cert(name: '/CN=DSA signer/DC=gettalong', serial: 3,
|
54
|
+
public_key: dsa_signer_key.public_key, issuer: ca_certificate)
|
55
|
+
add_extensions(cert, ca_certificate, ca_key, key_usage: 'digitalSignature')
|
56
|
+
cert
|
93
57
|
end
|
94
58
|
end
|
95
59
|
|
96
60
|
def timestamp_certificate
|
97
61
|
@timestamp_certificate ||=
|
98
62
|
begin
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
signer_cert.not_before = Time.now - 86400
|
105
|
-
signer_cert.not_after = Time.now + 86400
|
106
|
-
signer_cert.public_key = signer_key.public_key
|
107
|
-
signer_cert.subject = name
|
108
|
-
signer_cert.issuer = ca_certificate.subject
|
109
|
-
|
110
|
-
extension_factory = OpenSSL::X509::ExtensionFactory.new
|
111
|
-
extension_factory.subject_certificate = signer_cert
|
112
|
-
extension_factory.issuer_certificate = ca_certificate
|
113
|
-
signer_cert.add_extension(extension_factory.create_extension('subjectKeyIdentifier', 'hash'))
|
114
|
-
signer_cert.add_extension(extension_factory.create_extension('basicConstraints', 'CA:FALSE'))
|
115
|
-
signer_cert.add_extension(extension_factory.create_extension('keyUsage', 'digitalSignature'))
|
116
|
-
signer_cert.add_extension(extension_factory.create_extension('extendedKeyUsage',
|
117
|
-
'timeStamping', true))
|
118
|
-
signer_cert.sign(ca_key, OpenSSL::Digest.new('SHA1'))
|
119
|
-
|
120
|
-
signer_cert
|
63
|
+
cert = create_cert(name: '/CN=timestamp/DC=gettalong', serial: 3,
|
64
|
+
public_key: signer_key.public_key, issuer: ca_certificate)
|
65
|
+
add_extensions(cert, ca_certificate, ca_key, key_usage: 'digitalSignature',
|
66
|
+
extended_key_usage: 'timeStamping')
|
67
|
+
cert
|
121
68
|
end
|
122
69
|
end
|
123
70
|
|
71
|
+
def create_cert(name:, serial:, public_key:, issuer: nil)
|
72
|
+
name = OpenSSL::X509::Name.parse(name)
|
73
|
+
cert = OpenSSL::X509::Certificate.new
|
74
|
+
cert.serial = serial
|
75
|
+
cert.version = 2
|
76
|
+
cert.not_before = Time.now - 86400
|
77
|
+
cert.not_after = Time.now + 86400
|
78
|
+
cert.public_key = public_key
|
79
|
+
cert.subject = name
|
80
|
+
cert.issuer = (issuer ? issuer.subject : name)
|
81
|
+
cert
|
82
|
+
end
|
83
|
+
|
84
|
+
def add_extensions(subject_cert, issuer_cert, signing_key, is_ca: false, key_usage: nil,
|
85
|
+
extended_key_usage: nil)
|
86
|
+
extension_factory = OpenSSL::X509::ExtensionFactory.new
|
87
|
+
extension_factory.subject_certificate = subject_cert
|
88
|
+
extension_factory.issuer_certificate = issuer_cert
|
89
|
+
subject_cert.add_extension(extension_factory.create_extension('subjectKeyIdentifier', 'hash'))
|
90
|
+
if is_ca
|
91
|
+
subject_cert.add_extension(extension_factory.create_extension('basicConstraints', 'CA:TRUE', true))
|
92
|
+
else
|
93
|
+
subject_cert.add_extension(extension_factory.create_extension('basicConstraints', 'CA:FALSE'))
|
94
|
+
end
|
95
|
+
if key_usage
|
96
|
+
subject_cert.add_extension(extension_factory.create_extension('keyUsage', key_usage, true))
|
97
|
+
end
|
98
|
+
if extended_key_usage
|
99
|
+
subject_cert.add_extension(extension_factory.create_extension('extendedKeyUsage',
|
100
|
+
extended_key_usage, true))
|
101
|
+
end
|
102
|
+
subject_cert.sign(signing_key, OpenSSL::Digest.new('SHA1'))
|
103
|
+
end
|
104
|
+
private :add_extensions
|
105
|
+
|
124
106
|
def start_tsa_server
|
125
107
|
return if defined?(@tsa_server)
|
126
108
|
require 'webrick'
|
@@ -133,6 +133,13 @@ describe HexaPDF::DigitalSignature::Signing::DefaultHandler do
|
|
133
133
|
assert_equal(['Reason', 'Location', 'Contact'], @obj.value.values_at(:Reason, :Location, :ContactInfo))
|
134
134
|
end
|
135
135
|
|
136
|
+
it "sets the signing time" do
|
137
|
+
time = Time.now
|
138
|
+
@handler.signing_time = time
|
139
|
+
@handler.finalize_objects(@field, @obj)
|
140
|
+
assert_equal(time, @obj[:M])
|
141
|
+
end
|
142
|
+
|
136
143
|
it "fills the build properties dictionary with appropriate application information" do
|
137
144
|
@handler.finalize_objects(@field, @obj)
|
138
145
|
assert_equal(:HexaPDF, @obj[:Prop_Build][:App][:Name])
|
@@ -214,6 +214,15 @@ describe HexaPDF::DigitalSignature::Signing::SignedDataCreator do
|
|
214
214
|
assert_equal(Time.now.utc, attr.value[1].value[0].value)
|
215
215
|
end
|
216
216
|
end
|
217
|
+
|
218
|
+
it "can use a user-defined time as signing time" do
|
219
|
+
current_time = Time.now
|
220
|
+
@signed_data.signing_time = current_time
|
221
|
+
asn1 = OpenSSL::ASN1.decode(@signed_data.create("data"))
|
222
|
+
attr = asn1.value[1].value[0].value[4].value[0].value[3].value.
|
223
|
+
find {|obj| obj.value[0].value == 'signingTime' }
|
224
|
+
assert_equal(current_time.floor.utc, attr.value[1].value[0].value)
|
225
|
+
end
|
217
226
|
end
|
218
227
|
|
219
228
|
describe "pades signature" do
|
@@ -22,7 +22,7 @@ describe HexaPDF::DigitalSignature::CMSHandler do
|
|
22
22
|
assert_equal("RSA signer", @handler.signer_name)
|
23
23
|
end
|
24
24
|
|
25
|
-
it "returns the signing time" do
|
25
|
+
it "returns the signing time from the signed attributes" do
|
26
26
|
assert_equal(@pkcs7.signers.first.signed_time, @handler.signing_time)
|
27
27
|
end
|
28
28
|
|
@@ -86,6 +86,18 @@ describe HexaPDF::DigitalSignature::CMSHandler do
|
|
86
86
|
assert_match(/key usage is missing 'Digital Signature'/, result.messages.first.content)
|
87
87
|
end
|
88
88
|
|
89
|
+
it "provides info for a non-repudiation signature" do
|
90
|
+
@pkcs7 = OpenSSL::PKCS7.sign(CERTIFICATES.non_repudiation_signer_certificate,
|
91
|
+
CERTIFICATES.signer_key,
|
92
|
+
@data, [CERTIFICATES.ca_certificate],
|
93
|
+
OpenSSL::PKCS7::DETACHED)
|
94
|
+
@dict.contents = @pkcs7.to_der
|
95
|
+
@handler = HexaPDF::DigitalSignature::CMSHandler.new(@dict)
|
96
|
+
result = @handler.verify(@store)
|
97
|
+
assert_equal(:info, result.messages.first.type)
|
98
|
+
assert_match(/Certificate used for non-repudiation/, result.messages.first.content)
|
99
|
+
end
|
100
|
+
|
89
101
|
it "verifies the signature itself" do
|
90
102
|
result = @handler.verify(@store)
|
91
103
|
assert_equal(:info, result.messages.last.type)
|
@@ -117,4 +129,32 @@ describe HexaPDF::DigitalSignature::CMSHandler do
|
|
117
129
|
assert_match(/Signature valid/, result.messages.last.content)
|
118
130
|
end
|
119
131
|
end
|
132
|
+
|
133
|
+
describe "with embedded TSA signature" do
|
134
|
+
before do
|
135
|
+
CERTIFICATES.start_tsa_server
|
136
|
+
tsh = HexaPDF::DigitalSignature::Signing::TimestampHandler.new(
|
137
|
+
signature_size: 10_000, tsa_url: 'http://127.0.0.1:34567'
|
138
|
+
)
|
139
|
+
cms = HexaPDF::DigitalSignature::Signing::SignedDataCreator.create(
|
140
|
+
@data, type: :pades, certificate: CERTIFICATES.signer_certificate,
|
141
|
+
key: CERTIFICATES.signer_key, timestamp_handler: tsh,
|
142
|
+
certificates: [CERTIFICATES.ca_certificate]
|
143
|
+
)
|
144
|
+
@dict.contents = cms.to_der
|
145
|
+
@dict.signed_data = @data
|
146
|
+
@handler = HexaPDF::DigitalSignature::CMSHandler.new(@dict)
|
147
|
+
end
|
148
|
+
|
149
|
+
it "returns the signing time from the TSA signature" do
|
150
|
+
assert_equal(@handler.embedded_tsa_signature.signers.first.signed_time, @handler.signing_time)
|
151
|
+
end
|
152
|
+
|
153
|
+
it "provides informational output if the time is from a TSA signature" do
|
154
|
+
store = OpenSSL::X509::Store.new
|
155
|
+
result = @handler.verify(store)
|
156
|
+
assert_equal(:info, result.messages.first.type)
|
157
|
+
assert_match(/Signing time.*timestamp authority/, result.messages.first.content)
|
158
|
+
end
|
159
|
+
end
|
120
160
|
end
|
@@ -5,6 +5,7 @@ require 'hexapdf/digital_signature'
|
|
5
5
|
require 'hexapdf/document'
|
6
6
|
require 'time'
|
7
7
|
require 'ostruct'
|
8
|
+
require 'openssl'
|
8
9
|
|
9
10
|
describe HexaPDF::DigitalSignature::Handler do
|
10
11
|
before do
|
@@ -36,7 +37,7 @@ describe HexaPDF::DigitalSignature::Handler do
|
|
36
37
|
end
|
37
38
|
|
38
39
|
it "can allow self-signed certificates" do
|
39
|
-
[OpenSSL::X509::
|
40
|
+
[OpenSSL::X509::V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT,
|
40
41
|
OpenSSL::X509::V_ERR_SELF_SIGNED_CERT_IN_CHAIN].each do |error|
|
41
42
|
[true, false].each do |allow_self_signed|
|
42
43
|
@result.messages.clear
|
@@ -35,11 +35,11 @@ describe HexaPDF::Document::Layout::ChildrenCollector do
|
|
35
35
|
end
|
36
36
|
|
37
37
|
it "allows appending boxes created by the Layout class" do
|
38
|
-
@collector.lorem_ipsum
|
39
|
-
@collector.lorem_ipsum_box
|
40
|
-
@collector.column
|
41
|
-
@collector.column_box
|
42
|
-
assert_equal(
|
38
|
+
box1 = @collector.lorem_ipsum
|
39
|
+
box2 = @collector.lorem_ipsum_box
|
40
|
+
box3 = @collector.column
|
41
|
+
box4 = @collector.column_box
|
42
|
+
assert_equal([box1, box2, box3, box4], @collector.children)
|
43
43
|
assert_kind_of(HexaPDF::Layout::TextBox, @collector.children[0])
|
44
44
|
assert_kind_of(HexaPDF::Layout::TextBox, @collector.children[1])
|
45
45
|
assert_kind_of(HexaPDF::Layout::ColumnBox, @collector.children[2])
|
@@ -95,6 +95,11 @@ describe HexaPDF::Document::Layout::CellArgumentCollector do
|
|
95
95
|
@args[-3..-1, -5..-2] = {key: :value}
|
96
96
|
check_argument_info(@args.argument_infos.first, 17..19, 5..8, {key: :value})
|
97
97
|
end
|
98
|
+
|
99
|
+
it "allows using stepped ranges" do
|
100
|
+
@args[(0..-1).step(2)] = {key: :value}
|
101
|
+
check_argument_info(@args.argument_infos.first, (0..19).step(2), 0..9, {key: :value})
|
102
|
+
end
|
98
103
|
end
|
99
104
|
|
100
105
|
describe "retrieve_arguments_for" do
|
@@ -141,6 +146,40 @@ describe HexaPDF::Document::Layout do
|
|
141
146
|
end
|
142
147
|
end
|
143
148
|
|
149
|
+
describe "styles" do
|
150
|
+
it "returns the existing styles" do
|
151
|
+
@layout.style(:test, font_size: 20)
|
152
|
+
assert_equal([:base, :test], @layout.styles.keys)
|
153
|
+
end
|
154
|
+
|
155
|
+
it "sets multiple styles at once" do
|
156
|
+
styles = @layout.styles(
|
157
|
+
test: {font_size: 20},
|
158
|
+
test2: {font_size: 30},
|
159
|
+
)
|
160
|
+
assert_same(styles, @layout.styles)
|
161
|
+
assert_equal([:base, :test, :test2], @layout.styles.keys)
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
describe "private retrieve_style" do
|
166
|
+
it "resolves a font name to a font wrapper" do
|
167
|
+
style = @layout.send(:retrieve_style, {font: 'Helvetica'})
|
168
|
+
assert_kind_of(HexaPDF::Font::Type1Wrapper, style.font)
|
169
|
+
end
|
170
|
+
|
171
|
+
it "sets the :base style's font if no font is set" do
|
172
|
+
@layout.style(:base, font: 'Helvetica')
|
173
|
+
style = @layout.send(:retrieve_style, {})
|
174
|
+
assert_equal('Helvetica', style.font.wrapped_font.font_name)
|
175
|
+
end
|
176
|
+
|
177
|
+
it "sets the font specified in the config option font.default as fallback" do
|
178
|
+
style = @layout.send(:retrieve_style, {})
|
179
|
+
assert_equal('Times-Roman', style.font.wrapped_font.font_name)
|
180
|
+
end
|
181
|
+
end
|
182
|
+
|
144
183
|
describe "inline_box" do
|
145
184
|
it "takes a box as argument" do
|
146
185
|
box = HexaPDF::Layout::Box.create(width: 10, height: 10)
|
@@ -40,6 +40,9 @@ describe HexaPDF::Layout::Box do
|
|
40
40
|
@frame = Object.new
|
41
41
|
def @frame.x; 0; end
|
42
42
|
def @frame.y; 100; end
|
43
|
+
def @frame.bottom; 40; end
|
44
|
+
def @frame.width; 150; end
|
45
|
+
def @frame.height; 150; end
|
43
46
|
end
|
44
47
|
|
45
48
|
def create_box(**args, &block)
|
@@ -130,6 +133,14 @@ describe HexaPDF::Layout::Box do
|
|
130
133
|
assert_equal(100, box.height)
|
131
134
|
end
|
132
135
|
|
136
|
+
it "use the frame's width and its remaining height for position=:flow boxes" do
|
137
|
+
box = create_box(style: {position: :flow})
|
138
|
+
box.define_singleton_method(:supports_position_flow?) { true }
|
139
|
+
assert(box.fit(100, 100, @frame).success?)
|
140
|
+
assert_equal(150, box.width)
|
141
|
+
assert_equal(60, box.height)
|
142
|
+
end
|
143
|
+
|
133
144
|
it "uses float comparison" do
|
134
145
|
box = create_box(width: 50.0000002, height: 49.9999996)
|
135
146
|
assert(box.fit(50, 50, @frame).success?)
|
@@ -137,29 +148,36 @@ describe HexaPDF::Layout::Box do
|
|
137
148
|
assert_equal(49.9999996, box.height)
|
138
149
|
end
|
139
150
|
|
140
|
-
it "
|
151
|
+
it "works for boxes with no space for the content" do
|
152
|
+
box = create_box(height: 1, style: {border: {width: [1, 0, 0]}})
|
153
|
+
assert(box.fit(100, 100, @frame).success?)
|
154
|
+
assert_equal(1, box.height)
|
155
|
+
assert_equal(100, box.width)
|
156
|
+
end
|
157
|
+
|
158
|
+
it "fails if position != :flow and its width is greater than the available width" do
|
141
159
|
box = create_box(width: 101)
|
142
160
|
assert(box.fit(100, 100, @frame).failure?)
|
143
161
|
end
|
144
162
|
|
145
|
-
it "fails if
|
163
|
+
it "fails if position != :flow and its width is greater than the available width" do
|
146
164
|
box = create_box(height: 101)
|
147
165
|
assert(box.fit(100, 100, @frame).failure?)
|
148
166
|
end
|
149
167
|
|
150
|
-
it "fails if
|
168
|
+
it "fails if position != :flow and the reserved width is greater than the width" do
|
151
169
|
box = create_box(height: 100)
|
152
170
|
box.style.padding = [0, 100]
|
153
171
|
assert(box.fit(150, 150, @frame).failure?)
|
154
172
|
end
|
155
173
|
|
156
|
-
it "fails if
|
174
|
+
it "fails if position != :flow and the reserved height is greater than the height" do
|
157
175
|
box = create_box(width: 100)
|
158
176
|
box.style.padding = [100, 0]
|
159
177
|
assert(box.fit(150, 150, @frame).failure?)
|
160
178
|
end
|
161
179
|
|
162
|
-
it "can use the #
|
180
|
+
it "can use the #update_content_width/#update_content_height helper methods" do
|
163
181
|
box = create_box
|
164
182
|
box.define_singleton_method(:fit_content) do |_aw, _ah, _frame|
|
165
183
|
update_content_width { 10 }
|
@@ -316,10 +316,20 @@ describe HexaPDF::Layout::Frame do
|
|
316
316
|
|
317
317
|
describe "flowing boxes" do
|
318
318
|
it "flows inside the frame's outline" do
|
319
|
+
remove_area(:left)
|
319
320
|
check_box({width: 10, height: 20, margin: 10, position: :flow},
|
320
|
-
[
|
321
|
+
[10, 90],
|
321
322
|
[10, 80, 110, 110],
|
322
|
-
[[[
|
323
|
+
[[[20, 10], [110, 10], [110, 80], [20, 80]]])
|
324
|
+
assert_equal(10, @box.fit_result.x)
|
325
|
+
end
|
326
|
+
|
327
|
+
it "doesn't overwrite fit_result.x" do
|
328
|
+
box = HexaPDF::Layout::Box.create(position: :flow) {}
|
329
|
+
box.define_singleton_method(:supports_position_flow?) { true }
|
330
|
+
box.define_singleton_method(:fit_content) {|*args| fit_result.x = 30; super(*args) }
|
331
|
+
fit_result = @frame.fit(box)
|
332
|
+
assert_equal(30, fit_result.x)
|
323
333
|
end
|
324
334
|
|
325
335
|
it "uses position=default if the box indicates it doesn't support flowing contents" do
|
@@ -423,6 +433,15 @@ describe HexaPDF::Layout::Frame do
|
|
423
433
|
box = HexaPDF::Layout::Box.create
|
424
434
|
refute(@frame.fit(box).success?)
|
425
435
|
end
|
436
|
+
|
437
|
+
it "doesn't do post-fitting tasks if fitting is a failure" do
|
438
|
+
box = HexaPDF::Layout::Box.create(width: 400)
|
439
|
+
result = @frame.fit(box)
|
440
|
+
assert(result.failure?)
|
441
|
+
assert_nil(result.x)
|
442
|
+
assert_nil(result.y)
|
443
|
+
assert_nil(result.mask)
|
444
|
+
end
|
426
445
|
end
|
427
446
|
|
428
447
|
describe "split" do
|
@@ -23,47 +23,36 @@ describe HexaPDF::Layout::InlineBox do
|
|
23
23
|
ibox = inline_box(box, valign: :top)
|
24
24
|
assert_equal(:top, ibox.valign)
|
25
25
|
end
|
26
|
-
|
27
|
-
it "fails if the wrapped box has not width set" do
|
28
|
-
box = HexaPDF::Document.new.layout.text("test is not going good")
|
29
|
-
assert_raises(HexaPDF::Error) { inline_box(box) }
|
30
|
-
end
|
31
26
|
end
|
32
27
|
|
33
28
|
describe "fit_wrapped_box" do
|
34
|
-
it "automatically fits the provided box
|
35
|
-
ibox = inline_box(HexaPDF::Document.new.layout.text("test is going good",
|
29
|
+
it "automatically fits the provided box when given a frame" do
|
30
|
+
ibox = inline_box(HexaPDF::Document.new.layout.text("test is going good", margin: [5, 10]))
|
36
31
|
ibox.fit_wrapped_box(HexaPDF::Layout::Frame.new(0, 0, 50, 50))
|
37
|
-
assert_equal(
|
38
|
-
assert_equal(
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
assert_equal(20, ibox.width)
|
45
|
-
assert_equal(45, ibox.height)
|
46
|
-
end
|
47
|
-
|
48
|
-
it "fails if the wrapped box could not be fit" do
|
49
|
-
box = HexaPDF::Document.new.layout.text("test is not going good", width: 1)
|
50
|
-
assert_raises(HexaPDF::Error) { inline_box(box).fit_wrapped_box(nil) }
|
32
|
+
assert_equal(90.84, ibox.width)
|
33
|
+
assert_equal(19, ibox.height)
|
34
|
+
fit_result = ibox.instance_variable_get(:@fit_result)
|
35
|
+
assert_equal(10, fit_result.x)
|
36
|
+
assert_equal(5, fit_result.y)
|
37
|
+
assert_equal(70.84 + 2 * 10, fit_result.mask.width)
|
38
|
+
assert_equal(9 + 2 * 5, fit_result.mask.height)
|
51
39
|
end
|
52
40
|
|
53
|
-
it "
|
54
|
-
|
55
|
-
|
56
|
-
|
41
|
+
it "automatically fits the provided box without a given frame" do
|
42
|
+
ibox = inline_box(HexaPDF::Document.new.layout.text("test is going good"))
|
43
|
+
ibox.fit_wrapped_box
|
44
|
+
assert_equal(70.84, ibox.width)
|
45
|
+
assert_equal(9, ibox.height)
|
57
46
|
end
|
58
47
|
end
|
59
48
|
|
60
49
|
it "draws the wrapped box at the correct position" do
|
61
50
|
doc = HexaPDF::Document.new
|
62
51
|
canvas = doc.pages.add.canvas
|
63
|
-
box =
|
64
|
-
box.fit_wrapped_box
|
52
|
+
box = HexaPDF::Layout::InlineBox.create(width: 10, margin: [15, 10]) {}
|
53
|
+
box.fit_wrapped_box
|
65
54
|
box.draw(canvas, 100, 200)
|
66
|
-
assert_equal("q\n1 0 0 1 110
|
55
|
+
assert_equal("q\n1 0 0 1 110 215 cm\nQ\n", canvas.contents)
|
67
56
|
end
|
68
57
|
|
69
58
|
it "returns true if the inline box is empty with no drawing operations" do
|