hexapdf 0.45.0 → 0.47.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (68) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +120 -47
  3. data/examples/019-acro_form.rb +5 -0
  4. data/lib/hexapdf/cli/inspect.rb +5 -0
  5. data/lib/hexapdf/composer.rb +1 -1
  6. data/lib/hexapdf/configuration.rb +19 -0
  7. data/lib/hexapdf/digital_signature/cms_handler.rb +31 -3
  8. data/lib/hexapdf/digital_signature/signing/default_handler.rb +9 -1
  9. data/lib/hexapdf/digital_signature/signing/signed_data_creator.rb +5 -1
  10. data/lib/hexapdf/document/layout.rb +48 -27
  11. data/lib/hexapdf/document.rb +24 -2
  12. data/lib/hexapdf/encryption/standard_security_handler.rb +32 -26
  13. data/lib/hexapdf/importer.rb +15 -5
  14. data/lib/hexapdf/layout/box.rb +25 -28
  15. data/lib/hexapdf/layout/frame.rb +1 -1
  16. data/lib/hexapdf/layout/inline_box.rb +17 -23
  17. data/lib/hexapdf/layout/list_box.rb +24 -29
  18. data/lib/hexapdf/layout/page_style.rb +23 -16
  19. data/lib/hexapdf/layout/style.rb +2 -2
  20. data/lib/hexapdf/layout/table_box.rb +57 -10
  21. data/lib/hexapdf/layout/text_box.rb +2 -6
  22. data/lib/hexapdf/parser.rb +5 -1
  23. data/lib/hexapdf/revisions.rb +1 -1
  24. data/lib/hexapdf/stream.rb +3 -3
  25. data/lib/hexapdf/task/optimize.rb +4 -4
  26. data/lib/hexapdf/tokenizer.rb +3 -2
  27. data/lib/hexapdf/type/acro_form/appearance_generator.rb +8 -4
  28. data/lib/hexapdf/type/acro_form/button_field.rb +2 -0
  29. data/lib/hexapdf/type/acro_form/choice_field.rb +2 -0
  30. data/lib/hexapdf/type/acro_form/field.rb +8 -0
  31. data/lib/hexapdf/type/acro_form/form.rb +10 -6
  32. data/lib/hexapdf/type/acro_form/signature_field.rb +2 -1
  33. data/lib/hexapdf/type/acro_form/text_field.rb +2 -0
  34. data/lib/hexapdf/type/acro_form/variable_text_field.rb +11 -3
  35. data/lib/hexapdf/type/annotations/widget.rb +4 -2
  36. data/lib/hexapdf/version.rb +1 -1
  37. data/lib/hexapdf/writer.rb +1 -0
  38. data/test/data/standard-security-handler/bothpwd-aes-256bit-V5-R5.pdf +43 -0
  39. data/test/data/standard-security-handler/nopwd-aes-256bit-V5-R5.pdf +44 -0
  40. data/test/data/standard-security-handler/ownerpwd-aes-256bit-V5-R5.pdf +43 -0
  41. data/test/data/standard-security-handler/userpwd-aes-256bit-V5-R5.pdf +0 -0
  42. data/test/hexapdf/digital_signature/common.rb +66 -84
  43. data/test/hexapdf/digital_signature/signing/test_default_handler.rb +7 -0
  44. data/test/hexapdf/digital_signature/signing/test_signed_data_creator.rb +9 -0
  45. data/test/hexapdf/digital_signature/test_cms_handler.rb +41 -1
  46. data/test/hexapdf/digital_signature/test_handler.rb +2 -1
  47. data/test/hexapdf/digital_signature/test_signatures.rb +4 -4
  48. data/test/hexapdf/document/test_layout.rb +28 -5
  49. data/test/hexapdf/encryption/test_standard_security_handler.rb +5 -2
  50. data/test/hexapdf/layout/test_box.rb +12 -5
  51. data/test/hexapdf/layout/test_frame.rb +12 -2
  52. data/test/hexapdf/layout/test_inline_box.rb +17 -28
  53. data/test/hexapdf/layout/test_list_box.rb +5 -5
  54. data/test/hexapdf/layout/test_page_style.rb +7 -2
  55. data/test/hexapdf/layout/test_table_box.rb +52 -0
  56. data/test/hexapdf/layout/test_text_box.rb +3 -9
  57. data/test/hexapdf/layout/test_text_layouter.rb +0 -3
  58. data/test/hexapdf/task/test_optimize.rb +2 -0
  59. data/test/hexapdf/test_document.rb +30 -3
  60. data/test/hexapdf/test_importer.rb +24 -0
  61. data/test/hexapdf/test_revisions.rb +54 -41
  62. data/test/hexapdf/test_writer.rb +11 -2
  63. data/test/hexapdf/type/acro_form/test_appearance_generator.rb +22 -5
  64. data/test/hexapdf/type/acro_form/test_form.rb +9 -5
  65. data/test/hexapdf/type/acro_form/test_signature_field.rb +3 -1
  66. data/test/hexapdf/type/acro_form/test_variable_text_field.rb +14 -1
  67. data/test/hexapdf/type/annotations/test_widget.rb +4 -0
  68. metadata +6 -2
@@ -0,0 +1,44 @@
1
+ %PDF-1.7
2
+ %����
3
+ 1 0 obj
4
+ << /Extensions << /ADBE << /BaseVersion /1.7 /ExtensionLevel 3 >> >> /Pages 3 0 R /Type /Catalog >>
5
+ endobj
6
+ 2 0 obj
7
+ << /ModDate <0a09febf75d913e5eff6197f52f28b31321a43f49e0735a25a93dd77b1a2701e73ae05be0803747ca030ee4917544cbb> >>
8
+ endobj
9
+ 3 0 obj
10
+ << /Count 1 /Kids [ 4 0 R ] /MediaBox [ 0 0 612 446 ] /Type /Pages >>
11
+ endobj
12
+ 4 0 obj
13
+ << /Contents 5 0 R /Parent 3 0 R /Resources 6 0 R /Type /Page >>
14
+ endobj
15
+ 5 0 obj
16
+ << /Length 80 /Filter /FlateDecode >>
17
+ stream
18
+ \��sy%1����a��
19
+ �/���F�.�L:pb���C�` ��D�5 �� �کU"�ʇ�3�Fy/�9�<V��ڦ;�?5�S�8endstream
20
+ endobj
21
+ 6 0 obj
22
+ << /Font << /F1 7 0 R >> /ProcSet [ /PDF /Text ] >>
23
+ endobj
24
+ 7 0 obj
25
+ << /BaseFont /Helvetica /Name /F1 /Subtype /Type1 /Type /Font >>
26
+ endobj
27
+ 8 0 obj
28
+ << /CF << /StdCF << /AuthEvent /DocOpen /CFM /AESV3 /Length 32 >> >> /Filter /Standard /Length 256 /O <8034f99b1eff9e91054d7ee490155e22f65170f607d6a614236b089e602517d9a22abe7c1ea53f52dbc9ae8d9701a065> /OE <bdc5c4b46519af7b4041b7341f70e4d8b1c6087dc12d3f16f060313f18e73386> /P -4 /Perms <64028d6bceb0d927477ecf45e6be7f3f> /R 5 /StmF /StdCF /StrF /StdCF /U <5db7b7b5bd8bbe9fac28477756a930cb079e1dcbdcd3b50c1817d3de4ce5184b5189848832df0043f8e1fe19ff696a36> /UE <dc3fed7b473148238ba0689409edd3d80c91bd26802003c152594aa27a50a81e> /V 5 >>
29
+ endobj
30
+ xref
31
+ 0 9
32
+ 0000000000 65535 f
33
+ 0000000015 00000 n
34
+ 0000000130 00000 n
35
+ 0000000259 00000 n
36
+ 0000000344 00000 n
37
+ 0000000424 00000 n
38
+ 0000000574 00000 n
39
+ 0000000641 00000 n
40
+ 0000000721 00000 n
41
+ trailer << /Info 2 0 R /Root 1 0 R /Size 9 /ID [<6790ffa610024e78369114311fc0df96><da8e0d03302398724f68ef24a831285e>] /Encrypt 8 0 R >>
42
+ startxref
43
+ 1268
44
+ %%EOF
@@ -0,0 +1,43 @@
1
+ %PDF-1.7
2
+ %����
3
+ 1 0 obj
4
+ << /Extensions << /ADBE << /BaseVersion /1.7 /ExtensionLevel 3 >> >> /Pages 3 0 R /Type /Catalog >>
5
+ endobj
6
+ 2 0 obj
7
+ << /ModDate <d2aa32b8d87666bd29df499a424d2e6d23c0b475ebb67598733b5f06429470a37062d3d6a32fc9603301c5eb99a20605> >>
8
+ endobj
9
+ 3 0 obj
10
+ << /Count 1 /Kids [ 4 0 R ] /MediaBox [ 0 0 612 446 ] /Type /Pages >>
11
+ endobj
12
+ 4 0 obj
13
+ << /Contents 5 0 R /Parent 3 0 R /Resources 6 0 R /Type /Page >>
14
+ endobj
15
+ 5 0 obj
16
+ << /Length 80 /Filter /FlateDecode >>
17
+ stream
18
+ j��D3�(���"�ڛ�������xvY$B\4^Y����$��
19
+ endobj
20
+ 6 0 obj
21
+ << /Font << /F1 7 0 R >> /ProcSet [ /PDF /Text ] >>
22
+ endobj
23
+ 7 0 obj
24
+ << /BaseFont /Helvetica /Name /F1 /Subtype /Type1 /Type /Font >>
25
+ endobj
26
+ 8 0 obj
27
+ << /CF << /StdCF << /AuthEvent /DocOpen /CFM /AESV3 /Length 32 >> >> /Filter /Standard /Length 256 /O <95d20f6277a6955bf4bda243c2144e94889bd5fa4225a4cf4e0d496fa1ffa1d991e1a37d46e4afe9e2dfc207eba9ec53> /OE <432a7d086b4338f1020bbc5847e6dd4f49ce586ab2c5de0f5301450e45e3bb3e> /P -4 /Perms <7df865105074d365f2ce50407e8b6dc8> /R 5 /StmF /StdCF /StrF /StdCF /U <930a631e8b2b95ff6b024e9bde92cb73c5c43f0106ec4fb1c336e49608c0740d87395c6ea79b99ee07eeae5ffbacd031> /UE <3f7eb6b9a897049bfca85a5ae71470eca3dfedbe9101e8532b217e4d95bcf51a> /V 5 >>
28
+ endobj
29
+ xref
30
+ 0 9
31
+ 0000000000 65535 f
32
+ 0000000015 00000 n
33
+ 0000000130 00000 n
34
+ 0000000259 00000 n
35
+ 0000000344 00000 n
36
+ 0000000424 00000 n
37
+ 0000000574 00000 n
38
+ 0000000641 00000 n
39
+ 0000000721 00000 n
40
+ trailer << /Info 2 0 R /Root 1 0 R /Size 9 /ID [<6790ffa610024e78369114311fc0df96><dd2e6bd65a9735c0bef37d88d5291ff1>] /Encrypt 8 0 R >>
41
+ startxref
42
+ 1268
43
+ %%EOF
@@ -12,26 +12,10 @@ module HexaPDF
12
12
  def ca_certificate
13
13
  @ca_certificate ||=
14
14
  begin
15
- ca_name = OpenSSL::X509::Name.parse('/C=AT/O=HexaPDF/CN=HexaPDF Test Root CA')
16
-
17
- ca_cert = OpenSSL::X509::Certificate.new
18
- ca_cert.serial = 0
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
- name = OpenSSL::X509::Name.parse('/CN=RSA signer/DC=gettalong')
50
-
51
- signer_cert = OpenSSL::X509::Certificate.new
52
- signer_cert.serial = 2
53
- signer_cert.version = 2
54
- signer_cert.not_before = Time.now - 86400
55
- signer_cert.not_after = Time.now + 86400
56
- signer_cert.public_key = signer_key.public_key
57
- signer_cert.subject = name
58
- signer_cert.issuer = ca_certificate.subject
59
-
60
- extension_factory = OpenSSL::X509::ExtensionFactory.new
61
- extension_factory.subject_certificate = signer_cert
62
- extension_factory.issuer_certificate = ca_certificate
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
- signer_cert = OpenSSL::X509::Certificate.new
76
- signer_cert.serial = 3
77
- signer_cert.version = 2
78
- signer_cert.not_before = Time.now - 86400
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
- name = OpenSSL::X509::Name.parse('/CN=timestamp/DC=gettalong')
100
-
101
- signer_cert = OpenSSL::X509::Certificate.new
102
- signer_cert.serial = 3
103
- signer_cert.version = 2
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::V_ERR_SELF_SIGNED_CERT_IN_CHAIN,
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
@@ -16,13 +16,13 @@ describe HexaPDF::DigitalSignature::Signatures do
16
16
 
17
17
  it "iterates over all signature dictionaries" do
18
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)
19
+ @sig1.field_value = {k: :sig1}
20
+ @sig2.field_value = {k: :sig2}
21
+ assert_equal([{k: :sig1}, {k: :sig2}], @doc.signatures.to_a)
22
22
  end
23
23
 
24
24
  it "returns the number of signature dictionaries" do
25
- @sig1.field_value = :sig1
25
+ @sig1.field_value = {k: :sig1}
26
26
  assert_equal(1, @doc.signatures.count)
27
27
  end
28
28
 
@@ -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(4, @collector.children.size)
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
@@ -157,6 +162,24 @@ describe HexaPDF::Document::Layout do
157
162
  end
158
163
  end
159
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
+
160
183
  describe "inline_box" do
161
184
  it "takes a box as argument" do
162
185
  box = HexaPDF::Layout::Box.create(width: 10, height: 10)
@@ -228,11 +228,14 @@ describe HexaPDF::Encryption::StandardSecurityHandler do
228
228
  end
229
229
 
230
230
  it "fails if the /R value is incorrect" do
231
+ HexaPDF::Encryption::StandardEncryptionDictionary.field(:R).allowed_values << 7
231
232
  exp = assert_raises(HexaPDF::UnsupportedEncryptionError) do
232
- @handler.set_up_decryption({Filter: :Standard, V: 2, R: 5, O: 't' * 32, U: 't' * 32, P: 0,
233
+ @handler.set_up_decryption({Filter: :Standard, V: 2, R: 7, O: 't' * 32, U: 't' * 32, P: 0,
233
234
  Length: 128})
234
235
  end
235
- assert_match(/Invalid \/R value 5/i, exp.message)
236
+ assert_match(/Invalid \/R value 7/i, exp.message)
237
+ ensure
238
+ HexaPDF::Encryption::StandardEncryptionDictionary.field(:R).allowed_values.pop
236
239
  end
237
240
 
238
241
  it "fails if the supplied password is invalid" do
@@ -148,29 +148,36 @@ describe HexaPDF::Layout::Box do
148
148
  assert_equal(49.9999996, box.height)
149
149
  end
150
150
 
151
- it "fails if the box doesn't fit, position != :flow and its width is greater than the available width" do
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
152
159
  box = create_box(width: 101)
153
160
  assert(box.fit(100, 100, @frame).failure?)
154
161
  end
155
162
 
156
- it "fails if the box doesn't fit, position != :flow and its width is greater than the available width" do
163
+ it "fails if position != :flow and its width is greater than the available width" do
157
164
  box = create_box(height: 101)
158
165
  assert(box.fit(100, 100, @frame).failure?)
159
166
  end
160
167
 
161
- it "fails if its content width is zero" do
168
+ it "fails if position != :flow and the reserved width is greater than the width" do
162
169
  box = create_box(height: 100)
163
170
  box.style.padding = [0, 100]
164
171
  assert(box.fit(150, 150, @frame).failure?)
165
172
  end
166
173
 
167
- it "fails if its content height is zero" do
174
+ it "fails if position != :flow and the reserved height is greater than the height" do
168
175
  box = create_box(width: 100)
169
176
  box.style.padding = [100, 0]
170
177
  assert(box.fit(150, 150, @frame).failure?)
171
178
  end
172
179
 
173
- it "can use the #content_width/#content_height helper methods" do
180
+ it "can use the #update_content_width/#update_content_height helper methods" do
174
181
  box = create_box
175
182
  box.define_singleton_method(:fit_content) do |_aw, _ah, _frame|
176
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
- [0, 90],
321
+ [10, 90],
321
322
  [10, 80, 110, 110],
322
- [[[10, 10], [110, 10], [110, 80], [10, 80]]])
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
@@ -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 into the given frame" do
35
- ibox = inline_box(HexaPDF::Document.new.layout.text("test is going good", width: 20))
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(20, ibox.width)
38
- assert_equal(45, ibox.height)
39
- end
40
-
41
- it "automatically fits the provided box into a custom frame" do
42
- ibox = inline_box(HexaPDF::Document.new.layout.text("test is going good", width: 20))
43
- ibox.fit_wrapped_box(nil)
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 "fails if the height is not set explicitly and during fitting" do
54
- assert_raises(HexaPDF::Error) do
55
- inline_box(HexaPDF::Layout::Box.create(width: 10)).fit_wrapped_box(nil)
56
- end
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 = inline_box(doc.layout.text("", width: 20, margin: [15, 10]))
64
- box.fit_wrapped_box(nil)
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 -99785 cm\nQ\n", canvas.contents)
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
@@ -65,13 +65,13 @@ describe HexaPDF::Layout::ListBox do
65
65
  describe "fit" do
66
66
  [:default, :flow].each do |position|
67
67
  it "respects the set initial width, position #{position}" do
68
- box = create_box(children: @text_boxes[0, 2], width: 50, style: {position: position})
69
- check_box(box, 50, 80)
68
+ box = create_box(children: @text_boxes[0, 2], width: 55, style: {position: position})
69
+ check_box(box, 55, 80)
70
70
  end
71
71
 
72
72
  it "respects the set initial height, position #{position}" do
73
- box = create_box(children: @text_boxes[0, 2], height: 50, style: {position: position})
74
- check_box(box, 100, 40)
73
+ box = create_box(children: @text_boxes[0, 2], height: 55, style: {position: position})
74
+ check_box(box, 100, 55)
75
75
  end
76
76
 
77
77
  it "respects the set initial height even when it doesn't fit completely" do
@@ -123,7 +123,7 @@ describe HexaPDF::Layout::ListBox do
123
123
 
124
124
  it "fails if not even a part of the first list item fits" do
125
125
  box = create_box(children: @text_boxes[0, 2], height: 5)
126
- check_box(box, 100, 0, status: :failure)
126
+ check_box(box, 100, 5, status: :failure)
127
127
  end
128
128
 
129
129
  it "fails for unknown marker types" do
@@ -44,8 +44,13 @@ describe HexaPDF::Layout::PageStyle do
44
44
 
45
45
  it "works when no template is set" do
46
46
  style = HexaPDF::Layout::PageStyle.new
47
- page = style.create_page(@doc)
48
- assert_equal("", page.contents)
47
+ page1 = style.create_page(@doc)
48
+ frame1 = style.frame
49
+ assert_equal("", page1.contents)
50
+ assert_equal(523.275591, style.frame.width)
51
+
52
+ page2 = style.create_page(@doc)
53
+ refute_same(frame1, style.frame)
49
54
  end
50
55
 
51
56
  it "creates a default frame if none is set beforehand or during template execution" do