hexapdf 0.46.0 → 1.0.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 +83 -16
- data/lib/hexapdf/composer.rb +7 -0
- data/lib/hexapdf/configuration.rb +13 -0
- data/lib/hexapdf/content/parser.rb +3 -1
- data/lib/hexapdf/digital_signature/cms_handler.rb +13 -0
- data/lib/hexapdf/digital_signature/signature.rb +1 -1
- data/lib/hexapdf/digital_signature/signing/default_handler.rb +1 -0
- data/lib/hexapdf/document.rb +14 -3
- data/lib/hexapdf/encryption/standard_security_handler.rb +32 -26
- data/lib/hexapdf/font/cmap/writer.rb +58 -4
- data/lib/hexapdf/font/cmap.rb +7 -0
- data/lib/hexapdf/font/true_type_wrapper.rb +41 -16
- data/lib/hexapdf/importer.rb +1 -1
- data/lib/hexapdf/layout/table_box.rb +57 -10
- data/lib/hexapdf/layout/text_fragment.rb +2 -1
- data/lib/hexapdf/object.rb +1 -1
- data/lib/hexapdf/parser.rb +1 -1
- data/lib/hexapdf/reference.rb +1 -1
- data/lib/hexapdf/task/merge_acro_form.rb +164 -0
- data/lib/hexapdf/task/optimize.rb +4 -4
- data/lib/hexapdf/task.rb +1 -0
- data/lib/hexapdf/tokenizer.rb +2 -0
- data/lib/hexapdf/type/acro_form/appearance_generator.rb +8 -4
- data/lib/hexapdf/type/acro_form/form.rb +14 -24
- data/lib/hexapdf/type/acro_form/signature_field.rb +18 -7
- data/lib/hexapdf/type/acro_form/variable_text_field.rb +12 -4
- data/lib/hexapdf/type/actions/go_to.rb +1 -0
- data/lib/hexapdf/type/actions/go_to_r.rb +1 -0
- data/lib/hexapdf/type/actions/launch.rb +5 -1
- data/lib/hexapdf/type/annotation.rb +6 -1
- data/lib/hexapdf/type/annotations/markup_annotation.rb +14 -1
- data/lib/hexapdf/type/annotations/widget.rb +4 -2
- data/lib/hexapdf/type/catalog.rb +3 -0
- data/lib/hexapdf/type/cid_font.rb +4 -1
- data/lib/hexapdf/type/file_specification.rb +17 -14
- data/lib/hexapdf/type/font_descriptor.rb +4 -3
- data/lib/hexapdf/type/font_simple.rb +3 -1
- data/lib/hexapdf/type/font_true_type.rb +2 -0
- data/lib/hexapdf/type/font_type0.rb +1 -1
- data/lib/hexapdf/type/font_type1.rb +7 -0
- data/lib/hexapdf/type/font_type3.rb +0 -1
- data/lib/hexapdf/type/form.rb +5 -2
- data/lib/hexapdf/type/graphics_state_parameter.rb +7 -4
- data/lib/hexapdf/type/image.rb +8 -4
- data/lib/hexapdf/type/info.rb +2 -2
- data/lib/hexapdf/type/mark_information.rb +2 -2
- data/lib/hexapdf/type/optional_content_configuration.rb +1 -1
- data/lib/hexapdf/type/optional_content_membership.rb +1 -1
- data/lib/hexapdf/type/page.rb +5 -3
- data/lib/hexapdf/type/resources.rb +6 -6
- data/lib/hexapdf/type/viewer_preferences.rb +4 -3
- data/lib/hexapdf/version.rb +1 -1
- data/lib/hexapdf/writer.rb +1 -0
- data/test/data/standard-security-handler/bothpwd-aes-256bit-V5-R5.pdf +43 -0
- data/test/data/standard-security-handler/nopwd-aes-256bit-V5-R5.pdf +44 -0
- data/test/data/standard-security-handler/ownerpwd-aes-256bit-V5-R5.pdf +43 -0
- data/test/data/standard-security-handler/userpwd-aes-256bit-V5-R5.pdf +0 -0
- data/test/hexapdf/common_tokenizer_tests.rb +5 -0
- data/test/hexapdf/digital_signature/signing/test_default_handler.rb +6 -0
- data/test/hexapdf/digital_signature/test_cms_handler.rb +12 -7
- data/test/hexapdf/digital_signature/test_signature.rb +7 -0
- data/test/hexapdf/digital_signature/test_signatures.rb +12 -7
- data/test/hexapdf/encryption/test_standard_security_handler.rb +5 -2
- data/test/hexapdf/font/cmap/test_writer.rb +73 -16
- data/test/hexapdf/font/test_true_type_wrapper.rb +17 -3
- data/test/hexapdf/layout/test_list_box.rb +7 -7
- data/test/hexapdf/layout/test_table_box.rb +52 -0
- data/test/hexapdf/layout/test_text_fragment.rb +3 -3
- data/test/hexapdf/layout/test_text_layouter.rb +4 -2
- data/test/hexapdf/task/test_merge_acro_form.rb +104 -0
- data/test/hexapdf/task/test_optimize.rb +2 -0
- data/test/hexapdf/test_composer.rb +8 -0
- data/test/hexapdf/test_document.rb +12 -3
- data/test/hexapdf/test_importer.rb +7 -0
- data/test/hexapdf/test_parser.rb +7 -0
- data/test/hexapdf/test_writer.rb +19 -5
- data/test/hexapdf/type/acro_form/test_appearance_generator.rb +40 -23
- data/test/hexapdf/type/acro_form/test_form.rb +7 -8
- data/test/hexapdf/type/acro_form/test_signature_field.rb +3 -1
- data/test/hexapdf/type/acro_form/test_variable_text_field.rb +14 -1
- data/test/hexapdf/type/actions/test_launch.rb +6 -2
- data/test/hexapdf/type/annotations/test_widget.rb +4 -0
- data/test/hexapdf/type/test_font_type1.rb +5 -0
- data/test/hexapdf/type/test_form.rb +1 -1
- data/test/hexapdf/type/test_page.rb +7 -1
- metadata +8 -2
|
@@ -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 <aa1d1637459ea17c7fc9709f0c0c7b64761ff74e905b3765f33425776aa3010163cd5e898cfa7c5fc16159359375c323> >>
|
|
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<~S�)��9�M�$S�z��g�j���r�.R�"PO���_���X���^�GP�&z�g�*$�2SΈ�z�Kl��5ĩendstream
|
|
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 <c60934c0925e8d3ceebd0609102d9407dcf22d7fb3d87d030fce633af6d2ed99c1c3382bee5e8afc0d55ff8bca441d48> /OE <3fa2e3ecf34ddcb38b44af372a43268dda32111f58dc79da74d960b8fa206ead> /P -4 /Perms <b6c6ab65529f0a3322f03909e8a5547a> /R 5 /StmF /StdCF /StrF /StdCF /U <18fbd94777c28531c495a3116d273de9f8f3ed338b31c07687ca0ba06812842843765a66484d098194bcef0f9ecaaace> /UE <496a81bbb3207dfd12ddca4c16b60a411c6a18e638205824295e09afde826b4a> /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><ed6c0810cb0d19599ac62042a0487749>] /Encrypt 8 0 R >>
|
|
41
|
+
startxref
|
|
42
|
+
1268
|
|
43
|
+
%%EOF
|
|
@@ -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
|
|
Binary file
|
|
@@ -104,6 +104,11 @@ module CommonTokenizerTests
|
|
|
104
104
|
assert_raises(HexaPDF::MalformedPDFError) { @tokenizer.next_token }
|
|
105
105
|
end
|
|
106
106
|
|
|
107
|
+
it "next_token: fails on a closing parenthesis that is not part of a literal string" do
|
|
108
|
+
create_tokenizer(" )")
|
|
109
|
+
assert_raises(HexaPDF::MalformedPDFError) { @tokenizer.next_token }
|
|
110
|
+
end
|
|
111
|
+
|
|
107
112
|
it "next_token: fails on a missing greater than sign in a hex string" do
|
|
108
113
|
create_tokenizer("<ABCD")
|
|
109
114
|
assert_raises(HexaPDF::MalformedPDFError) { @tokenizer.next_token }
|
|
@@ -157,6 +157,12 @@ describe HexaPDF::DigitalSignature::Signing::DefaultHandler do
|
|
|
157
157
|
assert_same(@obj, @doc.catalog[:Perms][:DocMDP])
|
|
158
158
|
end
|
|
159
159
|
|
|
160
|
+
it "updates the document version if :pades signing is used" do
|
|
161
|
+
@handler.signature_type = :pades
|
|
162
|
+
@handler.finalize_objects(@field, @obj)
|
|
163
|
+
assert_equal('2.0', @doc.version)
|
|
164
|
+
end
|
|
165
|
+
|
|
160
166
|
it "fails if DocMDP should be set but there is already a signature" do
|
|
161
167
|
@handler.doc_mdp_permissions = :no_changes
|
|
162
168
|
2.times do
|
|
@@ -62,7 +62,7 @@ describe HexaPDF::DigitalSignature::CMSHandler do
|
|
|
62
62
|
@dict.contents = @pkcs7.to_der
|
|
63
63
|
@handler = HexaPDF::DigitalSignature::CMSHandler.new(@dict)
|
|
64
64
|
result = @handler.verify(@store)
|
|
65
|
-
assert_equal(
|
|
65
|
+
assert_equal(3, result.messages.size)
|
|
66
66
|
assert_equal(:error, result.messages.first.type)
|
|
67
67
|
assert_match(/Exactly one signer needed/, result.messages.first.content)
|
|
68
68
|
end
|
|
@@ -100,13 +100,13 @@ describe HexaPDF::DigitalSignature::CMSHandler do
|
|
|
100
100
|
|
|
101
101
|
it "verifies the signature itself" do
|
|
102
102
|
result = @handler.verify(@store)
|
|
103
|
-
assert_equal(:info, result.messages.
|
|
104
|
-
assert_match(/Signature valid/, result.messages.
|
|
103
|
+
assert_equal(:info, result.messages[-2].type)
|
|
104
|
+
assert_match(/Signature valid/, result.messages[-2].content)
|
|
105
105
|
|
|
106
106
|
@dict.signed_data = 'other data'
|
|
107
107
|
result = @handler.verify(@store)
|
|
108
|
-
assert_equal(:error, result.messages.
|
|
109
|
-
assert_match(/Signature verification failed/, result.messages.
|
|
108
|
+
assert_equal(:error, result.messages[-2].type)
|
|
109
|
+
assert_match(/Signature verification failed/, result.messages[-2].content)
|
|
110
110
|
end
|
|
111
111
|
|
|
112
112
|
it "verifies a timestamp signature" do
|
|
@@ -125,8 +125,13 @@ describe HexaPDF::DigitalSignature::CMSHandler do
|
|
|
125
125
|
@handler = HexaPDF::DigitalSignature::CMSHandler.new(@dict)
|
|
126
126
|
|
|
127
127
|
result = @handler.verify(@store)
|
|
128
|
-
assert_equal(:info, result.messages.
|
|
129
|
-
assert_match(/Signature valid/, result.messages.
|
|
128
|
+
assert_equal(:info, result.messages[-2].type)
|
|
129
|
+
assert_match(/Signature valid/, result.messages[-2].content)
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
it "provides information on the certificate chain" do
|
|
133
|
+
result = @handler.verify(@store)
|
|
134
|
+
assert_match(/RSA signer -> HexaPDF Test Root CA/, result.messages.last.content)
|
|
130
135
|
end
|
|
131
136
|
end
|
|
132
137
|
|
|
@@ -111,6 +111,13 @@ describe HexaPDF::DigitalSignature::Signature do
|
|
|
111
111
|
assert_equal((MINIMAL_PDF[0, 400] << MINIMAL_PDF[500, 333]).b, @sig.signed_data)
|
|
112
112
|
end
|
|
113
113
|
|
|
114
|
+
it "works for invalid offsets" do
|
|
115
|
+
doc = HexaPDF::Document.new(io: StringIO.new(MINIMAL_PDF))
|
|
116
|
+
@sig.document = doc
|
|
117
|
+
@sig[:ByteRange] = [0, 400, 9000, 333]
|
|
118
|
+
assert_equal(MINIMAL_PDF[0, 400], @sig.signed_data)
|
|
119
|
+
end
|
|
120
|
+
|
|
114
121
|
it "fails if the document isn't associated with an existing PDF file" do
|
|
115
122
|
assert_raises(HexaPDF::Error) { @sig.signed_data }
|
|
116
123
|
end
|
|
@@ -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
|
|
|
@@ -70,7 +70,8 @@ describe HexaPDF::DigitalSignature::Signatures do
|
|
|
70
70
|
end
|
|
71
71
|
@doc.signatures.add(@io, @handler, write_options: {update_fields: false})
|
|
72
72
|
sig = @doc.signatures.first
|
|
73
|
-
assert_equal([0, 925, 925 + sig[:Contents].size * 2 + 2,
|
|
73
|
+
assert_equal([0, 925, 925 + sig[:Contents].size * 2 + 2, 2455 + HexaPDF::VERSION.length],
|
|
74
|
+
sig[:ByteRange].value)
|
|
74
75
|
assert_equal(:sig, sig[:key])
|
|
75
76
|
assert_equal(:sig_field, @doc.acro_form.each_field.first[:key])
|
|
76
77
|
assert(sig.key?(:Contents))
|
|
@@ -132,14 +133,18 @@ describe HexaPDF::DigitalSignature::Signatures do
|
|
|
132
133
|
it "handles different xref section types correctly when determing the offsets" do
|
|
133
134
|
@doc.delete(7)
|
|
134
135
|
sig = @doc.signatures.add(@io, @handler, write_options: {update_fields: false})
|
|
135
|
-
|
|
136
|
+
l1 = 1030 + HexaPDF::VERSION.length
|
|
137
|
+
assert_equal([0, l1, l1 + sig[:Contents].size * 2 + 2, 2437 + HexaPDF::VERSION.length],
|
|
138
|
+
sig[:ByteRange].value)
|
|
136
139
|
end
|
|
137
140
|
|
|
138
141
|
it "works if the signature object is the last object of the xref section" do
|
|
139
142
|
field = @doc.acro_form(create: true).create_signature_field('Signature2')
|
|
140
143
|
field.create_widget(@doc.pages[0], Rect: [0, 0, 0, 0])
|
|
141
144
|
sig = @doc.signatures.add(@io, @handler, signature: field, write_options: {update_fields: false})
|
|
142
|
-
|
|
145
|
+
l1 = 3097 + HexaPDF::VERSION.length
|
|
146
|
+
assert_equal([0, l1, l1 + sig[:Contents].size * 2 + 2, 374 + HexaPDF::VERSION.length],
|
|
147
|
+
sig[:ByteRange].value)
|
|
143
148
|
end
|
|
144
149
|
|
|
145
150
|
it "allows writing to a file in addition to writing to an IO" do
|
|
@@ -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:
|
|
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
|
|
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
|
|
@@ -5,7 +5,7 @@ require 'hexapdf/font/cmap/writer'
|
|
|
5
5
|
|
|
6
6
|
describe HexaPDF::Font::CMap::Writer do
|
|
7
7
|
before do
|
|
8
|
-
@
|
|
8
|
+
@to_unicode_cmap_data = <<~EOF
|
|
9
9
|
/CIDInit /ProcSet findresource begin
|
|
10
10
|
12 dict begin
|
|
11
11
|
begincmap
|
|
@@ -32,35 +32,92 @@ describe HexaPDF::Font::CMap::Writer do
|
|
|
32
32
|
end
|
|
33
33
|
end
|
|
34
34
|
EOF
|
|
35
|
-
@
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
35
|
+
@cid_cmap_data = <<~EOF
|
|
36
|
+
%!PS-Adobe-3.0 Resource-CMap
|
|
37
|
+
%%DocumentNeededResources: ProcSet (CIDInit)
|
|
38
|
+
%%IncludeResource: ProcSet (CIDInit)
|
|
39
|
+
%%BeginResource: CMap (Custom)
|
|
40
|
+
%%Title: (Custom Adobe Identity 0)
|
|
41
|
+
%%Version: 1
|
|
42
|
+
/CIDInit /ProcSet findresource begin
|
|
43
|
+
12 dict begin
|
|
44
|
+
begincmap
|
|
45
|
+
/CIDSystemInfo 3 dict dup begin
|
|
46
|
+
/Registry (Adobe) def
|
|
47
|
+
/Ordering (Identity) def
|
|
48
|
+
/Supplement 0 def
|
|
49
|
+
end def
|
|
50
|
+
/CMapName /Custom def
|
|
51
|
+
/CMapType 1 def
|
|
52
|
+
/CMapVersion 1 def
|
|
53
|
+
/WMode 0 def
|
|
54
|
+
1 begincodespacerange
|
|
55
|
+
<0000> <FFFF>
|
|
56
|
+
endcodespacerange
|
|
57
|
+
1 begincidchar
|
|
58
|
+
<0060> 144
|
|
59
|
+
endcidchar
|
|
60
|
+
1 begincidrange
|
|
61
|
+
<0000><005E> 32
|
|
62
|
+
endcidrange
|
|
63
|
+
endcmap
|
|
64
|
+
CMapName currentdict /CMap defineresource pop
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
%%EndResource
|
|
68
|
+
%%EOF
|
|
69
|
+
EOF
|
|
70
|
+
|
|
71
|
+
@to_unicode_mapping = []
|
|
72
|
+
@cid_mapping = []
|
|
73
|
+
0x00.upto(0x5e) do |i|
|
|
74
|
+
@to_unicode_mapping << [i, 0x20 + i]
|
|
75
|
+
@cid_mapping << [i, 0x20 + i]
|
|
76
|
+
end
|
|
77
|
+
@to_unicode_mapping << [0x60, 0x90]
|
|
78
|
+
@cid_mapping << [0x60, 0x90]
|
|
79
|
+
0x1379.upto(0x137B) do |i|
|
|
80
|
+
@to_unicode_mapping << [i, 0x90FE + i - 0x1379]
|
|
81
|
+
end
|
|
82
|
+
@to_unicode_mapping << [0x3A51, 0x2003E]
|
|
40
83
|
end
|
|
41
84
|
|
|
42
85
|
describe "create_to_unicode_cmap" do
|
|
43
86
|
it "creates a correct CMap file" do
|
|
44
|
-
assert_equal(@
|
|
87
|
+
assert_equal(@to_unicode_cmap_data,
|
|
88
|
+
HexaPDF::Font::CMap.create_to_unicode_cmap(@to_unicode_mapping))
|
|
45
89
|
end
|
|
46
90
|
|
|
47
91
|
it "works if the last item is a range" do
|
|
48
|
-
@
|
|
49
|
-
@
|
|
50
|
-
@
|
|
51
|
-
assert_equal(@
|
|
92
|
+
@to_unicode_mapping.pop
|
|
93
|
+
@to_unicode_cmap_data.sub!(/2 beginbfchar/, '1 beginbfchar')
|
|
94
|
+
@to_unicode_cmap_data.sub!(/<3A51><d840dc3e>\n/, '')
|
|
95
|
+
assert_equal(@to_unicode_cmap_data,
|
|
96
|
+
HexaPDF::Font::CMap.create_to_unicode_cmap(@to_unicode_mapping))
|
|
52
97
|
end
|
|
53
98
|
|
|
54
99
|
it "works with only ranges" do
|
|
55
|
-
@
|
|
56
|
-
@
|
|
57
|
-
@
|
|
58
|
-
assert_equal(@
|
|
100
|
+
@to_unicode_mapping.delete_at(-1)
|
|
101
|
+
@to_unicode_mapping.delete_at(0x5f)
|
|
102
|
+
@to_unicode_cmap_data.sub!(/\n2 beginbfchar.*endbfchar/m, '')
|
|
103
|
+
assert_equal(@to_unicode_cmap_data,
|
|
104
|
+
HexaPDF::Font::CMap.create_to_unicode_cmap(@to_unicode_mapping))
|
|
59
105
|
end
|
|
60
106
|
|
|
61
107
|
it "returns an empty CMap if the mapping is empty" do
|
|
62
|
-
assert_equal(@
|
|
108
|
+
assert_equal(@to_unicode_cmap_data.sub(/\d+ beginbfchar.*endbfrange/m, ''),
|
|
63
109
|
HexaPDF::Font::CMap.create_to_unicode_cmap([]))
|
|
64
110
|
end
|
|
65
111
|
end
|
|
112
|
+
|
|
113
|
+
describe "create_cid_cmap" do
|
|
114
|
+
it "creates a correct CMap file" do
|
|
115
|
+
assert_equal(@cid_cmap_data, HexaPDF::Font::CMap.create_cid_cmap(@cid_mapping))
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
it "returns an empty CMap if the mapping is empty" do
|
|
119
|
+
assert_equal(@cid_cmap_data.sub(/\d+ begincidchar.*endcidrange/m, ''),
|
|
120
|
+
HexaPDF::Font::CMap.create_cid_cmap([]))
|
|
121
|
+
end
|
|
122
|
+
end
|
|
66
123
|
end
|
|
@@ -71,6 +71,12 @@ describe HexaPDF::Font::TrueTypeWrapper do
|
|
|
71
71
|
glyph.inspect)
|
|
72
72
|
end
|
|
73
73
|
|
|
74
|
+
it "caches glyphs based on the id and string" do
|
|
75
|
+
glyph = @font_wrapper.glyph(17)
|
|
76
|
+
assert_same(glyph, @font_wrapper.glyph(17))
|
|
77
|
+
refute_same(glyph, @font_wrapper.glyph(17, "1"))
|
|
78
|
+
end
|
|
79
|
+
|
|
74
80
|
it "invokes font.on_missing_glyph for missing glyphs" do
|
|
75
81
|
glyph = @font_wrapper.glyph(9999)
|
|
76
82
|
assert_kind_of(HexaPDF::Font::InvalidGlyph, glyph)
|
|
@@ -99,14 +105,18 @@ describe HexaPDF::Font::TrueTypeWrapper do
|
|
|
99
105
|
assert_equal([1].pack('n'), code)
|
|
100
106
|
code = @font_wrapper.encode(@font_wrapper.glyph(10))
|
|
101
107
|
assert_equal([2].pack('n'), code)
|
|
108
|
+
code = @font_wrapper.encode(@font_wrapper.glyph(10, "o"))
|
|
109
|
+
assert_equal([3].pack('n'), code)
|
|
102
110
|
end
|
|
103
111
|
|
|
104
112
|
it "returns the encoded glyph ID for fonts that are not subset" do
|
|
105
113
|
@font_wrapper = HexaPDF::Font::TrueTypeWrapper.new(@doc, @font, subset: false)
|
|
106
114
|
code = @font_wrapper.encode(@font_wrapper.glyph(3))
|
|
107
|
-
assert_equal([
|
|
115
|
+
assert_equal([1].pack('n'), code)
|
|
108
116
|
code = @font_wrapper.encode(@font_wrapper.glyph(10))
|
|
109
|
-
assert_equal([
|
|
117
|
+
assert_equal([2].pack('n'), code)
|
|
118
|
+
code = @font_wrapper.encode(@font_wrapper.glyph(10, "o"))
|
|
119
|
+
assert_equal([3].pack('n'), code)
|
|
110
120
|
end
|
|
111
121
|
|
|
112
122
|
it "raises an error if an InvalidGlyph is encoded" do
|
|
@@ -180,14 +190,18 @@ describe HexaPDF::Font::TrueTypeWrapper do
|
|
|
180
190
|
it "with fonts that are not subset (only differences to other case)" do
|
|
181
191
|
@font_wrapper = HexaPDF::Font::TrueTypeWrapper.new(@doc, @font, subset: false)
|
|
182
192
|
@font_wrapper.encode(@font_wrapper.glyph(3))
|
|
193
|
+
@font_wrapper.encode(@font_wrapper.glyph(3, "-"))
|
|
183
194
|
glyph = @font_wrapper.decode_utf8('H').first
|
|
184
195
|
@font_wrapper.encode(glyph)
|
|
185
196
|
@doc.dispatch_message(:complete_objects)
|
|
186
197
|
|
|
187
198
|
dict = @font_wrapper.pdf_object
|
|
188
199
|
|
|
189
|
-
assert_equal(HexaPDF::Font::CMap.create_to_unicode_cmap([[
|
|
200
|
+
assert_equal(HexaPDF::Font::CMap.create_to_unicode_cmap([[1, ' '.ord], [2, '-'.ord],
|
|
201
|
+
[3, 'H'.ord]]),
|
|
190
202
|
dict[:ToUnicode].stream)
|
|
203
|
+
assert_equal(HexaPDF::Font::CMap.create_cid_cmap([[1, 3], [2, 3], [3, glyph.id]]),
|
|
204
|
+
dict[:Encoding].stream)
|
|
191
205
|
assert_equal([glyph.id, [glyph.width]], dict[:DescendantFonts][0][:W].value)
|
|
192
206
|
end
|
|
193
207
|
end
|
|
@@ -185,7 +185,7 @@ describe HexaPDF::Layout::ListBox do
|
|
|
185
185
|
[:set_font_and_size, [:F1, 11]],
|
|
186
186
|
[:set_device_gray_non_stroking_color, [0.5]],
|
|
187
187
|
[:begin_text],
|
|
188
|
-
[:
|
|
188
|
+
[:move_text, [1.15, 92.487]],
|
|
189
189
|
[:show_text, ["\x95".b]],
|
|
190
190
|
[:end_text],
|
|
191
191
|
[:restore_graphics_state],
|
|
@@ -197,7 +197,7 @@ describe HexaPDF::Layout::ListBox do
|
|
|
197
197
|
[:set_font_and_size, [:F1, 11]],
|
|
198
198
|
[:set_device_gray_non_stroking_color, [0.5]],
|
|
199
199
|
[:begin_text],
|
|
200
|
-
[:
|
|
200
|
+
[:move_text, [1.15, 82.487]],
|
|
201
201
|
[:show_text, ["\x95".b]],
|
|
202
202
|
[:end_text],
|
|
203
203
|
[:restore_graphics_state],
|
|
@@ -219,7 +219,7 @@ describe HexaPDF::Layout::ListBox do
|
|
|
219
219
|
[:set_text_rise, [-6.111111]],
|
|
220
220
|
[:set_device_gray_non_stroking_color, [0.5]],
|
|
221
221
|
[:begin_text],
|
|
222
|
-
[:
|
|
222
|
+
[:move_text, [0.1985, 100]],
|
|
223
223
|
[:show_text, ["m".b]],
|
|
224
224
|
[:end_text],
|
|
225
225
|
[:restore_graphics_state],
|
|
@@ -241,7 +241,7 @@ describe HexaPDF::Layout::ListBox do
|
|
|
241
241
|
[:set_text_rise, [-6.111111]],
|
|
242
242
|
[:set_device_gray_non_stroking_color, [0.5]],
|
|
243
243
|
[:begin_text],
|
|
244
|
-
[:
|
|
244
|
+
[:move_text, [0.8145, 100]],
|
|
245
245
|
[:show_text, ["n".b]],
|
|
246
246
|
[:end_text],
|
|
247
247
|
[:restore_graphics_state],
|
|
@@ -263,7 +263,7 @@ describe HexaPDF::Layout::ListBox do
|
|
|
263
263
|
[:set_font_and_size, [:F1, 11]],
|
|
264
264
|
[:set_device_gray_non_stroking_color, [0.5]],
|
|
265
265
|
[:begin_text],
|
|
266
|
-
[:
|
|
266
|
+
[:move_text, [6.75, 92.487]],
|
|
267
267
|
[:show_text, ["1.".b]],
|
|
268
268
|
[:end_text],
|
|
269
269
|
[:restore_graphics_state],
|
|
@@ -275,7 +275,7 @@ describe HexaPDF::Layout::ListBox do
|
|
|
275
275
|
[:set_font_and_size, [:F1, 11]],
|
|
276
276
|
[:set_device_gray_non_stroking_color, [0.5]],
|
|
277
277
|
[:begin_text],
|
|
278
|
-
[:
|
|
278
|
+
[:move_text, [6.75, 82.487]],
|
|
279
279
|
[:show_text, ["2.".b]],
|
|
280
280
|
[:end_text],
|
|
281
281
|
[:restore_graphics_state],
|
|
@@ -314,7 +314,7 @@ describe HexaPDF::Layout::ListBox do
|
|
|
314
314
|
[:save_graphics_state],
|
|
315
315
|
[:set_font_and_size, [:F1, 10]],
|
|
316
316
|
[:begin_text],
|
|
317
|
-
[:
|
|
317
|
+
[:move_text, [1.5, 93.17]],
|
|
318
318
|
[:show_text, ["\x95".b]],
|
|
319
319
|
[:end_text],
|
|
320
320
|
[:restore_graphics_state],
|
|
@@ -511,6 +511,58 @@ describe HexaPDF::Layout::TableBox do
|
|
|
511
511
|
[0, 66, 39.75, 22], [39.75, 66, 39.75, 22], [79.5, 44, 39.75, 44], [119.25, 66, 39.75, 22]])
|
|
512
512
|
end
|
|
513
513
|
|
|
514
|
+
describe "row spans" do
|
|
515
|
+
# ----------
|
|
516
|
+
# | a | b |
|
|
517
|
+
# | a | |
|
|
518
|
+
# | a |----|
|
|
519
|
+
# | a | c |
|
|
520
|
+
# | a | |
|
|
521
|
+
# ----------
|
|
522
|
+
it "works if content of a row span cell is larger than the rows" do
|
|
523
|
+
cells = [[{row_span: 2, content: @fixed_size_boxes[0..2]}, @fixed_size_boxes[3]],
|
|
524
|
+
[@fixed_size_boxes[4]]]
|
|
525
|
+
check_box(create_box(cells: cells, cell_style: {padding: 0, border: {width: 0}}),
|
|
526
|
+
:success, 160, 30,
|
|
527
|
+
[[0, 0, 80, 30], [80, 0, 80, 15], [0, 0, 80, 30], [80, 15, 80, 15]])
|
|
528
|
+
end
|
|
529
|
+
|
|
530
|
+
# ----------
|
|
531
|
+
# | a | b |
|
|
532
|
+
# | |----|
|
|
533
|
+
# | | c |
|
|
534
|
+
# ----------
|
|
535
|
+
it "works if content of a row span cell is smaller than the rows" do
|
|
536
|
+
cells = [[{row_span: 2, content: @fixed_size_boxes[0]}, @fixed_size_boxes[3]],
|
|
537
|
+
[@fixed_size_boxes[4]]]
|
|
538
|
+
check_box(create_box(cells: cells, cell_style: {padding: 0, border: {width: 0}}),
|
|
539
|
+
:success, 160, 20,
|
|
540
|
+
[[0, 0, 80, 20], [80, 0, 80, 10], [0, 0, 80, 20], [80, 10, 80, 10]])
|
|
541
|
+
end
|
|
542
|
+
|
|
543
|
+
# -----------------
|
|
544
|
+
# | a | b | c | d |
|
|
545
|
+
# | a | b |---| d |
|
|
546
|
+
# | a | b | e | |
|
|
547
|
+
# | a | | e | |
|
|
548
|
+
# --------| e | |
|
|
549
|
+
# | f | g | e | |
|
|
550
|
+
# ----------------
|
|
551
|
+
it "works if multiple, possibly overlapping row spans are involved" do
|
|
552
|
+
cells = [[{row_span: 2, content: @fixed_size_boxes[0..2]},
|
|
553
|
+
{row_span: 2, content: @fixed_size_boxes[3..4]},
|
|
554
|
+
@fixed_size_boxes[5],
|
|
555
|
+
{row_span: 3, content: @fixed_size_boxes[6..7]}],
|
|
556
|
+
[{row_span: 2, content: @fixed_size_boxes[8, 3]}],
|
|
557
|
+
[@fixed_size_boxes[11], @fixed_size_boxes[12]]]
|
|
558
|
+
check_box(create_box(cells: cells, cell_style: {padding: 0, border: {width: 0}}),
|
|
559
|
+
:success, 160, 40,
|
|
560
|
+
[[0, 0, 40, 30], [40, 0, 40, 30], [80, 0, 40, 10], [120, 0, 40, 40],
|
|
561
|
+
[0, 0, 40, 30], [40, 0, 40, 30], [80, 10, 40, 30], [120, 0, 40, 40],
|
|
562
|
+
[0, 30, 40, 10], [40, 30, 40, 10], [80, 10, 40, 30], [120, 0, 40, 40]])
|
|
563
|
+
end
|
|
564
|
+
end
|
|
565
|
+
|
|
514
566
|
it "fits a table with header rows" do
|
|
515
567
|
result = [[0, 0, 80, 10], [80, 0, 80, 10], [0, 10, 80, 10], [80, 10, 80, 10]]
|
|
516
568
|
header = lambda {|_| [@fixed_size_boxes[10, 2], @fixed_size_boxes[12, 2]] }
|
|
@@ -139,7 +139,7 @@ describe HexaPDF::Layout::TextFragment do
|
|
|
139
139
|
[:set_text_rise, [2]],
|
|
140
140
|
*middle,
|
|
141
141
|
[:begin_text],
|
|
142
|
-
[:
|
|
142
|
+
[:move_text, [10, 15]],
|
|
143
143
|
[:show_text, ['!']],
|
|
144
144
|
*back,
|
|
145
145
|
].compact
|
|
@@ -156,7 +156,7 @@ describe HexaPDF::Layout::TextFragment do
|
|
|
156
156
|
@canvas = @doc.pages.add.canvas
|
|
157
157
|
@fragment.draw(@canvas, 10, 15, ignore_text_properties: true)
|
|
158
158
|
assert_operators(@canvas.contents, [[:begin_text],
|
|
159
|
-
[:
|
|
159
|
+
[:move_text, [10, 15]]])
|
|
160
160
|
end
|
|
161
161
|
|
|
162
162
|
describe "uses an appropriate text position setter" do
|
|
@@ -188,7 +188,7 @@ describe HexaPDF::Layout::TextFragment do
|
|
|
188
188
|
it "horizontal and vertical movement" do
|
|
189
189
|
@fragment.draw(@canvas, 10, 10, ignore_text_properties: true)
|
|
190
190
|
assert_operators(@canvas.contents, [[:begin_text],
|
|
191
|
-
[:
|
|
191
|
+
[:move_text, [10, 10]]])
|
|
192
192
|
end
|
|
193
193
|
end
|
|
194
194
|
|
|
@@ -743,10 +743,12 @@ describe HexaPDF::Layout::TextLayouter do
|
|
|
743
743
|
result = processor.recorded_ops
|
|
744
744
|
leading = (result.select {|name, _| name == :set_leading } || [0]).map(&:last).flatten.first
|
|
745
745
|
pos = [0, 0]
|
|
746
|
-
result.select!
|
|
747
|
-
|
|
746
|
+
result.select! do |name, _|
|
|
747
|
+
name == :set_text_matrix || name == :move_text || name == :move_text_next_line
|
|
748
|
+
end.map! do |name, ops|
|
|
748
749
|
case name
|
|
749
750
|
when :set_text_matrix then pos = ops[-2, 2]
|
|
751
|
+
when :move_text then pos = ops
|
|
750
752
|
when :move_text_next_line then pos[1] -= leading
|
|
751
753
|
end
|
|
752
754
|
pos.dup
|