hexapdf 0.26.2 → 0.28.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 +115 -1
- data/README.md +1 -1
- data/examples/013-text_layouter_shapes.rb +8 -8
- data/examples/016-frame_automatic_box_placement.rb +3 -3
- data/examples/017-frame_text_flow.rb +3 -3
- data/examples/019-acro_form.rb +14 -3
- data/examples/020-column_box.rb +3 -3
- data/examples/023-images.rb +30 -0
- data/lib/hexapdf/cli/info.rb +5 -1
- data/lib/hexapdf/cli/inspect.rb +2 -2
- data/lib/hexapdf/cli/split.rb +8 -8
- data/lib/hexapdf/cli/watermark.rb +2 -2
- data/lib/hexapdf/configuration.rb +3 -2
- data/lib/hexapdf/content/canvas.rb +8 -3
- data/lib/hexapdf/dictionary.rb +4 -17
- data/lib/hexapdf/document/destinations.rb +42 -5
- data/lib/hexapdf/document/signatures.rb +265 -48
- data/lib/hexapdf/document.rb +6 -10
- data/lib/hexapdf/filter/ascii85_decode.rb +1 -1
- data/lib/hexapdf/importer.rb +35 -27
- data/lib/hexapdf/layout/list_box.rb +1 -5
- data/lib/hexapdf/object.rb +5 -0
- data/lib/hexapdf/parser.rb +14 -0
- data/lib/hexapdf/revision.rb +15 -12
- data/lib/hexapdf/revisions.rb +7 -1
- data/lib/hexapdf/tokenizer.rb +15 -9
- data/lib/hexapdf/type/acro_form/appearance_generator.rb +174 -128
- data/lib/hexapdf/type/acro_form/button_field.rb +5 -3
- data/lib/hexapdf/type/acro_form/choice_field.rb +2 -0
- data/lib/hexapdf/type/acro_form/field.rb +11 -5
- data/lib/hexapdf/type/acro_form/form.rb +61 -8
- data/lib/hexapdf/type/acro_form/signature_field.rb +2 -0
- data/lib/hexapdf/type/acro_form/text_field.rb +12 -2
- data/lib/hexapdf/type/annotations/widget.rb +3 -0
- data/lib/hexapdf/type/catalog.rb +1 -1
- data/lib/hexapdf/type/font_true_type.rb +14 -0
- data/lib/hexapdf/type/object_stream.rb +2 -2
- data/lib/hexapdf/type/outline.rb +19 -1
- data/lib/hexapdf/type/outline_item.rb +72 -14
- data/lib/hexapdf/type/page.rb +95 -64
- data/lib/hexapdf/type/resources.rb +13 -17
- data/lib/hexapdf/type/signature/adbe_pkcs7_detached.rb +16 -2
- data/lib/hexapdf/type/signature.rb +10 -0
- data/lib/hexapdf/version.rb +1 -1
- data/lib/hexapdf/writer.rb +5 -3
- data/test/hexapdf/content/test_canvas.rb +5 -0
- data/test/hexapdf/document/test_destinations.rb +41 -0
- data/test/hexapdf/document/test_pages.rb +2 -2
- data/test/hexapdf/document/test_signatures.rb +139 -19
- data/test/hexapdf/encryption/test_aes.rb +1 -1
- data/test/hexapdf/filter/test_predictor.rb +0 -1
- data/test/hexapdf/layout/test_box.rb +2 -1
- data/test/hexapdf/layout/test_column_box.rb +1 -1
- data/test/hexapdf/layout/test_list_box.rb +1 -1
- data/test/hexapdf/test_document.rb +2 -8
- data/test/hexapdf/test_importer.rb +27 -6
- data/test/hexapdf/test_parser.rb +19 -2
- data/test/hexapdf/test_revision.rb +15 -14
- data/test/hexapdf/test_revisions.rb +63 -12
- data/test/hexapdf/test_stream.rb +1 -1
- data/test/hexapdf/test_tokenizer.rb +10 -1
- data/test/hexapdf/test_writer.rb +11 -3
- data/test/hexapdf/type/acro_form/test_appearance_generator.rb +135 -56
- data/test/hexapdf/type/acro_form/test_button_field.rb +6 -1
- data/test/hexapdf/type/acro_form/test_choice_field.rb +4 -0
- data/test/hexapdf/type/acro_form/test_field.rb +4 -4
- data/test/hexapdf/type/acro_form/test_form.rb +65 -0
- data/test/hexapdf/type/acro_form/test_signature_field.rb +4 -0
- data/test/hexapdf/type/acro_form/test_text_field.rb +13 -0
- data/test/hexapdf/type/signature/common.rb +54 -0
- data/test/hexapdf/type/signature/test_adbe_pkcs7_detached.rb +21 -0
- data/test/hexapdf/type/test_catalog.rb +5 -2
- data/test/hexapdf/type/test_font_true_type.rb +20 -0
- data/test/hexapdf/type/test_object_stream.rb +2 -1
- data/test/hexapdf/type/test_outline.rb +4 -1
- data/test/hexapdf/type/test_outline_item.rb +62 -1
- data/test/hexapdf/type/test_page.rb +103 -45
- data/test/hexapdf/type/test_page_tree_node.rb +4 -2
- data/test/hexapdf/type/test_resources.rb +0 -5
- data/test/hexapdf/type/test_signature.rb +8 -0
- data/test/test_helper.rb +1 -1
- metadata +61 -4
|
@@ -10,6 +10,10 @@ describe HexaPDF::Type::AcroForm::ButtonField do
|
|
|
10
10
|
@field = @doc.add({FT: :Btn, T: 'button'}, type: :XXAcroFormField, subtype: :Btn)
|
|
11
11
|
end
|
|
12
12
|
|
|
13
|
+
it "identifies as an :XXAcroFormField type" do
|
|
14
|
+
assert_equal(:XXAcroFormField, @field.type)
|
|
15
|
+
end
|
|
16
|
+
|
|
13
17
|
it "can be initialized as push button" do
|
|
14
18
|
@field.initialize_as_push_button
|
|
15
19
|
assert_nil(@field[:V])
|
|
@@ -232,6 +236,7 @@ describe HexaPDF::Type::AcroForm::ButtonField do
|
|
|
232
236
|
@field.create_appearances
|
|
233
237
|
yes = widget.appearance_dict.normal_appearance[:Yes]
|
|
234
238
|
off = widget.appearance_dict.normal_appearance[:Off]
|
|
239
|
+
widget.appearance_dict.normal_appearance[:Yes] = HexaPDF::Reference.new(yes.oid)
|
|
235
240
|
@field.create_appearances
|
|
236
241
|
assert_same(yes, widget.appearance_dict.normal_appearance[:Yes])
|
|
237
242
|
assert_same(off, widget.appearance_dict.normal_appearance[:Off])
|
|
@@ -252,7 +257,7 @@ describe HexaPDF::Type::AcroForm::ButtonField do
|
|
|
252
257
|
refute_same(yes, widget.appearance_dict.normal_appearance[:Yes])
|
|
253
258
|
end
|
|
254
259
|
|
|
255
|
-
it "fails for
|
|
260
|
+
it "fails for push buttons as they are not implemented yet" do
|
|
256
261
|
@field.flag(:push_button)
|
|
257
262
|
@field.create_widget(@doc.pages.add, Rect: [0, 0, 0, 0])
|
|
258
263
|
assert_raises(HexaPDF::Error) { @field.create_appearances }
|
|
@@ -10,6 +10,10 @@ describe HexaPDF::Type::AcroForm::ChoiceField do
|
|
|
10
10
|
@field = @doc.add({FT: :Ch, T: 'choice'}, type: :XXAcroFormField, subtype: :Ch)
|
|
11
11
|
end
|
|
12
12
|
|
|
13
|
+
it "identifies as an :XXAcroFormField type" do
|
|
14
|
+
assert_equal(:XXAcroFormField, @field.type)
|
|
15
|
+
end
|
|
16
|
+
|
|
13
17
|
it "can be initialized as list box" do
|
|
14
18
|
@field.initialize_as_list_box
|
|
15
19
|
assert_nil(@field[:V])
|
|
@@ -111,14 +111,14 @@ describe HexaPDF::Type::AcroForm::Field do
|
|
|
111
111
|
@field[:Subtype] = :Widget
|
|
112
112
|
@field[:Rect] = [0, 0, 0, 0]
|
|
113
113
|
widgets = @field.each_widget.to_a
|
|
114
|
-
assert_kind_of(HexaPDF::Type::Annotations::Widget,
|
|
114
|
+
assert_kind_of(HexaPDF::Type::Annotations::Widget, widgets.first)
|
|
115
115
|
assert_same(@field.data, widgets.first.data)
|
|
116
116
|
end
|
|
117
117
|
|
|
118
118
|
it "yields all widgets in the /Kids array" do
|
|
119
119
|
@field[:Kids] = [{Subtype: :Widget, Rect: [0, 0, 0, 0], X: 1}]
|
|
120
120
|
widgets = @field.each_widget.to_a
|
|
121
|
-
assert_kind_of(HexaPDF::Type::Annotations::Widget,
|
|
121
|
+
assert_kind_of(HexaPDF::Type::Annotations::Widget, widgets.first)
|
|
122
122
|
assert_equal(1, widgets.first[:X])
|
|
123
123
|
end
|
|
124
124
|
|
|
@@ -128,8 +128,8 @@ describe HexaPDF::Type::AcroForm::Field do
|
|
|
128
128
|
@doc.add({T: "b", Subtype: :Widget, Rect: [0, 0, 0, 0]}, type: :XXAcroFormField) <<
|
|
129
129
|
@doc.add({T: "a", X: 1, Subtype: :Widget, Rect: [0, 0, 0, 0]}, type: :XXAcroFormField)
|
|
130
130
|
|
|
131
|
-
widgets = @field.each_widget.to_a
|
|
132
|
-
assert_kind_of(HexaPDF::Type::Annotations::Widget,
|
|
131
|
+
widgets = @field.each_widget(direct_only: false).to_a
|
|
132
|
+
assert_kind_of(HexaPDF::Type::Annotations::Widget, widgets.first)
|
|
133
133
|
assert_equal(1, widgets.first[:X])
|
|
134
134
|
end
|
|
135
135
|
|
|
@@ -327,6 +327,12 @@ describe HexaPDF::Type::AcroForm::Form do
|
|
|
327
327
|
assert_equal(1, result.size)
|
|
328
328
|
assert(@doc.catalog.key?(:AcroForm))
|
|
329
329
|
end
|
|
330
|
+
|
|
331
|
+
it "returns the fields that could not be flattened" do
|
|
332
|
+
@cb.create_appearances
|
|
333
|
+
result = @acro_form.flatten(create_appearances: false)
|
|
334
|
+
assert_equal([@tf], result)
|
|
335
|
+
end
|
|
330
336
|
end
|
|
331
337
|
|
|
332
338
|
describe "perform_validation" do
|
|
@@ -349,6 +355,65 @@ describe HexaPDF::Type::AcroForm::Form do
|
|
|
349
355
|
assert_equal("0.0 g /F1 0 Tf", @acro_form[:DA])
|
|
350
356
|
end
|
|
351
357
|
|
|
358
|
+
describe "field hierarchy validation" do
|
|
359
|
+
before do
|
|
360
|
+
@acro_form[:Fields] = [
|
|
361
|
+
nil,
|
|
362
|
+
HexaPDF::Object.new(nil),
|
|
363
|
+
5,
|
|
364
|
+
HexaPDF::Object.new(5),
|
|
365
|
+
@doc.add({T: :Tx1}),
|
|
366
|
+
@doc.add({T: :Tx2, Kids: [nil, @doc.add({Subtype: :Widget})]}),
|
|
367
|
+
@doc.add({T: :Tx3, FT: :Tx, Kids: [@doc.add({T: :Tx4}),
|
|
368
|
+
[:nothing],
|
|
369
|
+
@doc.add({T: :Tx5, Kids: [@doc.add({T: :Tx6})]})]}),
|
|
370
|
+
]
|
|
371
|
+
@acro_form[:Fields][6][:Kids][0][:Parent] = @acro_form[:Fields][6]
|
|
372
|
+
@acro_form[:Fields][6][:Kids][2][:Parent] = @acro_form[:Fields][6]
|
|
373
|
+
@acro_form[:Fields][6][:Kids][2][:Kids][0][:Parent] = @acro_form[:Fields][6][:Kids][2]
|
|
374
|
+
end
|
|
375
|
+
|
|
376
|
+
it "removes invalid objects from the field hierarchy" do
|
|
377
|
+
assert(@acro_form.validate)
|
|
378
|
+
assert_equal([:Tx1, :Tx2, :Tx3, :Tx4, :Tx5, :Tx6],
|
|
379
|
+
@acro_form.each_field(terminal_only: false).map {|f| f[:T] })
|
|
380
|
+
end
|
|
381
|
+
|
|
382
|
+
it "handles missing /Parent fields" do
|
|
383
|
+
@acro_form[:Fields][6][:Kids][0].delete(:Parent)
|
|
384
|
+
assert(@acro_form.validate)
|
|
385
|
+
assert_equal(1, @acro_form[:Fields][2][:Kids].size)
|
|
386
|
+
assert_equal(:Tx5, @acro_form[:Fields][2][:Kids][0][:T])
|
|
387
|
+
assert_equal(:Tx4, @acro_form[:Fields][3][:T])
|
|
388
|
+
end
|
|
389
|
+
|
|
390
|
+
it "handles /Parent field pointing to somewhere else" do
|
|
391
|
+
@acro_form[:Fields][6][:Kids][0][:Parent] = @acro_form[:Fields][4]
|
|
392
|
+
assert(@acro_form.validate)
|
|
393
|
+
assert_equal(2, @acro_form[:Fields][2][:Kids].size)
|
|
394
|
+
assert_equal(:Tx4, @acro_form[:Fields][2][:Kids][0][:T])
|
|
395
|
+
assert_equal(@acro_form[:Fields][2], @acro_form[:Fields][2][:Kids][0][:Parent])
|
|
396
|
+
end
|
|
397
|
+
end
|
|
398
|
+
|
|
399
|
+
describe "combining fields with the same name" do
|
|
400
|
+
before do
|
|
401
|
+
@acro_form[:Fields] = [
|
|
402
|
+
@doc.add({T: 'e', Subtype: :Widget, Rect: [0, 0, 0, 1]}),
|
|
403
|
+
@doc.add({T: 'e', Subtype: :Widget, Rect: [0, 0, 0, 2]}),
|
|
404
|
+
@doc.add({T: 'Tx2'}),
|
|
405
|
+
@doc.add({T: 'e', Kids: [{Subtype: :Widget, Rect: [0, 0, 0, 3]}]}),
|
|
406
|
+
]
|
|
407
|
+
end
|
|
408
|
+
|
|
409
|
+
it "merges fields with the same name into the first one" do
|
|
410
|
+
assert(@acro_form.validate)
|
|
411
|
+
assert_equal(2, @acro_form.root_fields.size)
|
|
412
|
+
assert_equal([[0, 0, 0, 1], [0, 0, 0, 2], [0, 0, 0, 3]],
|
|
413
|
+
@acro_form.field_by_name('e').each_widget.map {|w| w[:Rect] })
|
|
414
|
+
end
|
|
415
|
+
end
|
|
416
|
+
|
|
352
417
|
describe "automatically creates the terminal fields; appearances" do
|
|
353
418
|
before do
|
|
354
419
|
@cb = @acro_form.create_check_box('test2')
|
|
@@ -20,6 +20,10 @@ describe HexaPDF::Type::AcroForm::SignatureField do
|
|
|
20
20
|
@field = @doc.wrap({}, type: :XXAcroFormField, subtype: :Sig)
|
|
21
21
|
end
|
|
22
22
|
|
|
23
|
+
it "identifies as an :XXAcroFormField type" do
|
|
24
|
+
assert_equal(:XXAcroFormField, @field.type)
|
|
25
|
+
end
|
|
26
|
+
|
|
23
27
|
it "sets the field value" do
|
|
24
28
|
@field.field_value = {Empty: :True}
|
|
25
29
|
assert_equal({Empty: :True}, @field[:V].value)
|
|
@@ -10,6 +10,10 @@ describe HexaPDF::Type::AcroForm::TextField do
|
|
|
10
10
|
@field = @doc.add({FT: :Tx}, type: :XXAcroFormField, subtype: :Tx)
|
|
11
11
|
end
|
|
12
12
|
|
|
13
|
+
it "identifies as an :XXAcroFormField type" do
|
|
14
|
+
assert_equal(:XXAcroFormField, @field.type)
|
|
15
|
+
end
|
|
16
|
+
|
|
13
17
|
it "resolves /MaxLen as inheritable field" do
|
|
14
18
|
assert_nil(@field[:MaxLen])
|
|
15
19
|
|
|
@@ -164,11 +168,20 @@ describe HexaPDF::Type::AcroForm::TextField do
|
|
|
164
168
|
assert_same(stream, @field[:AP][:N].raw_stream)
|
|
165
169
|
@field.field_value = 'test'
|
|
166
170
|
refute_same(stream, @field[:AP][:N].raw_stream)
|
|
171
|
+
stream = @field[:AP][:N].raw_stream
|
|
167
172
|
|
|
168
173
|
widget = @field.create_widget(@doc.pages.add, Rect: [0, 0, 0, 0])
|
|
169
174
|
assert_nil(widget[:AP])
|
|
170
175
|
@field.create_appearances
|
|
171
176
|
refute_nil(widget[:AP][:N])
|
|
177
|
+
|
|
178
|
+
@doc.clear_cache
|
|
179
|
+
@field.create_appearances
|
|
180
|
+
assert_same(stream, @field[:Kids][0][:AP][:N].raw_stream)
|
|
181
|
+
|
|
182
|
+
@doc.clear_cache
|
|
183
|
+
@field.field_value = 'other'
|
|
184
|
+
refute_same(stream, @field[:Kids][0][:AP][:N].raw_stream)
|
|
172
185
|
end
|
|
173
186
|
|
|
174
187
|
it "always creates a new appearance stream if force is true" do
|
|
@@ -65,6 +65,60 @@ module HexaPDF
|
|
|
65
65
|
end
|
|
66
66
|
end
|
|
67
67
|
|
|
68
|
+
def timestamp_certificate
|
|
69
|
+
@timestamp_certificate ||=
|
|
70
|
+
begin
|
|
71
|
+
name = OpenSSL::X509::Name.parse('/CN=timestamp/DC=gettalong')
|
|
72
|
+
|
|
73
|
+
signer_cert = OpenSSL::X509::Certificate.new
|
|
74
|
+
signer_cert.serial = 3
|
|
75
|
+
signer_cert.version = 2
|
|
76
|
+
signer_cert.not_before = Time.now - 86400
|
|
77
|
+
signer_cert.not_after = Time.now + 86400
|
|
78
|
+
signer_cert.public_key = signer_key.public_key
|
|
79
|
+
signer_cert.subject = name
|
|
80
|
+
signer_cert.issuer = ca_certificate.subject
|
|
81
|
+
|
|
82
|
+
extension_factory = OpenSSL::X509::ExtensionFactory.new
|
|
83
|
+
extension_factory.subject_certificate = signer_cert
|
|
84
|
+
extension_factory.issuer_certificate = ca_certificate
|
|
85
|
+
signer_cert.add_extension(extension_factory.create_extension('subjectKeyIdentifier', 'hash'))
|
|
86
|
+
signer_cert.add_extension(extension_factory.create_extension('basicConstraints', 'CA:FALSE'))
|
|
87
|
+
signer_cert.add_extension(extension_factory.create_extension('keyUsage', 'digitalSignature'))
|
|
88
|
+
signer_cert.add_extension(extension_factory.create_extension('extendedKeyUsage',
|
|
89
|
+
'timeStamping', true))
|
|
90
|
+
signer_cert.sign(ca_key, OpenSSL::Digest.new('SHA1'))
|
|
91
|
+
|
|
92
|
+
signer_cert
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def start_tsa_server
|
|
97
|
+
return if defined?(@tsa_server)
|
|
98
|
+
require 'webrick'
|
|
99
|
+
port = 34567
|
|
100
|
+
@tsa_server = WEBrick::HTTPServer.new(Port: port, BindAddress: '127.0.0.1',
|
|
101
|
+
Logger: WEBrick::Log.new(StringIO.new), AccessLog: [])
|
|
102
|
+
@tsa_server.mount_proc('/') do |request, response|
|
|
103
|
+
@tsr = OpenSSL::Timestamp::Request.new(request.body)
|
|
104
|
+
case (@tsr.policy_id || '1.2.3.4.0')
|
|
105
|
+
when '1.2.3.4.0', '1.2.3.4.2'
|
|
106
|
+
fac = OpenSSL::Timestamp::Factory.new
|
|
107
|
+
fac.gen_time = Time.now
|
|
108
|
+
fac.serial_number = 1
|
|
109
|
+
fac.default_policy_id = '1.2.3.4.5'
|
|
110
|
+
fac.allowed_digests = ["sha256", "sha512"]
|
|
111
|
+
tsr = fac.create_timestamp(CERTIFICATES.signer_key, CERTIFICATES.timestamp_certificate,
|
|
112
|
+
@tsr)
|
|
113
|
+
response.body = tsr.to_der
|
|
114
|
+
when '1.2.3.4.1'
|
|
115
|
+
response.status = 403
|
|
116
|
+
response.body = "Invalid"
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
Thread.new { @tsa_server.start }
|
|
120
|
+
end
|
|
121
|
+
|
|
68
122
|
end
|
|
69
123
|
|
|
70
124
|
end
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
# -*- encoding: utf-8 -*-
|
|
2
2
|
|
|
3
|
+
require 'digest'
|
|
3
4
|
require 'test_helper'
|
|
4
5
|
require_relative 'common'
|
|
5
6
|
require 'hexapdf/type/signature'
|
|
@@ -95,5 +96,25 @@ describe HexaPDF::Type::Signature::AdbePkcs7Detached do
|
|
|
95
96
|
assert_equal(:error, result.messages.last.type)
|
|
96
97
|
assert_match(/Signature verification failed/, result.messages.last.content)
|
|
97
98
|
end
|
|
99
|
+
|
|
100
|
+
it "verifies a timestamp signature" do
|
|
101
|
+
req = OpenSSL::Timestamp::Request.new
|
|
102
|
+
req.algorithm = 'SHA256'
|
|
103
|
+
req.message_imprint = Digest::SHA256.digest(@data)
|
|
104
|
+
req.policy_id = "1.2.3.4.5"
|
|
105
|
+
req.nonce = 42
|
|
106
|
+
fac = OpenSSL::Timestamp::Factory.new
|
|
107
|
+
fac.gen_time = Time.now
|
|
108
|
+
fac.serial_number = 1
|
|
109
|
+
fac.allowed_digests = ["sha256", "sha512"]
|
|
110
|
+
res = fac.create_timestamp(CERTIFICATES.signer_key, CERTIFICATES.timestamp_certificate, req)
|
|
111
|
+
@dict.contents = res.token.to_der
|
|
112
|
+
@dict.signature_type = 'ETSI.RFC3161'
|
|
113
|
+
@handler = HexaPDF::Type::Signature::AdbePkcs7Detached.new(@dict)
|
|
114
|
+
|
|
115
|
+
result = @handler.verify(@store)
|
|
116
|
+
assert_equal(:info, result.messages.last.type)
|
|
117
|
+
assert_match(/Signature valid/, result.messages.last.content)
|
|
118
|
+
end
|
|
98
119
|
end
|
|
99
120
|
end
|
|
@@ -29,8 +29,11 @@ describe HexaPDF::Type::Catalog do
|
|
|
29
29
|
assert_same(other, names)
|
|
30
30
|
end
|
|
31
31
|
|
|
32
|
-
it "creates the document outline on access" do
|
|
33
|
-
|
|
32
|
+
it "uses or creates the document outline on access" do
|
|
33
|
+
@catalog[:Outlines] = {}
|
|
34
|
+
assert_equal(:Outlines, @catalog.outline.type)
|
|
35
|
+
|
|
36
|
+
@catalog.delete(:Outlines)
|
|
34
37
|
outline = @catalog.outline
|
|
35
38
|
assert_equal(:Outlines, outline.type)
|
|
36
39
|
assert_same(outline, @catalog.outline)
|
|
@@ -15,6 +15,26 @@ describe HexaPDF::Type::FontTrueType do
|
|
|
15
15
|
BaseFont: :Something, FontDescriptor: font_descriptor})
|
|
16
16
|
end
|
|
17
17
|
|
|
18
|
+
describe "font_wrapper" do
|
|
19
|
+
it "returns the default value if the font is subset" do
|
|
20
|
+
@font[:BaseFont] = :'ABCDEF+Something'
|
|
21
|
+
assert_nil(@font.font_wrapper)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
it "returns the default value if the font has no embedded font file" do
|
|
25
|
+
assert_nil(@font.font_wrapper)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
it "uses a fully embedded TrueType font file" do
|
|
29
|
+
font_file = File.binread(File.join(TEST_DATA_DIR, "fonts", "Ubuntu-Title.ttf"))
|
|
30
|
+
@font[:FontDescriptor][:FontFile2] = @doc.add({}, stream: font_file)
|
|
31
|
+
font_wrapper = @font.font_wrapper
|
|
32
|
+
assert(font_wrapper)
|
|
33
|
+
assert_equal(font_file, font_wrapper.wrapped_font.io.string)
|
|
34
|
+
assert_same(font_wrapper, @font.font_wrapper)
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
18
38
|
describe "validation" do
|
|
19
39
|
it "ignores some missing fields if the font name is one of the standard PDF fonts" do
|
|
20
40
|
@font[:BaseFont] = :'Arial,Bold'
|
|
@@ -104,7 +104,8 @@ describe HexaPDF::Type::ObjectStream do
|
|
|
104
104
|
assert_equal("", @obj.stream)
|
|
105
105
|
end
|
|
106
106
|
|
|
107
|
-
it "doesn't allow the Catalog entry to be compressed
|
|
107
|
+
it "doesn't allow the Catalog entry to be compressed" do
|
|
108
|
+
@doc.trailer.delete(:Encrypt)
|
|
108
109
|
@obj.add_object(HexaPDF::Dictionary.new({Type: :Catalog}, oid: 8))
|
|
109
110
|
@obj.write_objects(@revision)
|
|
110
111
|
assert_equal(0, @obj.value[:N])
|
|
@@ -25,7 +25,7 @@ describe HexaPDF::Type::Outline do
|
|
|
25
25
|
end
|
|
26
26
|
item1.add_item("Item5")
|
|
27
27
|
end
|
|
28
|
-
assert_equal(%w[Item1 Item2 Item3 Item4 Item5], @outline.each_item.map
|
|
28
|
+
assert_equal(%w[Item1 Item2 Item3 Item4 Item5], @outline.each_item.map {|i, _| i.title })
|
|
29
29
|
end
|
|
30
30
|
|
|
31
31
|
describe "perform_validation" do
|
|
@@ -64,6 +64,9 @@ describe HexaPDF::Type::Outline do
|
|
|
64
64
|
assert(correctable)
|
|
65
65
|
end
|
|
66
66
|
refute(@outline.key?(:Count))
|
|
67
|
+
|
|
68
|
+
@outline[:Count] = 0
|
|
69
|
+
assert(@outline.validate(auto_correct: false))
|
|
67
70
|
end
|
|
68
71
|
end
|
|
69
72
|
end
|
|
@@ -10,6 +10,10 @@ describe HexaPDF::Type::OutlineItem do
|
|
|
10
10
|
@item = @doc.add({Title: "root", Count: 0}, type: :XXOutlineItem)
|
|
11
11
|
end
|
|
12
12
|
|
|
13
|
+
it "must be an indirect object" do
|
|
14
|
+
assert(@item.must_be_indirect?)
|
|
15
|
+
end
|
|
16
|
+
|
|
13
17
|
describe "title" do
|
|
14
18
|
it "returns the set title" do
|
|
15
19
|
@item[:Title] = 'Test'
|
|
@@ -78,6 +82,43 @@ describe HexaPDF::Type::OutlineItem do
|
|
|
78
82
|
end
|
|
79
83
|
end
|
|
80
84
|
|
|
85
|
+
describe "level" do
|
|
86
|
+
it "returns 0 for the outline dictionary when treated as an item" do
|
|
87
|
+
assert_equal(0, @item.level)
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
it "returns 1 for the root level items" do
|
|
91
|
+
@item[:Parent] = {Type: :Outlines}
|
|
92
|
+
assert_equal(1, @item.level)
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
it "returns the correct level for items in the hierarchy" do
|
|
96
|
+
@item[:Parent] = {Title: 'Root elem', Parent: {Type: :Outlines}}
|
|
97
|
+
assert_equal(2, @item.level)
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
describe "destination_page" do
|
|
102
|
+
it "returns the page of a set destination" do
|
|
103
|
+
@item[:Dest] = [5, :Fit]
|
|
104
|
+
assert_equal(5, @item.destination_page)
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
it "returns the page of a set GoTO action" do
|
|
108
|
+
@item[:A] = {S: :GoTo, D: [5, :Fit]}
|
|
109
|
+
assert_equal(5, @item.destination_page)
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
it "returns nil if no destination or action is set" do
|
|
113
|
+
assert_nil(@item.destination_page)
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
it "returns nil if an action besides GoTo is set" do
|
|
117
|
+
@item[:A] = {S: :GoToR}
|
|
118
|
+
assert_nil(@item.destination_page)
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
|
|
81
122
|
describe "add" do
|
|
82
123
|
it "returns the created item" do
|
|
83
124
|
new_item = @item.add_item("Test")
|
|
@@ -118,6 +159,24 @@ describe HexaPDF::Type::OutlineItem do
|
|
|
118
159
|
assert_same(new_item, yielded_item)
|
|
119
160
|
end
|
|
120
161
|
|
|
162
|
+
it "uses the provided outline item instead of creating a new one" do
|
|
163
|
+
item = @doc.wrap({Dest: [1, :Fit], flags: 1, First: 5, Count: 2}, type: :XXOutlineItem)
|
|
164
|
+
new_item = @item.add_item(item, destination: [2, :Fit])
|
|
165
|
+
assert_same(item, new_item)
|
|
166
|
+
assert_equal([1, :Fit], new_item.destination)
|
|
167
|
+
assert_same(@item, new_item[:Parent])
|
|
168
|
+
refute(new_item.key?(:First))
|
|
169
|
+
assert_equal(0, new_item[:Count])
|
|
170
|
+
|
|
171
|
+
item = @doc.wrap({Count: nil}, type: :XXOutlineItem)
|
|
172
|
+
new_item = @item.add_item(item)
|
|
173
|
+
refute(new_item.key?(:Count))
|
|
174
|
+
|
|
175
|
+
item = @doc.wrap({Count: -1}, type: :XXOutlineItem)
|
|
176
|
+
new_item = @item.add_item(item)
|
|
177
|
+
refute(new_item.key?(:Count))
|
|
178
|
+
end
|
|
179
|
+
|
|
121
180
|
describe "position" do
|
|
122
181
|
it "works for an empty item" do
|
|
123
182
|
new_item = @item.add_item("Test")
|
|
@@ -211,7 +270,8 @@ describe HexaPDF::Type::OutlineItem do
|
|
|
211
270
|
end
|
|
212
271
|
item1.add_item("Item5")
|
|
213
272
|
end
|
|
214
|
-
assert_equal(
|
|
273
|
+
assert_equal(['Item1', 1, 'Item2', 2, 'Item3', 2, 'Item4', 3, 'Item5', 2],
|
|
274
|
+
@item.each_item.map {|i, l| [i.title, l] }.flatten)
|
|
215
275
|
end
|
|
216
276
|
|
|
217
277
|
describe "perform_validation" do
|
|
@@ -251,6 +311,7 @@ describe HexaPDF::Type::OutlineItem do
|
|
|
251
311
|
assert(correctable)
|
|
252
312
|
end
|
|
253
313
|
refute(@item.key?(:Count))
|
|
314
|
+
assert(@item.validate(auto_correct: false))
|
|
254
315
|
end
|
|
255
316
|
|
|
256
317
|
it "fails validation if the previous item's /Next points somewhere else" do
|