hexapdf 1.1.1 → 1.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +34 -0
- data/lib/hexapdf/cli/command.rb +63 -63
- data/lib/hexapdf/cli/inspect.rb +1 -1
- data/lib/hexapdf/cli/modify.rb +0 -1
- data/lib/hexapdf/cli/optimize.rb +5 -5
- data/lib/hexapdf/configuration.rb +21 -0
- data/lib/hexapdf/content/graphics_state.rb +1 -1
- data/lib/hexapdf/digital_signature/signing/signed_data_creator.rb +1 -1
- data/lib/hexapdf/document/annotations.rb +115 -0
- data/lib/hexapdf/document.rb +28 -7
- data/lib/hexapdf/font/true_type_wrapper.rb +1 -0
- data/lib/hexapdf/font/type1_wrapper.rb +1 -0
- data/lib/hexapdf/type/acro_form/java_script_actions.rb +9 -2
- data/lib/hexapdf/type/acro_form/text_field.rb +9 -2
- data/lib/hexapdf/type/annotation.rb +59 -1
- data/lib/hexapdf/type/annotations/appearance_generator.rb +273 -0
- data/lib/hexapdf/type/annotations/border_styling.rb +160 -0
- data/lib/hexapdf/type/annotations/line.rb +521 -0
- data/lib/hexapdf/type/annotations/widget.rb +2 -96
- data/lib/hexapdf/type/annotations.rb +3 -0
- data/lib/hexapdf/type/form.rb +2 -2
- data/lib/hexapdf/version.rb +1 -1
- data/lib/hexapdf/writer.rb +0 -1
- data/lib/hexapdf/xref_section.rb +7 -4
- data/test/hexapdf/content/test_graphics_state.rb +2 -3
- data/test/hexapdf/content/test_operator.rb +4 -5
- data/test/hexapdf/digital_signature/test_cms_handler.rb +7 -8
- data/test/hexapdf/digital_signature/test_handler.rb +2 -3
- data/test/hexapdf/digital_signature/test_pkcs1_handler.rb +1 -2
- data/test/hexapdf/document/test_annotations.rb +33 -0
- data/test/hexapdf/font/test_true_type_wrapper.rb +7 -0
- data/test/hexapdf/font/test_type1_wrapper.rb +7 -0
- data/test/hexapdf/task/test_optimize.rb +1 -1
- data/test/hexapdf/test_document.rb +11 -3
- data/test/hexapdf/test_stream.rb +1 -2
- data/test/hexapdf/test_xref_section.rb +1 -1
- data/test/hexapdf/type/acro_form/test_java_script_actions.rb +21 -0
- data/test/hexapdf/type/acro_form/test_text_field.rb +7 -1
- data/test/hexapdf/type/annotations/test_appearance_generator.rb +398 -0
- data/test/hexapdf/type/annotations/test_border_styling.rb +114 -0
- data/test/hexapdf/type/annotations/test_line.rb +189 -0
- data/test/hexapdf/type/annotations/test_widget.rb +0 -81
- data/test/hexapdf/type/test_annotation.rb +55 -0
- data/test/hexapdf/type/test_form.rb +6 -0
- metadata +10 -2
data/lib/hexapdf/xref_section.rb
CHANGED
@@ -113,9 +113,10 @@ module HexaPDF
|
|
113
113
|
|
114
114
|
# Marks this XRefSection object as being the first cross-reference section in a PDF file.
|
115
115
|
#
|
116
|
-
# This has the consequence that only a single sub-section is created.
|
116
|
+
# This has the consequence that only a single sub-section starting a zero is created.
|
117
117
|
def mark_as_initial_section!
|
118
118
|
@initial_section = true
|
119
|
+
add_free_entry(0, 65535)
|
119
120
|
end
|
120
121
|
|
121
122
|
# Adds an in-use entry to the cross-reference section.
|
@@ -161,9 +162,10 @@ module HexaPDF
|
|
161
162
|
return to_enum(__method__) unless block_given?
|
162
163
|
|
163
164
|
temp = []
|
164
|
-
oids.sort
|
165
|
-
|
166
|
-
|
165
|
+
sorted_oids = oids.sort
|
166
|
+
expected_next_oid = sorted_oids[0]
|
167
|
+
sorted_oids.each do |oid|
|
168
|
+
if expected_next_oid != oid
|
167
169
|
if @initial_section
|
168
170
|
expected_next_oid.upto(oid - 1) do |free_oid|
|
169
171
|
temp << self.class.free_entry(free_oid, 0)
|
@@ -174,6 +176,7 @@ module HexaPDF
|
|
174
176
|
end
|
175
177
|
end
|
176
178
|
temp << self[oid]
|
179
|
+
expected_next_oid = oid + 1
|
177
180
|
end
|
178
181
|
yield(temp)
|
179
182
|
self
|
@@ -2,7 +2,6 @@
|
|
2
2
|
|
3
3
|
require 'test_helper'
|
4
4
|
require 'hexapdf/content/graphics_state'
|
5
|
-
require 'ostruct'
|
6
5
|
|
7
6
|
# Dummy class used as wrapper so that constant lookup works correctly
|
8
7
|
class GraphicsStateWrapper < Minitest::Spec
|
@@ -149,8 +148,8 @@ class GraphicsStateWrapper < Minitest::Spec
|
|
149
148
|
end
|
150
149
|
|
151
150
|
it "uses the correct glyph to text space scaling" do
|
152
|
-
font =
|
153
|
-
font.glyph_scaling_factor
|
151
|
+
font = Object.new
|
152
|
+
font.define_singleton_method(:glyph_scaling_factor) { 0.002 }
|
154
153
|
@gs.font = font
|
155
154
|
@gs.font_size = 10
|
156
155
|
assert_equal(0.02, @gs.scaled_font_size)
|
@@ -4,7 +4,6 @@ require 'test_helper'
|
|
4
4
|
require 'hexapdf/content/operator'
|
5
5
|
require 'hexapdf/content/processor'
|
6
6
|
require 'hexapdf/serializer'
|
7
|
-
require 'ostruct'
|
8
7
|
|
9
8
|
describe HexaPDF::Content::Operator::BaseOperator do
|
10
9
|
before do
|
@@ -191,8 +190,8 @@ end
|
|
191
190
|
|
192
191
|
describe_operator :SetGraphicsStateParameters, :gs do
|
193
192
|
it "applies parameters from an ExtGState dictionary" do
|
194
|
-
font =
|
195
|
-
font.glyph_scaling_factor
|
193
|
+
font = Object.new
|
194
|
+
font.define_singleton_method(:glyph_scaling_factor) { 0.01 }
|
196
195
|
@processor.resources[:ExtGState] = {Name: {LW: 10, LC: 2, LJ: 2, ML: 2, D: [[3, 5], 2],
|
197
196
|
RI: 2, SA: true, BM: :Multiply, CA: 0.5, ca: 0.5,
|
198
197
|
AIS: true, TK: false, Font: [font, 10],
|
@@ -453,8 +452,8 @@ describe_operator :SetFontAndSize, :Tf do
|
|
453
452
|
self[:Font] && self[:Font][name]
|
454
453
|
end
|
455
454
|
|
456
|
-
font =
|
457
|
-
font.glyph_scaling_factor
|
455
|
+
font = Object.new
|
456
|
+
font.define_singleton_method(:glyph_scaling_factor) { 0.01 }
|
458
457
|
@processor.resources[:Font] = {F1: font}
|
459
458
|
invoke(:F1, 10)
|
460
459
|
assert_equal(@processor.resources.font(:F1), @processor.graphics_state.font)
|
@@ -4,17 +4,16 @@ require 'digest'
|
|
4
4
|
require 'test_helper'
|
5
5
|
require_relative 'common'
|
6
6
|
require 'hexapdf/digital_signature'
|
7
|
-
require 'ostruct'
|
8
7
|
|
9
8
|
describe HexaPDF::DigitalSignature::CMSHandler do
|
10
9
|
before do
|
11
|
-
@data = 'Some data'
|
12
|
-
@dict =
|
13
|
-
@pkcs7 = OpenSSL::PKCS7.sign(CERTIFICATES.signer_certificate, CERTIFICATES.signer_key,
|
14
|
-
|
15
|
-
|
16
|
-
@dict.contents =
|
17
|
-
@dict.signed_data =
|
10
|
+
@data = data = 'Some data'
|
11
|
+
@dict = Struct.new(:contents, :signed_data, :signature_type, :Reference, :M).new
|
12
|
+
@pkcs7 = pkcs7 = OpenSSL::PKCS7.sign(CERTIFICATES.signer_certificate, CERTIFICATES.signer_key,
|
13
|
+
@data, [CERTIFICATES.ca_certificate],
|
14
|
+
OpenSSL::PKCS7::DETACHED)
|
15
|
+
@dict.contents = pkcs7.to_der
|
16
|
+
@dict.signed_data = data
|
18
17
|
@handler = HexaPDF::DigitalSignature::CMSHandler.new(@dict)
|
19
18
|
end
|
20
19
|
|
@@ -4,7 +4,6 @@ require 'test_helper'
|
|
4
4
|
require 'hexapdf/digital_signature'
|
5
5
|
require 'hexapdf/document'
|
6
6
|
require 'time'
|
7
|
-
require 'ostruct'
|
8
7
|
require 'openssl'
|
9
8
|
|
10
9
|
describe HexaPDF::DigitalSignature::Handler do
|
@@ -33,7 +32,7 @@ describe HexaPDF::DigitalSignature::Handler do
|
|
33
32
|
|
34
33
|
describe "store_verification_callback" do
|
35
34
|
before do
|
36
|
-
@context =
|
35
|
+
@context = Struct.new(:error).new
|
37
36
|
end
|
38
37
|
|
39
38
|
it "can allow self-signed certificates" do
|
@@ -60,7 +59,7 @@ describe HexaPDF::DigitalSignature::Handler do
|
|
60
59
|
].each do |success, not_before, not_after|
|
61
60
|
@result.messages.clear
|
62
61
|
@handler.define_singleton_method(:signer_certificate) do
|
63
|
-
|
62
|
+
Struct.new(:not_before, :not_after).new.tap do |struct|
|
64
63
|
struct.not_before = Time.parse("2021-11-14 #{not_before}")
|
65
64
|
struct.not_after = Time.parse("2021-11-14 #{not_after}")
|
66
65
|
end
|
@@ -3,12 +3,11 @@
|
|
3
3
|
require 'test_helper'
|
4
4
|
require_relative 'common'
|
5
5
|
require 'hexapdf/digital_signature'
|
6
|
-
require 'ostruct'
|
7
6
|
|
8
7
|
describe HexaPDF::DigitalSignature::PKCS1Handler do
|
9
8
|
before do
|
10
9
|
@data = 'Some data'
|
11
|
-
@dict =
|
10
|
+
@dict = Struct.new(:signed_data, :contents, :Cert, :Reference, :M).new
|
12
11
|
@dict.signed_data = @data
|
13
12
|
encoded_data = CERTIFICATES.signer_key.sign(OpenSSL::Digest.new('SHA1'), @data)
|
14
13
|
@dict.contents = OpenSSL::ASN1::OctetString.new(encoded_data).to_der
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
|
3
|
+
require 'test_helper'
|
4
|
+
require 'hexapdf/document'
|
5
|
+
|
6
|
+
describe HexaPDF::Document::Annotations do
|
7
|
+
before do
|
8
|
+
@doc = HexaPDF::Document.new
|
9
|
+
@page = @doc.pages.add
|
10
|
+
@annots = @doc.annotations
|
11
|
+
end
|
12
|
+
|
13
|
+
describe "create" do
|
14
|
+
it "fails if the type argument doesn't refer to an implemented method" do
|
15
|
+
assert_raises(ArgumentError) { @annots.create(:unknown, @page) }
|
16
|
+
end
|
17
|
+
|
18
|
+
it "delegates to the actual create_TYPE implementation" do
|
19
|
+
annot = @annots.create(:line, @page, start_point: [0, 0], end_point: [10, 10])
|
20
|
+
assert_equal(:Line, annot[:Subtype])
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
describe "create_line" do
|
25
|
+
it "creates an appropriate line annotation object" do
|
26
|
+
annot = @annots.create(:line, @page, start_point: [0, 5], end_point: [10, 15])
|
27
|
+
assert_equal(:Annot, annot[:Type])
|
28
|
+
assert_equal(:Line, annot[:Subtype])
|
29
|
+
assert_equal([0, 5, 10, 15], annot.line)
|
30
|
+
assert_equal(annot, @page[:Annots].first)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -209,6 +209,13 @@ describe HexaPDF::Font::TrueTypeWrapper do
|
|
209
209
|
dict[:Encoding].stream)
|
210
210
|
assert_equal([glyph.id, [glyph.width]], dict[:DescendantFonts][0][:W].value)
|
211
211
|
end
|
212
|
+
|
213
|
+
it "handles the case where the font is added but then not used and deleted" do
|
214
|
+
@doc.task(:optimize, compact: true)
|
215
|
+
assert(@font_wrapper.pdf_object.null?)
|
216
|
+
@doc.dispatch_message(:complete_objects)
|
217
|
+
assert(@font_wrapper.pdf_object.null?)
|
218
|
+
end
|
212
219
|
end
|
213
220
|
|
214
221
|
describe "font file embedding" do
|
@@ -140,5 +140,12 @@ describe HexaPDF::Font::Type1Wrapper do
|
|
140
140
|
it "makes sure that the PDF dictionaries are indirect" do
|
141
141
|
assert(@times_wrapper.pdf_object.indirect?)
|
142
142
|
end
|
143
|
+
|
144
|
+
it "handles the case where the font is added but then not used and deleted" do
|
145
|
+
@doc.task(:optimize, compact: true)
|
146
|
+
assert(@times_wrapper.pdf_object.null?)
|
147
|
+
@doc.dispatch_message(:complete_objects)
|
148
|
+
assert(@times_wrapper.pdf_object.null?)
|
149
|
+
end
|
143
150
|
end
|
144
151
|
end
|
@@ -99,7 +99,7 @@ describe HexaPDF::Task::Optimize do
|
|
99
99
|
objstm = @doc.add({}, type: HexaPDF::Type::ObjectStream)
|
100
100
|
@doc.add({}, type: HexaPDF::Type::XRefStream)
|
101
101
|
objstm.add_object(@doc.add({Type: :Test}))
|
102
|
-
@doc.write(io)
|
102
|
+
@doc.write(io, compact: false)
|
103
103
|
io.rewind
|
104
104
|
@doc = HexaPDF::Document.new(io: io)
|
105
105
|
end
|
@@ -54,7 +54,7 @@ describe HexaPDF::Document do
|
|
54
54
|
describe "::open" do
|
55
55
|
before do
|
56
56
|
@file = Tempfile.new('hexapdf-document')
|
57
|
-
@io_doc.write(@file)
|
57
|
+
@io_doc.write(@file, compact: false)
|
58
58
|
@file.close
|
59
59
|
end
|
60
60
|
|
@@ -370,7 +370,7 @@ describe HexaPDF::Document do
|
|
370
370
|
it "writes the document to a file" do
|
371
371
|
file = Tempfile.new('hexapdf-write')
|
372
372
|
file.close
|
373
|
-
@io_doc.write(file.path)
|
373
|
+
@io_doc.write(file.path, compact: false)
|
374
374
|
HexaPDF::Document.open(file.path) do |doc|
|
375
375
|
assert_equal(200, doc.object(2).value)
|
376
376
|
end
|
@@ -422,10 +422,18 @@ describe HexaPDF::Document do
|
|
422
422
|
|
423
423
|
it "allows optimizing the file by using object streams" do
|
424
424
|
io = StringIO.new(''.b)
|
425
|
-
@io_doc.write(io, optimize: true)
|
425
|
+
@io_doc.write(io, optimize: true, compact: false)
|
426
426
|
doc = HexaPDF::Document.new(io: io)
|
427
427
|
assert_equal(2, doc.each.count {|o| o.type == :ObjStm })
|
428
428
|
end
|
429
|
+
|
430
|
+
it "automatically compacts the file" do
|
431
|
+
io = StringIO.new(''.b)
|
432
|
+
@io_doc.write(io)
|
433
|
+
doc = HexaPDF::Document.new(io: io)
|
434
|
+
assert_equal(1, doc.revisions.count)
|
435
|
+
assert_equal(4, doc.each.count)
|
436
|
+
end
|
429
437
|
end
|
430
438
|
|
431
439
|
describe "version" do
|
data/test/hexapdf/test_stream.rb
CHANGED
@@ -1,7 +1,6 @@
|
|
1
1
|
# -*- encoding: utf-8 -*-
|
2
2
|
|
3
3
|
require 'test_helper'
|
4
|
-
require 'ostruct'
|
5
4
|
require 'stringio'
|
6
5
|
require 'tempfile'
|
7
6
|
require 'hexapdf/configuration'
|
@@ -80,7 +79,7 @@ end
|
|
80
79
|
|
81
80
|
describe HexaPDF::Stream do
|
82
81
|
before do
|
83
|
-
@document =
|
82
|
+
@document = Struct.new(:config).new
|
84
83
|
@document.config = HexaPDF::Configuration.with_defaults
|
85
84
|
@document.instance_variable_set(:@version, '1.2')
|
86
85
|
def (@document).unwrap(obj); obj; end
|
@@ -66,7 +66,7 @@ describe HexaPDF::XRefSection do
|
|
66
66
|
@xref_section.add_in_use_entry(1, 0, 0)
|
67
67
|
@xref_section.add_in_use_entry(2, 0, 0)
|
68
68
|
result = @xref_section.each_subsection.map {|s| s.map {|e| [e.oid, e.type] }}
|
69
|
-
assert_equal([[[1, :in_use], [2, :in_use],
|
69
|
+
assert_equal([[[0, :free], [1, :in_use], [2, :in_use],
|
70
70
|
[3, :free], [4, :free], [5, :free],
|
71
71
|
[6, :in_use], [7, :in_use],
|
72
72
|
[8, :free],
|
@@ -82,6 +82,20 @@ describe HexaPDF::Type::AcroForm::JavaScriptActions do
|
|
82
82
|
assert_equal('1.234,57', value)
|
83
83
|
end
|
84
84
|
|
85
|
+
it "works with the special Infinity and NaN values" do
|
86
|
+
@value = 'Infinity'
|
87
|
+
assert_format('2, 2, 0, 0, "", false', "Inf", "black")
|
88
|
+
@value = '-Infinity'
|
89
|
+
assert_format('2, 2, 0, 0, "", false', "-Inf", "black")
|
90
|
+
@value = 'Nan'
|
91
|
+
assert_format('2, 2, 0, 0, "", false', "NaN", "black")
|
92
|
+
end
|
93
|
+
|
94
|
+
it "works if the value is nil" do
|
95
|
+
@value = nil
|
96
|
+
assert_format('2, 2, 0, 0, "", false', "0,00", "black")
|
97
|
+
end
|
98
|
+
|
85
99
|
it "does nothing to the value if the JavaScript method could not be determined " do
|
86
100
|
assert_format('2, 3, 0, 0, " E", false, a', "1234567.898765", nil)
|
87
101
|
end
|
@@ -244,6 +258,13 @@ describe HexaPDF::Type::AcroForm::JavaScriptActions do
|
|
244
258
|
assert_calculation('SUM', [@field1, @field2], "30.54")
|
245
259
|
end
|
246
260
|
|
261
|
+
it "works with the special values Infinity and NaN" do
|
262
|
+
@field1.field_value = "Infinity"
|
263
|
+
assert_calculation('SUM', [@field1, @field2], "Infinity")
|
264
|
+
@field1.field_value = "NaN"
|
265
|
+
assert_calculation('SUM', [@field1, @field2], "NaN")
|
266
|
+
end
|
267
|
+
|
247
268
|
it "returns nil if a field cannot be resolved" do
|
248
269
|
@action[:JS] = 'AFSimple_Calculate("SUM", ["unknown"]);'
|
249
270
|
assert_nil(@klass.calculate(@form, @action))
|
@@ -130,9 +130,12 @@ describe HexaPDF::Type::AcroForm::TextField do
|
|
130
130
|
assert_raises(HexaPDF::Error) { @field.field_value = 'test' }
|
131
131
|
end
|
132
132
|
|
133
|
-
it "
|
133
|
+
it "calls acro_form.text_field.on_max_len_exceeded if the value exceeds the length set by /MaxLen" do
|
134
134
|
@field[:MaxLen] = 5
|
135
135
|
assert_raises(HexaPDF::Error) { @field.field_value = 'testdf' }
|
136
|
+
@doc.config['acro_form.text_field.on_max_len_exceeded'] = proc {|f, v| v }
|
137
|
+
@field.field_value = 'testdf'
|
138
|
+
assert_equal('testdf', @field[:V])
|
136
139
|
end
|
137
140
|
end
|
138
141
|
|
@@ -278,6 +281,9 @@ describe HexaPDF::Type::AcroForm::TextField do
|
|
278
281
|
assert(@field.validate)
|
279
282
|
@field[:MaxLen] = 2
|
280
283
|
refute(@field.validate)
|
284
|
+
@doc.config['acro_form.text_field.on_max_len_exceeded'] = proc {|field, str| "Hello" }
|
285
|
+
assert(@field.validate)
|
286
|
+
assert_equal('Hello', @field[:V])
|
281
287
|
@field[:V] = nil
|
282
288
|
assert(@field.validate)
|
283
289
|
end
|