hexapdf 1.1.0 → 1.2.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 +41 -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 +30 -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
|