hexapdf 0.44.0 → 0.46.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 +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
|