hexapdf 0.12.0 → 0.14.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.
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