hexapdf 0.12.0 → 0.14.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (99) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +126 -0
  3. data/examples/019-acro_form.rb +41 -4
  4. data/lib/hexapdf/cli/command.rb +4 -2
  5. data/lib/hexapdf/cli/image2pdf.rb +2 -1
  6. data/lib/hexapdf/cli/info.rb +51 -2
  7. data/lib/hexapdf/cli/inspect.rb +30 -8
  8. data/lib/hexapdf/cli/merge.rb +1 -1
  9. data/lib/hexapdf/cli/split.rb +74 -14
  10. data/lib/hexapdf/configuration.rb +15 -0
  11. data/lib/hexapdf/content/graphic_object/arc.rb +3 -3
  12. data/lib/hexapdf/content/parser.rb +1 -1
  13. data/lib/hexapdf/dictionary.rb +4 -4
  14. data/lib/hexapdf/dictionary_fields.rb +1 -9
  15. data/lib/hexapdf/document.rb +41 -16
  16. data/lib/hexapdf/document/files.rb +0 -1
  17. data/lib/hexapdf/encryption/fast_arc4.rb +1 -1
  18. data/lib/hexapdf/encryption/security_handler.rb +1 -0
  19. data/lib/hexapdf/encryption/standard_security_handler.rb +1 -0
  20. data/lib/hexapdf/font/cmap.rb +1 -4
  21. data/lib/hexapdf/font/encoding/base.rb +8 -0
  22. data/lib/hexapdf/font/encoding/difference_encoding.rb +6 -0
  23. data/lib/hexapdf/font/true_type/table/head.rb +1 -0
  24. data/lib/hexapdf/font/true_type/table/os2.rb +2 -0
  25. data/lib/hexapdf/font/type1_wrapper.rb +1 -1
  26. data/lib/hexapdf/image_loader/png.rb +3 -2
  27. data/lib/hexapdf/layout/line.rb +1 -1
  28. data/lib/hexapdf/layout/style.rb +23 -23
  29. data/lib/hexapdf/layout/text_layouter.rb +2 -2
  30. data/lib/hexapdf/layout/text_shaper.rb +3 -2
  31. data/lib/hexapdf/object.rb +52 -25
  32. data/lib/hexapdf/parser.rb +87 -3
  33. data/lib/hexapdf/pdf_array.rb +11 -4
  34. data/lib/hexapdf/revisions.rb +29 -21
  35. data/lib/hexapdf/serializer.rb +1 -1
  36. data/lib/hexapdf/task/optimize.rb +6 -4
  37. data/lib/hexapdf/tokenizer.rb +4 -3
  38. data/lib/hexapdf/type/acro_form/appearance_generator.rb +132 -28
  39. data/lib/hexapdf/type/acro_form/button_field.rb +21 -13
  40. data/lib/hexapdf/type/acro_form/choice_field.rb +68 -14
  41. data/lib/hexapdf/type/acro_form/field.rb +35 -5
  42. data/lib/hexapdf/type/acro_form/form.rb +139 -14
  43. data/lib/hexapdf/type/acro_form/text_field.rb +70 -4
  44. data/lib/hexapdf/type/actions/uri.rb +3 -2
  45. data/lib/hexapdf/type/annotations/widget.rb +3 -4
  46. data/lib/hexapdf/type/catalog.rb +2 -2
  47. data/lib/hexapdf/type/cid_font.rb +1 -1
  48. data/lib/hexapdf/type/file_specification.rb +1 -1
  49. data/lib/hexapdf/type/font.rb +1 -1
  50. data/lib/hexapdf/type/font_simple.rb +4 -2
  51. data/lib/hexapdf/type/font_true_type.rb +6 -2
  52. data/lib/hexapdf/type/font_type0.rb +4 -4
  53. data/lib/hexapdf/type/form.rb +15 -2
  54. data/lib/hexapdf/type/image.rb +2 -2
  55. data/lib/hexapdf/type/page.rb +37 -13
  56. data/lib/hexapdf/type/page_tree_node.rb +29 -5
  57. data/lib/hexapdf/type/resources.rb +1 -0
  58. data/lib/hexapdf/type/trailer.rb +2 -3
  59. data/lib/hexapdf/utils/object_hash.rb +0 -1
  60. data/lib/hexapdf/utils/sorted_tree_node.rb +18 -15
  61. data/lib/hexapdf/version.rb +1 -1
  62. data/test/hexapdf/common_tokenizer_tests.rb +6 -1
  63. data/test/hexapdf/content/graphic_object/test_arc.rb +4 -4
  64. data/test/hexapdf/content/test_canvas.rb +3 -3
  65. data/test/hexapdf/content/test_color_space.rb +1 -1
  66. data/test/hexapdf/encryption/test_aes.rb +4 -4
  67. data/test/hexapdf/encryption/test_standard_security_handler.rb +11 -11
  68. data/test/hexapdf/filter/test_ascii85_decode.rb +1 -1
  69. data/test/hexapdf/filter/test_ascii_hex_decode.rb +1 -1
  70. data/test/hexapdf/font/encoding/test_base.rb +10 -0
  71. data/test/hexapdf/font/encoding/test_difference_encoding.rb +8 -0
  72. data/test/hexapdf/font/test_type1_wrapper.rb +4 -3
  73. data/test/hexapdf/layout/test_style.rb +1 -1
  74. data/test/hexapdf/layout/test_text_layouter.rb +12 -5
  75. data/test/hexapdf/test_configuration.rb +2 -2
  76. data/test/hexapdf/test_dictionary.rb +3 -1
  77. data/test/hexapdf/test_dictionary_fields.rb +2 -2
  78. data/test/hexapdf/test_document.rb +18 -10
  79. data/test/hexapdf/test_object.rb +71 -26
  80. data/test/hexapdf/test_parser.rb +159 -53
  81. data/test/hexapdf/test_pdf_array.rb +8 -1
  82. data/test/hexapdf/test_revisions.rb +35 -0
  83. data/test/hexapdf/test_writer.rb +2 -2
  84. data/test/hexapdf/type/acro_form/test_appearance_generator.rb +296 -38
  85. data/test/hexapdf/type/acro_form/test_button_field.rb +22 -2
  86. data/test/hexapdf/type/acro_form/test_choice_field.rb +92 -9
  87. data/test/hexapdf/type/acro_form/test_field.rb +39 -0
  88. data/test/hexapdf/type/acro_form/test_form.rb +87 -15
  89. data/test/hexapdf/type/acro_form/test_text_field.rb +77 -1
  90. data/test/hexapdf/type/test_font_simple.rb +2 -1
  91. data/test/hexapdf/type/test_font_true_type.rb +6 -0
  92. data/test/hexapdf/type/test_form.rb +26 -1
  93. data/test/hexapdf/type/test_page.rb +45 -7
  94. data/test/hexapdf/type/test_page_tree_node.rb +42 -0
  95. data/test/hexapdf/utils/test_bit_field.rb +2 -0
  96. data/test/hexapdf/utils/test_object_hash.rb +5 -0
  97. data/test/hexapdf/utils/test_sorted_tree_node.rb +10 -9
  98. data/test/test_helper.rb +2 -0
  99. metadata +6 -11
@@ -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)
@@ -43,13 +43,20 @@ describe HexaPDF::Type::Form do
43
43
  @form.contents = 'test'
44
44
  assert_equal('test', @form.stream)
45
45
  end
46
+
47
+ it "clears the cache to make sure that a new canvas can be created" do
48
+ @form[:BBox] = [0, 0, 100, 100]
49
+ canvas = @form.canvas
50
+ @form.contents = ''
51
+ refute_same(canvas, @form.canvas)
52
+ end
46
53
  end
47
54
 
48
55
  describe "resources" do
49
56
  it "creates the resource dictionary if it is not found" do
50
57
  resources = @form.resources
51
58
  assert_equal(:XXResources, resources.type)
52
- assert_equal({}, resources.value)
59
+ assert_equal({ProcSet: [:PDF, :Text, :ImageB, :ImageC, :ImageI]}, resources.value)
53
60
  end
54
61
 
55
62
  it "returns the already used resource dictionary" do
@@ -82,9 +89,27 @@ describe HexaPDF::Type::Form do
82
89
  end
83
90
 
84
91
  describe "canvas" do
92
+ # Asserts that the form's contents contains the operators.
93
+ def assert_operators(form, operators)
94
+ processor = TestHelper::OperatorRecorder.new
95
+ form.process_contents(processor)
96
+ assert_equal(operators, processor.recorded_ops)
97
+ end
98
+
85
99
  it "always returns the same Canvas instance" do
100
+ @form[:BBox] = [0, 0, 100, 100]
86
101
  canvas = @form.canvas
87
102
  assert_same(canvas, @form.canvas)
103
+ assert_operators(@form, [])
104
+ end
105
+
106
+ it "always moves the origin to the bottom left corner of the bounding box" do
107
+ @form[:BBox] = [-10, -5, 100, 300]
108
+ @form.canvas.line_width = 5
109
+ assert_operators(@form, [[:save_graphics_state],
110
+ [:concatenate_matrix, [1, 0, 0, 1, -10, -5]],
111
+ [:set_line_width, [5]],
112
+ [:restore_graphics_state]])
88
113
  end
89
114
 
90
115
  it "fails if the form XObject already has data" 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
@@ -331,31 +338,36 @@ describe HexaPDF::Type::Page do
331
338
  @page.canvas(type: :overlay).line_width = 5
332
339
  assert_operators(@page, [[:save_graphics_state], [:restore_graphics_state],
333
340
  [:save_graphics_state], [:set_line_width, [10]],
334
- [:restore_graphics_state], [:set_line_width, [5]]])
341
+ [:restore_graphics_state], [:save_graphics_state],
342
+ [:set_line_width, [5]], [:restore_graphics_state]])
335
343
 
336
344
  @page.canvas(type: :underlay).line_width = 2
337
345
  assert_operators(@page, [[:save_graphics_state], [:set_line_width, [2]],
338
346
  [:restore_graphics_state], [:save_graphics_state],
339
347
  [:set_line_width, [10]],
340
- [:restore_graphics_state], [:set_line_width, [5]]])
348
+ [:restore_graphics_state], [:save_graphics_state],
349
+ [:set_line_width, [5]], [:restore_graphics_state]])
341
350
  end
342
351
 
343
352
  it "works correctly if invoked on an empty page, using type :underlay in first invocation" do
344
353
  @page.canvas(type: :underlay).line_width = 2
345
354
  assert_operators(@page, [[:save_graphics_state], [:set_line_width, [2]],
355
+ [:restore_graphics_state], [:save_graphics_state],
346
356
  [:restore_graphics_state], [:save_graphics_state],
347
357
  [:restore_graphics_state]])
348
358
 
349
359
  @page.canvas.line_width = 10
350
360
  assert_operators(@page, [[:save_graphics_state], [:set_line_width, [2]],
351
361
  [:restore_graphics_state], [:save_graphics_state],
352
- [:set_line_width, [10]], [:restore_graphics_state]])
362
+ [:set_line_width, [10]], [:restore_graphics_state],
363
+ [:save_graphics_state], [:restore_graphics_state]])
353
364
 
354
365
  @page.canvas(type: :overlay).line_width = 5
355
366
  assert_operators(@page, [[:save_graphics_state], [:set_line_width, [2]],
356
367
  [:restore_graphics_state], [:save_graphics_state],
357
368
  [:set_line_width, [10]],
358
- [:restore_graphics_state], [:set_line_width, [5]]])
369
+ [:restore_graphics_state], [:save_graphics_state],
370
+ [:set_line_width, [5]], [:restore_graphics_state]])
359
371
  end
360
372
 
361
373
  it "works correctly if invoked on a page with existing contents" do
@@ -364,13 +376,39 @@ describe HexaPDF::Type::Page do
364
376
  @page.canvas(type: :overlay).line_width = 5
365
377
  assert_operators(@page, [[:save_graphics_state], [:restore_graphics_state],
366
378
  [:save_graphics_state], [:set_line_width, [10]],
367
- [:restore_graphics_state], [:set_line_width, [5]]])
379
+ [:restore_graphics_state],
380
+ [:save_graphics_state], [:set_line_width, [5]],
381
+ [:restore_graphics_state]])
368
382
 
369
383
  @page.canvas(type: :underlay).line_width = 2
370
384
  assert_operators(@page, [[:save_graphics_state], [:set_line_width, [2]],
371
385
  [:restore_graphics_state], [:save_graphics_state],
372
386
  [:set_line_width, [10]],
373
- [:restore_graphics_state], [:set_line_width, [5]]])
387
+ [:restore_graphics_state],
388
+ [:save_graphics_state], [:set_line_width, [5]],
389
+ [:restore_graphics_state]])
390
+ end
391
+
392
+ it "works correctly if the page has its origin not at (0,0)" do
393
+ @page.box(:media, [-10, -5, 100, 300])
394
+ @page.canvas(type: :underlay).line_width = 2
395
+ @page.canvas(type: :page).line_width = 2
396
+ @page.canvas(type: :overlay).line_width = 2
397
+
398
+ assert_operators(@page, [[:save_graphics_state],
399
+ [:concatenate_matrix, [1, 0, 0, 1, -10, -5]],
400
+ [:set_line_width, [2]],
401
+ [:restore_graphics_state],
402
+
403
+ [:save_graphics_state],
404
+ [:concatenate_matrix, [1, 0, 0, 1, -10, -5]],
405
+ [:set_line_width, [2]],
406
+ [:restore_graphics_state],
407
+
408
+ [:save_graphics_state],
409
+ [:concatenate_matrix, [1, 0, 0, 1, -10, -5]],
410
+ [:set_line_width, [2]],
411
+ [:restore_graphics_state]])
374
412
  end
375
413
 
376
414
  it "fails if the page canvas is requested for a page with existing contents" do
@@ -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
 
@@ -112,4 +112,9 @@ describe HexaPDF::Utils::ObjectHash do
112
112
  assert_equal(3, @hash.max_oid)
113
113
  end
114
114
  end
115
+
116
+ it "can return a list of all object IDs" do
117
+ @hash[1, 0] = @hash[3, 1] = 7
118
+ assert_equal([3, 1], @hash.oids)
119
+ end
115
120
  end
@@ -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
@@ -16,6 +16,8 @@ require 'zlib'
16
16
  TEST_DATA_DIR = File.join(__dir__, 'data')
17
17
  MINIMAL_PDF = File.read(File.join(TEST_DATA_DIR, 'minimal.pdf')).freeze
18
18
 
19
+ Minitest::Test.make_my_diffs_pretty!
20
+
19
21
  module TestHelper
20
22
 
21
23
  # Asserts that the method +name+ of +object+ gets invoked with the +expected_values+ when
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.0
4
+ version: 0.14.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-12 00:00:00.000000000 Z
11
+ date: 2020-12-30 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.
@@ -637,6 +631,7 @@ files:
637
631
  homepage: https://hexapdf.gettalong.org
638
632
  licenses:
639
633
  - AGPL-3.0
634
+ - Commercial License
640
635
  metadata: {}
641
636
  post_install_message:
642
637
  rdoc_options: []
@@ -653,7 +648,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
653
648
  - !ruby/object:Gem::Version
654
649
  version: '0'
655
650
  requirements: []
656
- rubygems_version: 3.1.2
651
+ rubygems_version: 3.2.3
657
652
  signing_key:
658
653
  specification_version: 4
659
654
  summary: HexaPDF - A Versatile PDF Creation and Manipulation Library For Ruby