hexapdf 0.12.3 → 0.14.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (103) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +132 -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/dictionary.rb +12 -6
  13. data/lib/hexapdf/dictionary_fields.rb +2 -10
  14. data/lib/hexapdf/document.rb +41 -16
  15. data/lib/hexapdf/document/files.rb +0 -1
  16. data/lib/hexapdf/encryption/fast_arc4.rb +1 -1
  17. data/lib/hexapdf/encryption/security_handler.rb +1 -0
  18. data/lib/hexapdf/encryption/standard_security_handler.rb +1 -0
  19. data/lib/hexapdf/font/cmap.rb +1 -4
  20. data/lib/hexapdf/font/true_type/subsetter.rb +16 -3
  21. data/lib/hexapdf/font/true_type/table/head.rb +1 -0
  22. data/lib/hexapdf/font/true_type/table/os2.rb +2 -0
  23. data/lib/hexapdf/font/true_type/table/post.rb +15 -10
  24. data/lib/hexapdf/font_loader/from_configuration.rb +2 -2
  25. data/lib/hexapdf/font_loader/from_file.rb +18 -8
  26. data/lib/hexapdf/image_loader/png.rb +3 -2
  27. data/lib/hexapdf/importer.rb +3 -2
  28. data/lib/hexapdf/layout/line.rb +1 -1
  29. data/lib/hexapdf/layout/style.rb +23 -23
  30. data/lib/hexapdf/layout/text_layouter.rb +2 -2
  31. data/lib/hexapdf/layout/text_shaper.rb +3 -2
  32. data/lib/hexapdf/object.rb +52 -25
  33. data/lib/hexapdf/parser.rb +107 -7
  34. data/lib/hexapdf/pdf_array.rb +15 -5
  35. data/lib/hexapdf/revisions.rb +29 -21
  36. data/lib/hexapdf/serializer.rb +37 -10
  37. data/lib/hexapdf/task/optimize.rb +6 -4
  38. data/lib/hexapdf/tokenizer.rb +22 -0
  39. data/lib/hexapdf/type/acro_form/appearance_generator.rb +130 -27
  40. data/lib/hexapdf/type/acro_form/button_field.rb +5 -2
  41. data/lib/hexapdf/type/acro_form/choice_field.rb +68 -14
  42. data/lib/hexapdf/type/acro_form/field.rb +35 -5
  43. data/lib/hexapdf/type/acro_form/form.rb +139 -14
  44. data/lib/hexapdf/type/acro_form/text_field.rb +70 -4
  45. data/lib/hexapdf/type/actions/uri.rb +3 -2
  46. data/lib/hexapdf/type/annotations/widget.rb +3 -4
  47. data/lib/hexapdf/type/catalog.rb +2 -2
  48. data/lib/hexapdf/type/cid_font.rb +1 -1
  49. data/lib/hexapdf/type/file_specification.rb +1 -1
  50. data/lib/hexapdf/type/font.rb +1 -1
  51. data/lib/hexapdf/type/font_simple.rb +4 -2
  52. data/lib/hexapdf/type/font_true_type.rb +6 -2
  53. data/lib/hexapdf/type/font_type0.rb +4 -4
  54. data/lib/hexapdf/type/form.rb +6 -2
  55. data/lib/hexapdf/type/image.rb +2 -2
  56. data/lib/hexapdf/type/page.rb +21 -12
  57. data/lib/hexapdf/type/page_tree_node.rb +29 -5
  58. data/lib/hexapdf/type/resources.rb +5 -0
  59. data/lib/hexapdf/type/trailer.rb +2 -3
  60. data/lib/hexapdf/utils/object_hash.rb +0 -1
  61. data/lib/hexapdf/utils/sorted_tree_node.rb +18 -15
  62. data/lib/hexapdf/version.rb +1 -1
  63. data/test/hexapdf/common_tokenizer_tests.rb +2 -2
  64. data/test/hexapdf/content/graphic_object/test_arc.rb +4 -4
  65. data/test/hexapdf/content/test_canvas.rb +3 -3
  66. data/test/hexapdf/content/test_color_space.rb +1 -1
  67. data/test/hexapdf/encryption/test_aes.rb +4 -4
  68. data/test/hexapdf/encryption/test_standard_security_handler.rb +11 -11
  69. data/test/hexapdf/filter/test_ascii85_decode.rb +1 -1
  70. data/test/hexapdf/filter/test_ascii_hex_decode.rb +1 -1
  71. data/test/hexapdf/font/true_type/table/test_post.rb +1 -1
  72. data/test/hexapdf/font/true_type/test_subsetter.rb +10 -0
  73. data/test/hexapdf/font_loader/test_from_configuration.rb +7 -3
  74. data/test/hexapdf/font_loader/test_from_file.rb +7 -0
  75. data/test/hexapdf/layout/test_text_layouter.rb +12 -5
  76. data/test/hexapdf/test_configuration.rb +2 -2
  77. data/test/hexapdf/test_dictionary.rb +8 -1
  78. data/test/hexapdf/test_dictionary_fields.rb +9 -2
  79. data/test/hexapdf/test_document.rb +18 -10
  80. data/test/hexapdf/test_object.rb +71 -26
  81. data/test/hexapdf/test_parser.rb +205 -51
  82. data/test/hexapdf/test_pdf_array.rb +8 -1
  83. data/test/hexapdf/test_revisions.rb +35 -0
  84. data/test/hexapdf/test_serializer.rb +7 -0
  85. data/test/hexapdf/test_tokenizer.rb +28 -0
  86. data/test/hexapdf/test_writer.rb +2 -2
  87. data/test/hexapdf/type/acro_form/test_appearance_generator.rb +288 -35
  88. data/test/hexapdf/type/acro_form/test_button_field.rb +15 -0
  89. data/test/hexapdf/type/acro_form/test_choice_field.rb +92 -9
  90. data/test/hexapdf/type/acro_form/test_field.rb +39 -0
  91. data/test/hexapdf/type/acro_form/test_form.rb +87 -15
  92. data/test/hexapdf/type/acro_form/test_text_field.rb +77 -1
  93. data/test/hexapdf/type/test_font_simple.rb +2 -1
  94. data/test/hexapdf/type/test_font_true_type.rb +6 -0
  95. data/test/hexapdf/type/test_form.rb +8 -1
  96. data/test/hexapdf/type/test_page.rb +8 -1
  97. data/test/hexapdf/type/test_page_tree_node.rb +42 -0
  98. data/test/hexapdf/type/test_resources.rb +6 -0
  99. data/test/hexapdf/utils/test_bit_field.rb +2 -0
  100. data/test/hexapdf/utils/test_object_hash.rb +5 -0
  101. data/test/hexapdf/utils/test_sorted_tree_node.rb +10 -9
  102. data/test/test_helper.rb +2 -0
  103. metadata +6 -12
@@ -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))
@@ -115,6 +115,10 @@ describe HexaPDF::Type::AcroForm::Form do
115
115
  end
116
116
 
117
117
  describe "create fields" do
118
+ before do
119
+ @acro_form = @doc.acro_form(create: true)
120
+ end
121
+
118
122
  describe "handles the general case" do
119
123
  it "works for names with a dot" do
120
124
  @acro_form[:Fields] = [{T: "root"}]
@@ -134,9 +138,57 @@ describe HexaPDF::Type::AcroForm::Form do
134
138
  end
135
139
  end
136
140
 
141
+ def applies_variable_text_properties(method, **args)
142
+ field = @acro_form.send(method, "field", **args, font: 'Times')
143
+ font_name, font_size = field.parse_default_appearance_string
144
+ assert_equal(:'Times-Roman', @acro_form.default_resources.font(font_name)[:BaseFont])
145
+ assert_equal(0, font_size)
146
+
147
+ field = @acro_form.send(method, "field", **args, font_size: 10)
148
+ font_name, font_size = field.parse_default_appearance_string
149
+ assert_equal(:Helvetica, @acro_form.default_resources.font(font_name)[:BaseFont])
150
+ assert_equal(10, font_size)
151
+
152
+ field = @acro_form.send(method, "field", **args, font: 'Courier', font_size: 10, align: :center)
153
+ font_name, font_size = field.parse_default_appearance_string
154
+ assert_equal(:Courier, @acro_form.default_resources.font(font_name)[:BaseFont])
155
+ assert_equal(10, font_size)
156
+ assert_equal(:center, field.text_alignment)
157
+ end
158
+
137
159
  it "creates a text field" do
138
160
  field = @acro_form.create_text_field("field")
139
161
  assert_equal(:Tx, field.field_type)
162
+ applies_variable_text_properties(:create_text_field)
163
+ end
164
+
165
+ it "creates a multiline text field" do
166
+ field = @acro_form.create_multiline_text_field("field")
167
+ assert_equal(:Tx, field.field_type)
168
+ assert(field.multiline_text_field?)
169
+ applies_variable_text_properties(:create_multiline_text_field)
170
+ end
171
+
172
+ it "creates a comb text field" do
173
+ field = @acro_form.create_comb_text_field("field", max_chars: 9)
174
+ assert_equal(:Tx, field.field_type)
175
+ assert_equal(9, field[:MaxLen])
176
+ assert(field.comb_text_field?)
177
+ applies_variable_text_properties(:create_comb_text_field, max_chars: 9)
178
+ end
179
+
180
+ it "creates a password field" do
181
+ field = @acro_form.create_password_field("field")
182
+ assert_equal(:Tx, field.field_type)
183
+ assert(field.password_field?)
184
+ applies_variable_text_properties(:create_password_field)
185
+ end
186
+
187
+ it "creates a file select field" do
188
+ field = @acro_form.create_file_select_field("field")
189
+ assert_equal(:Tx, field.field_type)
190
+ assert(field.file_select_field?)
191
+ applies_variable_text_properties(:create_file_select_field)
140
192
  end
141
193
 
142
194
  it "creates a check box" do
@@ -150,13 +202,19 @@ describe HexaPDF::Type::AcroForm::Form do
150
202
  end
151
203
 
152
204
  it "creates a combo box" do
153
- field = @acro_form.create_combo_box("field")
205
+ field = @acro_form.create_combo_box("field", option_items: ['a', 'b', 'c'], editable: true)
154
206
  assert(field.combo_box?)
207
+ assert_equal(['a', 'b', 'c'], field.option_items)
208
+ assert(field.flagged?(:edit))
209
+ applies_variable_text_properties(:create_combo_box)
155
210
  end
156
211
 
157
212
  it "creates a list box" do
158
- field = @acro_form.create_list_box("field")
213
+ field = @acro_form.create_list_box("field", option_items: ['a', 'b', 'c'], multi_select: true)
159
214
  assert(field.list_box?)
215
+ assert_equal(['a', 'b', 'c'], field.option_items)
216
+ assert(field.flagged?(:multi_select))
217
+ applies_variable_text_properties(:create_list_box)
160
218
  end
161
219
  end
162
220
 
@@ -185,15 +243,29 @@ describe HexaPDF::Type::AcroForm::Form do
185
243
  assert(@acro_form[:NeedAppearances])
186
244
  end
187
245
 
188
- it "creates the appearances of all field widgets if necessary" do
189
- tf = @acro_form.create_text_field('test')
190
- tf.set_default_appearance_string
191
- tf.create_widget(@doc.pages.add)
192
- cb = @acro_form.create_check_box('test2')
193
- cb.create_widget(@doc.pages.add)
194
- @acro_form.create_appearances
195
- assert(tf.each_widget.all? {|w| w.appearance.normal_appearance.kind_of?(HexaPDF::Stream) })
196
- assert(cb.each_widget.all? {|w| w.appearance.normal_appearance[:Yes].kind_of?(HexaPDF::Stream) })
246
+ describe "create_appearances" do
247
+ before do
248
+ @tf = @acro_form.create_text_field('test')
249
+ @tf.set_default_appearance_string
250
+ @tf.create_widget(@doc.pages.add)
251
+ @cb = @acro_form.create_check_box('test2')
252
+ @cb.create_widget(@doc.pages.add)
253
+ end
254
+
255
+ it "creates the appearances of all field widgets if necessary" do
256
+ @acro_form.create_appearances
257
+ assert(@tf.each_widget.all? {|w| w.appearance.normal_appearance.kind_of?(HexaPDF::Stream) })
258
+ assert(@cb.each_widget.all? {|w| w.appearance.normal_appearance[:Yes].kind_of?(HexaPDF::Stream) })
259
+ end
260
+
261
+ it "force the creation of appearances if force is true" do
262
+ @acro_form.create_appearances
263
+ text_stream = @tf[:AP][:N].raw_stream
264
+ @acro_form.create_appearances
265
+ assert_same(text_stream, @tf[:AP][:N].raw_stream)
266
+ @acro_form.create_appearances(force: true)
267
+ refute_same(text_stream, @tf[:AP][:N].raw_stream)
268
+ end
197
269
  end
198
270
 
199
271
  describe "perform_validation" do
@@ -204,9 +276,9 @@ describe HexaPDF::Type::AcroForm::Form do
204
276
 
205
277
  it "checks whether the font used in /DA is available in /DR" do
206
278
  @acro_form[:DA] = '/F2 0 Tf /F1 0 Tf'
207
- refute(@acro_form.validate {|msg, c| assert_match(/DR must also be present/, msg) })
279
+ refute(@acro_form.validate {|msg| assert_match(/DR must also be present/, msg) })
208
280
  @acro_form.default_resources[:Font] = {}
209
- refute(@acro_form.validate {|msg, c| assert_match(/font.*is not.*resource/, msg) })
281
+ refute(@acro_form.validate {|msg| assert_match(/font.*is not.*resource/, msg) })
210
282
  @acro_form.default_resources[:Font][:F1] = :yes
211
283
  assert(@acro_form.validate)
212
284
  end
@@ -21,6 +21,56 @@ describe HexaPDF::Type::AcroForm::TextField do
21
21
  assert_equal(6, @field[:MaxLen])
22
22
  end
23
23
 
24
+ it "can be initialized as a multiline text field" do
25
+ @field.flag(:comb)
26
+ @field.initialize_as_multiline_text_field
27
+ assert(@field.multiline_text_field?)
28
+ end
29
+
30
+ it "can be initialized as comb text field" do
31
+ @field.flag(:multiline)
32
+ @field.initialize_as_comb_text_field
33
+ assert(@field.comb_text_field?)
34
+ end
35
+
36
+ it "can be initialized as password field" do
37
+ @field.flag(:multiline)
38
+ @field[:V] = 'test'
39
+ @field.initialize_as_password_field
40
+ assert_nil(@field[:V])
41
+ assert(@field.password_field?)
42
+ end
43
+
44
+ it "can be initialized as a file select field" do
45
+ @field.flag(:multiline)
46
+ @field.initialize_as_file_select_field
47
+ assert(@field.file_select_field?)
48
+ end
49
+
50
+ it "can check whether the field is a multiline text field" do
51
+ refute(@field.multiline_text_field?)
52
+ @field.flag(:multiline)
53
+ assert(@field.multiline_text_field?)
54
+ end
55
+
56
+ it "can check whether the field is a comb text field" do
57
+ refute(@field.comb_text_field?)
58
+ @field.flag(:comb)
59
+ assert(@field.comb_text_field?)
60
+ end
61
+
62
+ it "can check whether the field is a password field" do
63
+ refute(@field.password_field?)
64
+ @field.flag(:password)
65
+ assert(@field.password_field?)
66
+ end
67
+
68
+ it "can check whether the field is a file select field" do
69
+ refute(@field.file_select_field?)
70
+ @field.flag(:file_select)
71
+ assert(@field.file_select_field?)
72
+ end
73
+
24
74
  describe "field_value" do
25
75
  it "handles unset values" do
26
76
  assert_nil(@field.field_value)
@@ -81,14 +131,38 @@ describe HexaPDF::Type::AcroForm::TextField do
81
131
  end
82
132
 
83
133
  describe "create_appearances" do
84
- it "creates the needed streams" do
134
+ before do
85
135
  @doc.acro_form(create: true)
86
136
  @field.create_widget(@doc.pages.add, Rect: [0, 0, 0, 0])
87
137
  @field.set_default_appearance_string
138
+ end
139
+
140
+ it "creates the needed streams" do
88
141
  @field.create_appearances
89
142
  assert(@field[:AP][:N])
90
143
  end
91
144
 
145
+ it "doesn't create a new appearance stream if the field value hasn't changed, checked per widget" do
146
+ @field.create_appearances
147
+ stream = @field[:AP][:N].raw_stream
148
+ @field.create_appearances
149
+ assert_same(stream, @field[:AP][:N].raw_stream)
150
+ @field.field_value = 'test'
151
+ refute_same(stream, @field[:AP][:N].raw_stream)
152
+
153
+ widget = @field.create_widget(@doc.pages.add, Rect: [0, 0, 0, 0])
154
+ assert_nil(widget[:AP])
155
+ @field.create_appearances
156
+ refute_nil(widget[:AP][:N])
157
+ end
158
+
159
+ it "always creates a new appearance stream if force is true" do
160
+ @field.create_appearances
161
+ stream = @field[:AP][:N].raw_stream
162
+ @field.create_appearances(force: true)
163
+ refute_same(stream, @field[:AP][:N].raw_stream)
164
+ end
165
+
92
166
  it "uses the configuration option acro_form.appearance_generator" do
93
167
  @doc.config['acro_form.appearance_generator'] = 'NonExistent'
94
168
  assert_raises(Exception) { @field.create_appearances }
@@ -114,6 +188,8 @@ describe HexaPDF::Type::AcroForm::TextField do
114
188
  assert(@field.validate)
115
189
  @field[:MaxLen] = 2
116
190
  refute(@field.validate)
191
+ @field[:V] = nil
192
+ assert(@field.validate)
117
193
  end
118
194
  end
119
195
  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)
@@ -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
@@ -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
@@ -194,6 +194,12 @@ describe HexaPDF::Type::Resources do
194
194
  assert_equal([:PDF, :Text, :ImageB, :ImageC, :ImageI], @res[:ProcSet].value)
195
195
  end
196
196
 
197
+ it "handles an invalid ProcSet containing a single value instead of an array" do
198
+ @res[:ProcSet] = :PDF
199
+ @res.validate
200
+ assert_equal([:PDF], @res[:ProcSet].value)
201
+ end
202
+
197
203
  it "removes invalid procedure set names from ProcSet" do
198
204
  @res[:ProcSet] = [:PDF, :Unknown]
199
205
  @res.validate