hexapdf 0.12.3 → 0.13.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (78) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +38 -0
  3. data/lib/hexapdf/cli/command.rb +4 -2
  4. data/lib/hexapdf/cli/image2pdf.rb +2 -1
  5. data/lib/hexapdf/cli/info.rb +51 -2
  6. data/lib/hexapdf/cli/inspect.rb +30 -8
  7. data/lib/hexapdf/cli/merge.rb +1 -1
  8. data/lib/hexapdf/configuration.rb +15 -0
  9. data/lib/hexapdf/content/graphic_object/arc.rb +3 -3
  10. data/lib/hexapdf/dictionary.rb +4 -4
  11. data/lib/hexapdf/dictionary_fields.rb +1 -9
  12. data/lib/hexapdf/document.rb +31 -12
  13. data/lib/hexapdf/document/files.rb +0 -1
  14. data/lib/hexapdf/encryption/fast_arc4.rb +1 -1
  15. data/lib/hexapdf/encryption/security_handler.rb +1 -0
  16. data/lib/hexapdf/encryption/standard_security_handler.rb +1 -0
  17. data/lib/hexapdf/font/cmap.rb +1 -4
  18. data/lib/hexapdf/font/true_type/table/head.rb +1 -0
  19. data/lib/hexapdf/font/true_type/table/os2.rb +2 -0
  20. data/lib/hexapdf/image_loader/png.rb +3 -2
  21. data/lib/hexapdf/layout/line.rb +1 -1
  22. data/lib/hexapdf/layout/style.rb +23 -23
  23. data/lib/hexapdf/layout/text_shaper.rb +3 -2
  24. data/lib/hexapdf/object.rb +30 -25
  25. data/lib/hexapdf/parser.rb +65 -3
  26. data/lib/hexapdf/pdf_array.rb +9 -2
  27. data/lib/hexapdf/revisions.rb +29 -21
  28. data/lib/hexapdf/serializer.rb +1 -1
  29. data/lib/hexapdf/task/optimize.rb +6 -4
  30. data/lib/hexapdf/type/acro_form/choice_field.rb +4 -4
  31. data/lib/hexapdf/type/acro_form/field.rb +35 -5
  32. data/lib/hexapdf/type/acro_form/form.rb +6 -4
  33. data/lib/hexapdf/type/acro_form/text_field.rb +2 -1
  34. data/lib/hexapdf/type/actions/uri.rb +3 -2
  35. data/lib/hexapdf/type/annotations/widget.rb +3 -4
  36. data/lib/hexapdf/type/catalog.rb +2 -2
  37. data/lib/hexapdf/type/file_specification.rb +1 -1
  38. data/lib/hexapdf/type/font_simple.rb +3 -1
  39. data/lib/hexapdf/type/font_true_type.rb +6 -2
  40. data/lib/hexapdf/type/font_type0.rb +1 -1
  41. data/lib/hexapdf/type/form.rb +2 -1
  42. data/lib/hexapdf/type/image.rb +2 -2
  43. data/lib/hexapdf/type/page.rb +16 -7
  44. data/lib/hexapdf/type/page_tree_node.rb +29 -5
  45. data/lib/hexapdf/type/resources.rb +1 -0
  46. data/lib/hexapdf/type/trailer.rb +2 -3
  47. data/lib/hexapdf/utils/sorted_tree_node.rb +18 -15
  48. data/lib/hexapdf/version.rb +1 -1
  49. data/test/hexapdf/common_tokenizer_tests.rb +2 -2
  50. data/test/hexapdf/content/graphic_object/test_arc.rb +4 -4
  51. data/test/hexapdf/content/test_canvas.rb +3 -3
  52. data/test/hexapdf/content/test_color_space.rb +1 -1
  53. data/test/hexapdf/encryption/test_aes.rb +4 -4
  54. data/test/hexapdf/encryption/test_standard_security_handler.rb +11 -11
  55. data/test/hexapdf/filter/test_ascii85_decode.rb +1 -1
  56. data/test/hexapdf/filter/test_ascii_hex_decode.rb +1 -1
  57. data/test/hexapdf/layout/test_text_layouter.rb +3 -4
  58. data/test/hexapdf/test_configuration.rb +2 -2
  59. data/test/hexapdf/test_dictionary.rb +3 -1
  60. data/test/hexapdf/test_dictionary_fields.rb +2 -2
  61. data/test/hexapdf/test_document.rb +4 -4
  62. data/test/hexapdf/test_object.rb +44 -26
  63. data/test/hexapdf/test_parser.rb +115 -55
  64. data/test/hexapdf/test_pdf_array.rb +7 -0
  65. data/test/hexapdf/test_revisions.rb +35 -0
  66. data/test/hexapdf/test_writer.rb +2 -2
  67. data/test/hexapdf/type/acro_form/test_appearance_generator.rb +1 -2
  68. data/test/hexapdf/type/acro_form/test_field.rb +39 -0
  69. data/test/hexapdf/type/acro_form/test_form.rb +4 -4
  70. data/test/hexapdf/type/acro_form/test_text_field.rb +2 -0
  71. data/test/hexapdf/type/test_font_simple.rb +2 -1
  72. data/test/hexapdf/type/test_font_true_type.rb +6 -0
  73. data/test/hexapdf/type/test_form.rb +1 -1
  74. data/test/hexapdf/type/test_page.rb +8 -1
  75. data/test/hexapdf/type/test_page_tree_node.rb +42 -0
  76. data/test/hexapdf/utils/test_bit_field.rb +2 -0
  77. data/test/hexapdf/utils/test_sorted_tree_node.rb +10 -9
  78. metadata +5 -12
@@ -107,6 +107,13 @@ describe HexaPDF::PDFArray do
107
107
  assert_equal([1, :data, @array[2]], @array[0, 5])
108
108
  end
109
109
 
110
+ it "allows deleting an object" do
111
+ obj = @array.value[1]
112
+ assert_same(obj, @array.delete(obj))
113
+ ref = HexaPDF::Object.new(:test, oid: 1)
114
+ assert_equal(ref, @array.delete(ref))
115
+ end
116
+
110
117
  describe "slice!" do
111
118
  it "allows deleting a single element" do
112
119
  @array.slice!(2)
@@ -158,4 +158,39 @@ describe HexaPDF::Revisions do
158
158
  doc = HexaPDF::Document.new(io: io)
159
159
  assert_equal(2, doc.revisions.count)
160
160
  end
161
+
162
+ it "uses the reconstructed revision if errors are found when loading from an IO" do
163
+ io = StringIO.new(<<~EOF)
164
+ %PDF-1.7
165
+ 1 0 obj
166
+ 10
167
+ endobj
168
+
169
+ xref
170
+ 0 2
171
+ 0000000000 65535 f
172
+ 0000000009 00000 n
173
+ trailer
174
+ << /Size 5 >>
175
+ startxref
176
+ 28
177
+ %%EOF
178
+
179
+ 2 0 obj
180
+ 300
181
+ endobj
182
+
183
+ xref
184
+ 2 1
185
+ 0000000301 00000 n
186
+ trailer
187
+ << /Size 3 /Prev 100>>
188
+ startxref
189
+ 139
190
+ %%EOF
191
+ EOF
192
+ doc = HexaPDF::Document.new(io: io)
193
+ assert_equal(2, doc.revisions.count)
194
+ assert_same(doc.revisions[0].trailer.value, doc.revisions[1].trailer.value)
195
+ end
161
196
  end
@@ -40,7 +40,7 @@ describe HexaPDF::Writer do
40
40
  219
41
41
  %%EOF
42
42
  3 0 obj
43
- <</Producer(HexaPDF version 0.12.3)>>
43
+ <</Producer(HexaPDF version 0.13.0)>>
44
44
  endobj
45
45
  xref
46
46
  3 1
@@ -72,7 +72,7 @@ describe HexaPDF::Writer do
72
72
  141
73
73
  %%EOF
74
74
  6 0 obj
75
- <</Producer(HexaPDF version 0.12.3)>>
75
+ <</Producer(HexaPDF version 0.13.0)>>
76
76
  endobj
77
77
  2 0 obj
78
78
  <</Length 10>>stream
@@ -478,7 +478,6 @@ describe HexaPDF::Type::AcroForm::AppearanceGenerator do
478
478
  [:restore_graphics_state],
479
479
  [:end_marked_content]])
480
480
  end
481
-
482
481
  end
483
482
 
484
483
  describe "choice fields" do
@@ -495,7 +494,7 @@ describe HexaPDF::Type::AcroForm::AppearanceGenerator do
495
494
 
496
495
  describe "font resolution in case the referenced font is not usable" do
497
496
  before do
498
- @doc.config['acro_form.fallback_font'] = ['Times', variant: :none]
497
+ @doc.config['acro_form.fallback_font'] = ['Times', {variant: :none}]
499
498
  @field[:V] = 'Test'
500
499
  end
501
500
 
@@ -99,6 +99,12 @@ describe HexaPDF::Type::AcroForm::Field do
99
99
  refute(@field.terminal_field?)
100
100
  end
101
101
 
102
+ it "can check whether a widget is embedded in the field" do
103
+ refute(@field.embedded_widget?)
104
+ @field[:Subtype] = :Wdiget
105
+ assert(@field.embedded_widget?)
106
+ end
107
+
102
108
  describe "each_widget" do
103
109
  it "yields a wrapped instance of self if a single widget is embedded" do
104
110
  @field[:Subtype] = :Widget
@@ -175,6 +181,39 @@ describe HexaPDF::Type::AcroForm::Field do
175
181
  end
176
182
  end
177
183
 
184
+ describe "delete_widget" do
185
+ before do
186
+ @page = @doc.pages.add
187
+ end
188
+
189
+ it "does nothing if the provided widget doesn't belong to the field" do
190
+ wrong_widget = @doc.add({Subtype: :Widget})
191
+
192
+ @field.create_widget(@page)
193
+ @field.delete_widget(wrong_widget)
194
+ assert_equal(:Widget, @field[:Subtype])
195
+
196
+ @field.create_widget(@page)
197
+ @field.delete_widget(wrong_widget)
198
+ assert_equal(2, @field[:Kids].size)
199
+ end
200
+
201
+ it "deletes the widget if it is embedded" do
202
+ widget = @field.create_widget(@page)
203
+ @field.delete_widget(widget)
204
+ refute(@field.key?(:Subtype))
205
+ assert(@page[:Annots].empty?)
206
+ end
207
+
208
+ it "deletes the widget if it is not embedded" do
209
+ @field.create_widget(@page)
210
+ widget2 = @field.create_widget(@page)
211
+ @field.delete_widget(widget2)
212
+ assert_equal(1, @field[:Kids].size)
213
+ assert_equal(@field[:Kids].value, @page[:Annots].value)
214
+ end
215
+ end
216
+
178
217
  describe "perform_validation" do
179
218
  before do
180
219
  @field[:FT] = :Tx
@@ -7,7 +7,7 @@ require 'hexapdf/type/acro_form/form'
7
7
  describe HexaPDF::Type::AcroForm::Form do
8
8
  before do
9
9
  @doc = HexaPDF::Document.new
10
- @acro_form = @doc.add({}, type: :XXAcroForm)
10
+ @acro_form = @doc.add({Fields: []}, type: :XXAcroForm)
11
11
  end
12
12
 
13
13
  describe "signature flags" do
@@ -49,7 +49,7 @@ describe HexaPDF::Type::AcroForm::Form do
49
49
  root_fields = @acro_form.find_root_fields
50
50
  assert_equal(result, root_fields.map(&:value))
51
51
  assert_kind_of(HexaPDF::Type::AcroForm::TextField, root_fields[0])
52
- refute(@acro_form.key?(:Fields))
52
+ assert_equal([], @acro_form[:Fields].value)
53
53
 
54
54
  @acro_form.find_root_fields!
55
55
  assert_equal(result, @acro_form[:Fields].value.map(&:value))
@@ -204,9 +204,9 @@ describe HexaPDF::Type::AcroForm::Form do
204
204
 
205
205
  it "checks whether the font used in /DA is available in /DR" do
206
206
  @acro_form[:DA] = '/F2 0 Tf /F1 0 Tf'
207
- refute(@acro_form.validate {|msg, c| assert_match(/DR must also be present/, msg) })
207
+ refute(@acro_form.validate {|msg| assert_match(/DR must also be present/, msg) })
208
208
  @acro_form.default_resources[:Font] = {}
209
- refute(@acro_form.validate {|msg, c| assert_match(/font.*is not.*resource/, msg) })
209
+ refute(@acro_form.validate {|msg| assert_match(/font.*is not.*resource/, msg) })
210
210
  @acro_form.default_resources[:Font][:F1] = :yes
211
211
  assert(@acro_form.validate)
212
212
  end
@@ -114,6 +114,8 @@ describe HexaPDF::Type::AcroForm::TextField do
114
114
  assert(@field.validate)
115
115
  @field[:MaxLen] = 2
116
116
  refute(@field.validate)
117
+ @field[:V] = nil
118
+ assert(@field.validate)
117
119
  end
118
120
  end
119
121
  end
@@ -161,8 +161,9 @@ describe HexaPDF::Type::FontSimple do
161
161
  end
162
162
 
163
163
  describe "validation" do
164
+ before { assert(@font.validate) }
165
+
164
166
  it "validates the existence of required keys" do
165
- assert(@font.validate)
166
167
  @font.delete(:FirstChar)
167
168
  refute(@font.validate)
168
169
  end
@@ -16,6 +16,12 @@ describe HexaPDF::Type::FontTrueType do
16
16
  end
17
17
 
18
18
  describe "validation" do
19
+ it "ignores some missing fields if the font name is one of the standard PDF fonts" do
20
+ @font[:BaseFont] = :'Arial,Bold'
21
+ [:FirstChar, :LastChar, :Widths, :FontDescriptor].each {|field| @font.delete(field) }
22
+ assert(@font.validate)
23
+ end
24
+
19
25
  it "requires that the FontDescriptor key is set" do
20
26
  assert(@font.validate)
21
27
  @font.delete(:FontDescriptor)
@@ -49,7 +49,7 @@ describe HexaPDF::Type::Form do
49
49
  it "creates the resource dictionary if it is not found" do
50
50
  resources = @form.resources
51
51
  assert_equal(:XXResources, resources.type)
52
- assert_equal({}, resources.value)
52
+ assert_equal({ProcSet: [:PDF, :Text, :ImageB, :ImageC, :ImageI]}, resources.value)
53
53
  end
54
54
 
55
55
  it "returns the already used resource dictionary" do
@@ -277,7 +277,7 @@ describe HexaPDF::Type::Page do
277
277
  page = @doc.add({Type: :Page, Parent: @doc.pages.root})
278
278
  resources = page.resources
279
279
  assert_equal(:XXResources, resources.type)
280
- assert_equal({}, resources.value)
280
+ assert_equal({ProcSet: [:PDF, :Text, :ImageB, :ImageC, :ImageI]}, resources.value)
281
281
  end
282
282
 
283
283
  it "returns the already used resource dictionary" do
@@ -319,6 +319,13 @@ describe HexaPDF::Type::Page do
319
319
  end
320
320
  end
321
321
 
322
+ it "returns all ancestor page tree nodes of a page" do
323
+ root = @doc.add({Type: :Pages})
324
+ kid = @doc.add({Type: :Pages, Parent: root})
325
+ page = @doc.add({Type: :Page, Parent: kid})
326
+ assert_equal([kid, root], page.ancestor_nodes)
327
+ end
328
+
322
329
  describe "canvas" do
323
330
  before do
324
331
  @page = @doc.pages.add
@@ -206,6 +206,48 @@ describe HexaPDF::Type::PageTreeNode do
206
206
  end
207
207
  end
208
208
 
209
+ describe "move_page" do
210
+ before do
211
+ define_multilevel_page_tree
212
+ end
213
+
214
+ it "moves the page to the first place" do
215
+ @root.move_page(@pages[1], 0)
216
+ assert_equal([@pages[1], @pages[0], *@pages[2..-1]], @root.each_page.to_a)
217
+ assert(@root.validate)
218
+ end
219
+
220
+ it "moves the page to the correct location with a positive index" do
221
+ @root.move_page(1, 3)
222
+ assert_equal([@pages[0], @pages[2], @pages[1], *@pages[3..-1]], @root.each_page.to_a)
223
+ assert(@root.validate)
224
+ end
225
+
226
+ it "moves the page to the last place" do
227
+ @root.move_page(1, -1)
228
+ assert_equal([@pages[0], *@pages[2..-1], @pages[1]], @root.each_page.to_a)
229
+ assert(@root.validate)
230
+ end
231
+
232
+ it "fails if the index to the moving page is invalid" do
233
+ assert_raises(HexaPDF::Error) { @root.move_page(10, 0) }
234
+ end
235
+
236
+ it "fails if the moving page was deleted/is null" do
237
+ @doc.delete(@pages[0])
238
+ assert_raises(HexaPDF::Error) { @root.move_page(@pages[0], 3) }
239
+ end
240
+
241
+ it "fails if the page was not yet added to a page tree" do
242
+ page = @doc.add({Type: :Page})
243
+ assert_raises(HexaPDF::Error) { @root.move_page(page, 3) }
244
+ end
245
+
246
+ it "fails if the page is not part of the page tree" do
247
+ assert_raises(HexaPDF::Error) { @kid1.move_page(@pages[6], 3) }
248
+ end
249
+ end
250
+
209
251
  describe "each_page" do
210
252
  before do
211
253
  define_multilevel_page_tree
@@ -6,7 +6,9 @@ require 'hexapdf/utils/bit_field'
6
6
  class TestBitField
7
7
 
8
8
  extend HexaPDF::Utils::BitField
9
+
9
10
  attr_accessor :data
11
+
10
12
  bit_field(:data, {bit0: 0, bit1: 1, bit5: 5}, lister: "list", getter: "get", setter: "set",
11
13
  unsetter: 'unset')
12
14
 
@@ -13,9 +13,9 @@ describe HexaPDF::Utils::SortedTreeNode do
13
13
 
14
14
  def add_multilevel_entries
15
15
  @kid11 = @doc.add({Limits: ['c', 'f'], Names: ['c', 1, 'f', 1]}, type: HexaPDF::NameTreeNode)
16
- @kid12 = @doc.add({Limits: ['i', 'm'], Names: ['i', 1, 'm', 1]})
16
+ @kid12 = @doc.add({Limits: ['i', 'm'], Names: ['i', 1, 'm', 1]}, type: HexaPDF::NameTreeNode)
17
17
  ref = HexaPDF::Reference.new(@kid11.oid, @kid11.gen)
18
- @kid1 = @doc.add({Limits: ['c', 'm'], Kids: [ref, @kid12]})
18
+ @kid1 = @doc.add({Limits: ['c', 'm'], Kids: [ref, @kid12]}, type: HexaPDF::NameTreeNode)
19
19
  @kid21 = @doc.add({Limits: ['o', 'q'], Names: ['o', 1, 'q', 1]}, type: HexaPDF::NameTreeNode)
20
20
  @kid221 = @doc.add({Limits: ['s', 'u'], Names: ['s', 1, 'u', 1]}, type: HexaPDF::NameTreeNode)
21
21
  @kid22 = @doc.add({Limits: ['s', 'u'], Kids: [@kid221]}, type: HexaPDF::NameTreeNode)
@@ -73,11 +73,11 @@ describe HexaPDF::Utils::SortedTreeNode do
73
73
  @root.add_entry('p', 1)
74
74
  @root.add_entry('r', 1)
75
75
  @root.add_entry('v', 1)
76
- assert_equal(['a', 'm'], @kid1[:Limits])
76
+ assert_equal(['a', 'm'], @kid1[:Limits].value)
77
77
  assert_equal(['a', 'f'], @kid11[:Limits].value)
78
78
  assert_equal(['a', 1, 'c', 1, 'e', 1, 'f', 1], @kid11[:Names].value)
79
- assert_equal(['g', 'm'], @kid12[:Limits])
80
- assert_equal(['g', 1, 'i', 1, 'j', 1, 'm', 1], @kid12[:Names])
79
+ assert_equal(['g', 'm'], @kid12[:Limits].value)
80
+ assert_equal(['g', 1, 'i', 1, 'j', 1, 'm', 1], @kid12[:Names].value)
81
81
  assert_equal(['n', 'v'], @kid2[:Limits].value)
82
82
  assert_equal(['n', 'q'], @kid21[:Limits].value)
83
83
  assert_equal(['n', 1, 'o', 1, 'p', 1, 'q', 1], @kid21[:Names].value)
@@ -193,15 +193,16 @@ describe HexaPDF::Utils::SortedTreeNode do
193
193
  end
194
194
 
195
195
  it "checks that all kid objects are indirect objects" do
196
- @root[:Kids][0] = HexaPDF::Reference.new(@kid1.oid, @kid1.gen)
196
+ @root[:Kids][0] = ref = HexaPDF::Reference.new(@kid1.oid, @kid1.gen)
197
197
  assert(@root.validate)
198
198
 
199
- @root[:Kids][0] = @kid1
199
+ @root[:Kids][0] = ref
200
200
  @kid1.oid = 0
201
- refute(@root.validate do |message, c|
201
+ assert(@root.validate do |message, c|
202
202
  assert_match(/must be indirect objects/, message)
203
- refute(c)
203
+ assert(c)
204
204
  end)
205
+ assert(@kid1.indirect?)
205
206
  end
206
207
 
207
208
  it "checks that leaf node containers have an even number of entries" do
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: hexapdf
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.12.3
4
+ version: 0.13.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Thomas Leitner
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-08-22 00:00:00.000000000 Z
11
+ date: 2020-11-14 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: cmdparse
@@ -78,20 +78,14 @@ dependencies:
78
78
  requirements:
79
79
  - - "~>"
80
80
  - !ruby/object:Gem::Version
81
- version: '0.82'
82
- - - ">="
83
- - !ruby/object:Gem::Version
84
- version: 0.82.0
81
+ version: '1.0'
85
82
  type: :development
86
83
  prerelease: false
87
84
  version_requirements: !ruby/object:Gem::Requirement
88
85
  requirements:
89
86
  - - "~>"
90
87
  - !ruby/object:Gem::Version
91
- version: '0.82'
92
- - - ">="
93
- - !ruby/object:Gem::Version
94
- version: 0.82.0
88
+ version: '1.0'
95
89
  description: |
96
90
  HexaPDF is a pure Ruby library with an accompanying application for working with PDF
97
91
  files.
@@ -653,8 +647,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
653
647
  - !ruby/object:Gem::Version
654
648
  version: '0'
655
649
  requirements: []
656
- rubyforge_project:
657
- rubygems_version: 2.7.6.2
650
+ rubygems_version: 3.0.3
658
651
  signing_key:
659
652
  specification_version: 4
660
653
  summary: HexaPDF - A Versatile PDF Creation and Manipulation Library For Ruby