hexapdf 1.1.1 → 1.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (46) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +34 -0
  3. data/lib/hexapdf/cli/command.rb +63 -63
  4. data/lib/hexapdf/cli/inspect.rb +1 -1
  5. data/lib/hexapdf/cli/modify.rb +0 -1
  6. data/lib/hexapdf/cli/optimize.rb +5 -5
  7. data/lib/hexapdf/configuration.rb +21 -0
  8. data/lib/hexapdf/content/graphics_state.rb +1 -1
  9. data/lib/hexapdf/digital_signature/signing/signed_data_creator.rb +1 -1
  10. data/lib/hexapdf/document/annotations.rb +115 -0
  11. data/lib/hexapdf/document.rb +28 -7
  12. data/lib/hexapdf/font/true_type_wrapper.rb +1 -0
  13. data/lib/hexapdf/font/type1_wrapper.rb +1 -0
  14. data/lib/hexapdf/type/acro_form/java_script_actions.rb +9 -2
  15. data/lib/hexapdf/type/acro_form/text_field.rb +9 -2
  16. data/lib/hexapdf/type/annotation.rb +59 -1
  17. data/lib/hexapdf/type/annotations/appearance_generator.rb +273 -0
  18. data/lib/hexapdf/type/annotations/border_styling.rb +160 -0
  19. data/lib/hexapdf/type/annotations/line.rb +521 -0
  20. data/lib/hexapdf/type/annotations/widget.rb +2 -96
  21. data/lib/hexapdf/type/annotations.rb +3 -0
  22. data/lib/hexapdf/type/form.rb +2 -2
  23. data/lib/hexapdf/version.rb +1 -1
  24. data/lib/hexapdf/writer.rb +0 -1
  25. data/lib/hexapdf/xref_section.rb +7 -4
  26. data/test/hexapdf/content/test_graphics_state.rb +2 -3
  27. data/test/hexapdf/content/test_operator.rb +4 -5
  28. data/test/hexapdf/digital_signature/test_cms_handler.rb +7 -8
  29. data/test/hexapdf/digital_signature/test_handler.rb +2 -3
  30. data/test/hexapdf/digital_signature/test_pkcs1_handler.rb +1 -2
  31. data/test/hexapdf/document/test_annotations.rb +33 -0
  32. data/test/hexapdf/font/test_true_type_wrapper.rb +7 -0
  33. data/test/hexapdf/font/test_type1_wrapper.rb +7 -0
  34. data/test/hexapdf/task/test_optimize.rb +1 -1
  35. data/test/hexapdf/test_document.rb +11 -3
  36. data/test/hexapdf/test_stream.rb +1 -2
  37. data/test/hexapdf/test_xref_section.rb +1 -1
  38. data/test/hexapdf/type/acro_form/test_java_script_actions.rb +21 -0
  39. data/test/hexapdf/type/acro_form/test_text_field.rb +7 -1
  40. data/test/hexapdf/type/annotations/test_appearance_generator.rb +398 -0
  41. data/test/hexapdf/type/annotations/test_border_styling.rb +114 -0
  42. data/test/hexapdf/type/annotations/test_line.rb +189 -0
  43. data/test/hexapdf/type/annotations/test_widget.rb +0 -81
  44. data/test/hexapdf/type/test_annotation.rb +55 -0
  45. data/test/hexapdf/type/test_form.rb +6 -0
  46. metadata +10 -2
@@ -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.each do |oid|
165
- expected_next_oid = !temp.empty? && temp[-1].oid + 1
166
- if expected_next_oid && expected_next_oid != oid
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 = OpenStruct.new
153
- font.glyph_scaling_factor = 0.002
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 = OpenStruct.new
195
- font.glyph_scaling_factor = 0.01
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 = OpenStruct.new
457
- font.glyph_scaling_factor = 0.01
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 = OpenStruct.new
13
- @pkcs7 = OpenSSL::PKCS7.sign(CERTIFICATES.signer_certificate, CERTIFICATES.signer_key,
14
- @data, [CERTIFICATES.ca_certificate],
15
- OpenSSL::PKCS7::DETACHED)
16
- @dict.contents = @pkcs7.to_der
17
- @dict.signed_data = @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 = OpenStruct.new
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
- OpenStruct.new.tap do |struct|
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 = OpenStruct.new
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
@@ -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 = OpenStruct.new
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 "fails if the value exceeds the length set by /MaxLen" do
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