hexapdf 0.21.1 → 0.24.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (253) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +137 -0
  3. data/LICENSE +1 -1
  4. data/Rakefile +1 -1
  5. data/examples/016-frame_automatic_box_placement.rb +7 -2
  6. data/examples/017-frame_text_flow.rb +10 -18
  7. data/examples/020-column_box.rb +20 -37
  8. data/examples/021-list_box.rb +26 -0
  9. data/lib/hexapdf/cli/batch.rb +1 -1
  10. data/lib/hexapdf/cli/command.rb +1 -1
  11. data/lib/hexapdf/cli/files.rb +1 -1
  12. data/lib/hexapdf/cli/fonts.rb +1 -1
  13. data/lib/hexapdf/cli/form.rb +31 -4
  14. data/lib/hexapdf/cli/image2pdf.rb +1 -1
  15. data/lib/hexapdf/cli/images.rb +1 -1
  16. data/lib/hexapdf/cli/info.rb +2 -2
  17. data/lib/hexapdf/cli/inspect.rb +19 -6
  18. data/lib/hexapdf/cli/merge.rb +1 -1
  19. data/lib/hexapdf/cli/modify.rb +24 -4
  20. data/lib/hexapdf/cli/optimize.rb +1 -1
  21. data/lib/hexapdf/cli/split.rb +1 -1
  22. data/lib/hexapdf/cli/watermark.rb +1 -1
  23. data/lib/hexapdf/cli.rb +1 -1
  24. data/lib/hexapdf/composer.rb +66 -125
  25. data/lib/hexapdf/configuration.rb +17 -1
  26. data/lib/hexapdf/content/canvas.rb +1 -1
  27. data/lib/hexapdf/content/color_space.rb +1 -1
  28. data/lib/hexapdf/content/graphic_object/arc.rb +1 -1
  29. data/lib/hexapdf/content/graphic_object/endpoint_arc.rb +1 -1
  30. data/lib/hexapdf/content/graphic_object/geom2d.rb +2 -1
  31. data/lib/hexapdf/content/graphic_object/solid_arc.rb +1 -1
  32. data/lib/hexapdf/content/graphic_object.rb +1 -1
  33. data/lib/hexapdf/content/graphics_state.rb +1 -1
  34. data/lib/hexapdf/content/operator.rb +1 -1
  35. data/lib/hexapdf/content/parser.rb +1 -1
  36. data/lib/hexapdf/content/processor.rb +1 -1
  37. data/lib/hexapdf/content/transformation_matrix.rb +1 -1
  38. data/lib/hexapdf/content.rb +1 -1
  39. data/lib/hexapdf/data_dir.rb +1 -1
  40. data/lib/hexapdf/dictionary.rb +1 -1
  41. data/lib/hexapdf/dictionary_fields.rb +2 -2
  42. data/lib/hexapdf/document/destinations.rb +396 -0
  43. data/lib/hexapdf/document/files.rb +1 -1
  44. data/lib/hexapdf/document/fonts.rb +1 -1
  45. data/lib/hexapdf/document/images.rb +1 -1
  46. data/lib/hexapdf/document/layout.rb +397 -0
  47. data/lib/hexapdf/document/pages.rb +17 -1
  48. data/lib/hexapdf/document/signatures.rb +5 -4
  49. data/lib/hexapdf/document.rb +46 -90
  50. data/lib/hexapdf/encryption/aes.rb +1 -1
  51. data/lib/hexapdf/encryption/arc4.rb +1 -1
  52. data/lib/hexapdf/encryption/fast_aes.rb +1 -1
  53. data/lib/hexapdf/encryption/fast_arc4.rb +30 -21
  54. data/lib/hexapdf/encryption/identity.rb +1 -1
  55. data/lib/hexapdf/encryption/ruby_aes.rb +1 -1
  56. data/lib/hexapdf/encryption/ruby_arc4.rb +1 -1
  57. data/lib/hexapdf/encryption/security_handler.rb +1 -1
  58. data/lib/hexapdf/encryption/standard_security_handler.rb +1 -1
  59. data/lib/hexapdf/encryption.rb +1 -1
  60. data/lib/hexapdf/error.rb +1 -1
  61. data/lib/hexapdf/filter/ascii85_decode.rb +1 -1
  62. data/lib/hexapdf/filter/ascii_hex_decode.rb +1 -1
  63. data/lib/hexapdf/filter/crypt.rb +1 -1
  64. data/lib/hexapdf/filter/encryption.rb +1 -1
  65. data/lib/hexapdf/filter/flate_decode.rb +1 -1
  66. data/lib/hexapdf/filter/lzw_decode.rb +1 -1
  67. data/lib/hexapdf/filter/pass_through.rb +1 -1
  68. data/lib/hexapdf/filter/predictor.rb +1 -1
  69. data/lib/hexapdf/filter/run_length_decode.rb +1 -1
  70. data/lib/hexapdf/filter.rb +1 -1
  71. data/lib/hexapdf/font/cmap/parser.rb +1 -1
  72. data/lib/hexapdf/font/cmap/writer.rb +1 -1
  73. data/lib/hexapdf/font/cmap.rb +1 -1
  74. data/lib/hexapdf/font/encoding/base.rb +1 -1
  75. data/lib/hexapdf/font/encoding/difference_encoding.rb +1 -1
  76. data/lib/hexapdf/font/encoding/glyph_list.rb +2 -2
  77. data/lib/hexapdf/font/encoding/mac_expert_encoding.rb +1 -1
  78. data/lib/hexapdf/font/encoding/mac_roman_encoding.rb +1 -1
  79. data/lib/hexapdf/font/encoding/standard_encoding.rb +1 -1
  80. data/lib/hexapdf/font/encoding/symbol_encoding.rb +1 -1
  81. data/lib/hexapdf/font/encoding/win_ansi_encoding.rb +1 -1
  82. data/lib/hexapdf/font/encoding/zapf_dingbats_encoding.rb +1 -1
  83. data/lib/hexapdf/font/encoding.rb +1 -1
  84. data/lib/hexapdf/font/invalid_glyph.rb +1 -1
  85. data/lib/hexapdf/font/true_type/builder.rb +1 -1
  86. data/lib/hexapdf/font/true_type/font.rb +1 -1
  87. data/lib/hexapdf/font/true_type/optimizer.rb +1 -1
  88. data/lib/hexapdf/font/true_type/subsetter.rb +1 -1
  89. data/lib/hexapdf/font/true_type/table/cmap.rb +1 -1
  90. data/lib/hexapdf/font/true_type/table/cmap_subtable.rb +1 -1
  91. data/lib/hexapdf/font/true_type/table/directory.rb +1 -1
  92. data/lib/hexapdf/font/true_type/table/glyf.rb +1 -1
  93. data/lib/hexapdf/font/true_type/table/head.rb +1 -1
  94. data/lib/hexapdf/font/true_type/table/hhea.rb +1 -1
  95. data/lib/hexapdf/font/true_type/table/hmtx.rb +1 -1
  96. data/lib/hexapdf/font/true_type/table/kern.rb +1 -1
  97. data/lib/hexapdf/font/true_type/table/loca.rb +1 -1
  98. data/lib/hexapdf/font/true_type/table/maxp.rb +1 -1
  99. data/lib/hexapdf/font/true_type/table/name.rb +1 -1
  100. data/lib/hexapdf/font/true_type/table/os2.rb +1 -1
  101. data/lib/hexapdf/font/true_type/table/post.rb +1 -1
  102. data/lib/hexapdf/font/true_type/table.rb +1 -1
  103. data/lib/hexapdf/font/true_type.rb +1 -1
  104. data/lib/hexapdf/font/true_type_wrapper.rb +1 -1
  105. data/lib/hexapdf/font/type1/afm_parser.rb +1 -1
  106. data/lib/hexapdf/font/type1/character_metrics.rb +1 -1
  107. data/lib/hexapdf/font/type1/font.rb +1 -1
  108. data/lib/hexapdf/font/type1/font_metrics.rb +1 -1
  109. data/lib/hexapdf/font/type1/pfb_parser.rb +1 -1
  110. data/lib/hexapdf/font/type1.rb +1 -1
  111. data/lib/hexapdf/font/type1_wrapper.rb +1 -1
  112. data/lib/hexapdf/font_loader/from_configuration.rb +1 -1
  113. data/lib/hexapdf/font_loader/from_file.rb +1 -1
  114. data/lib/hexapdf/font_loader/standard14.rb +1 -1
  115. data/lib/hexapdf/font_loader.rb +1 -1
  116. data/lib/hexapdf/image_loader/jpeg.rb +1 -1
  117. data/lib/hexapdf/image_loader/pdf.rb +1 -1
  118. data/lib/hexapdf/image_loader/png.rb +1 -1
  119. data/lib/hexapdf/image_loader.rb +1 -1
  120. data/lib/hexapdf/importer.rb +1 -1
  121. data/lib/hexapdf/layout/box.rb +121 -22
  122. data/lib/hexapdf/layout/box_fitter.rb +136 -0
  123. data/lib/hexapdf/layout/column_box.rb +168 -89
  124. data/lib/hexapdf/layout/frame.rb +155 -140
  125. data/lib/hexapdf/layout/image_box.rb +19 -4
  126. data/lib/hexapdf/layout/inline_box.rb +1 -1
  127. data/lib/hexapdf/layout/line.rb +1 -1
  128. data/lib/hexapdf/layout/list_box.rb +355 -0
  129. data/lib/hexapdf/layout/numeric_refinements.rb +1 -1
  130. data/lib/hexapdf/layout/style.rb +285 -8
  131. data/lib/hexapdf/layout/text_box.rb +30 -11
  132. data/lib/hexapdf/layout/text_fragment.rb +3 -2
  133. data/lib/hexapdf/layout/text_layouter.rb +23 -3
  134. data/lib/hexapdf/layout/text_shaper.rb +1 -1
  135. data/lib/hexapdf/layout/width_from_polygon.rb +12 -7
  136. data/lib/hexapdf/layout.rb +4 -1
  137. data/lib/hexapdf/name_tree_node.rb +1 -1
  138. data/lib/hexapdf/number_tree_node.rb +1 -1
  139. data/lib/hexapdf/object.rb +1 -1
  140. data/lib/hexapdf/parser.rb +1 -8
  141. data/lib/hexapdf/pdf_array.rb +1 -1
  142. data/lib/hexapdf/rectangle.rb +1 -1
  143. data/lib/hexapdf/reference.rb +1 -1
  144. data/lib/hexapdf/revision.rb +9 -2
  145. data/lib/hexapdf/revisions.rb +152 -51
  146. data/lib/hexapdf/serializer.rb +1 -1
  147. data/lib/hexapdf/stream.rb +1 -1
  148. data/lib/hexapdf/task/dereference.rb +1 -1
  149. data/lib/hexapdf/task/optimize.rb +22 -12
  150. data/lib/hexapdf/task.rb +1 -1
  151. data/lib/hexapdf/tokenizer.rb +1 -1
  152. data/lib/hexapdf/type/acro_form/appearance_generator.rb +1 -1
  153. data/lib/hexapdf/type/acro_form/button_field.rb +1 -1
  154. data/lib/hexapdf/type/acro_form/choice_field.rb +1 -1
  155. data/lib/hexapdf/type/acro_form/field.rb +1 -1
  156. data/lib/hexapdf/type/acro_form/form.rb +12 -6
  157. data/lib/hexapdf/type/acro_form/signature_field.rb +1 -1
  158. data/lib/hexapdf/type/acro_form/text_field.rb +9 -1
  159. data/lib/hexapdf/type/acro_form/variable_text_field.rb +1 -1
  160. data/lib/hexapdf/type/acro_form.rb +1 -1
  161. data/lib/hexapdf/type/action.rb +1 -1
  162. data/lib/hexapdf/type/actions/go_to.rb +1 -1
  163. data/lib/hexapdf/type/actions/go_to_r.rb +1 -1
  164. data/lib/hexapdf/type/actions/launch.rb +1 -1
  165. data/lib/hexapdf/type/actions/uri.rb +1 -1
  166. data/lib/hexapdf/type/actions.rb +1 -1
  167. data/lib/hexapdf/type/annotation.rb +1 -1
  168. data/lib/hexapdf/type/annotations/link.rb +1 -1
  169. data/lib/hexapdf/type/annotations/markup_annotation.rb +1 -1
  170. data/lib/hexapdf/type/annotations/text.rb +1 -1
  171. data/lib/hexapdf/type/annotations/widget.rb +1 -1
  172. data/lib/hexapdf/type/annotations.rb +1 -1
  173. data/lib/hexapdf/type/catalog.rb +10 -2
  174. data/lib/hexapdf/type/cid_font.rb +1 -1
  175. data/lib/hexapdf/type/embedded_file.rb +1 -1
  176. data/lib/hexapdf/type/file_specification.rb +1 -1
  177. data/lib/hexapdf/type/font.rb +1 -1
  178. data/lib/hexapdf/type/font_descriptor.rb +1 -1
  179. data/lib/hexapdf/type/font_simple.rb +1 -1
  180. data/lib/hexapdf/type/font_true_type.rb +1 -1
  181. data/lib/hexapdf/type/font_type0.rb +1 -1
  182. data/lib/hexapdf/type/font_type1.rb +1 -1
  183. data/lib/hexapdf/type/font_type3.rb +1 -1
  184. data/lib/hexapdf/type/form.rb +1 -1
  185. data/lib/hexapdf/type/graphics_state_parameter.rb +1 -1
  186. data/lib/hexapdf/type/icon_fit.rb +1 -1
  187. data/lib/hexapdf/type/image.rb +48 -4
  188. data/lib/hexapdf/type/info.rb +1 -1
  189. data/lib/hexapdf/type/names.rb +14 -1
  190. data/lib/hexapdf/type/object_stream.rb +1 -1
  191. data/lib/hexapdf/type/page.rb +1 -1
  192. data/lib/hexapdf/type/page_tree_node.rb +19 -2
  193. data/lib/hexapdf/type/resources.rb +1 -1
  194. data/lib/hexapdf/type/signature/adbe_pkcs7_detached.rb +1 -1
  195. data/lib/hexapdf/type/signature/adbe_x509_rsa_sha1.rb +1 -1
  196. data/lib/hexapdf/type/signature/handler.rb +1 -1
  197. data/lib/hexapdf/type/signature/verification_result.rb +1 -1
  198. data/lib/hexapdf/type/signature.rb +1 -1
  199. data/lib/hexapdf/type/trailer.rb +2 -2
  200. data/lib/hexapdf/type/viewer_preferences.rb +1 -1
  201. data/lib/hexapdf/type/xref_stream.rb +3 -2
  202. data/lib/hexapdf/type.rb +1 -1
  203. data/lib/hexapdf/utils/bit_field.rb +1 -1
  204. data/lib/hexapdf/utils/bit_stream.rb +1 -1
  205. data/lib/hexapdf/utils/graphics_helpers.rb +1 -1
  206. data/lib/hexapdf/utils/lru_cache.rb +1 -1
  207. data/lib/hexapdf/utils/math_helpers.rb +1 -1
  208. data/lib/hexapdf/utils/object_hash.rb +1 -1
  209. data/lib/hexapdf/utils/pdf_doc_encoding.rb +1 -1
  210. data/lib/hexapdf/utils/sorted_tree_node.rb +4 -2
  211. data/lib/hexapdf/version.rb +2 -2
  212. data/lib/hexapdf/writer.rb +23 -8
  213. data/lib/hexapdf/xref_section.rb +1 -1
  214. data/lib/hexapdf.rb +1 -1
  215. data/test/hexapdf/content/graphic_object/test_geom2d.rb +1 -1
  216. data/test/hexapdf/document/test_destinations.rb +338 -0
  217. data/test/hexapdf/document/test_images.rb +1 -1
  218. data/test/hexapdf/document/test_layout.rb +264 -0
  219. data/test/hexapdf/document/test_pages.rb +9 -0
  220. data/test/hexapdf/document/test_signatures.rb +10 -3
  221. data/test/hexapdf/encryption/test_security_handler.rb +3 -3
  222. data/test/hexapdf/font/encoding/test_glyph_list.rb +4 -0
  223. data/test/hexapdf/layout/test_box.rb +53 -3
  224. data/test/hexapdf/layout/test_box_fitter.rb +62 -0
  225. data/test/hexapdf/layout/test_column_box.rb +159 -0
  226. data/test/hexapdf/layout/test_frame.rb +114 -39
  227. data/test/hexapdf/layout/test_image_box.rb +1 -1
  228. data/test/hexapdf/layout/test_list_box.rb +249 -0
  229. data/test/hexapdf/layout/test_text_box.rb +33 -2
  230. data/test/hexapdf/layout/test_text_fragment.rb +1 -1
  231. data/test/hexapdf/layout/test_text_layouter.rb +49 -17
  232. data/test/hexapdf/layout/test_width_from_polygon.rb +13 -0
  233. data/test/hexapdf/task/test_optimize.rb +17 -4
  234. data/test/hexapdf/test_composer.rb +35 -1
  235. data/test/hexapdf/test_dictionary_fields.rb +10 -10
  236. data/test/hexapdf/test_document.rb +33 -136
  237. data/test/hexapdf/test_filter.rb +1 -1
  238. data/test/hexapdf/test_parser.rb +1 -3
  239. data/test/hexapdf/test_revision.rb +14 -0
  240. data/test/hexapdf/test_revisions.rb +137 -29
  241. data/test/hexapdf/test_serializer.rb +1 -5
  242. data/test/hexapdf/test_writer.rb +99 -15
  243. data/test/hexapdf/type/acro_form/test_form.rb +2 -1
  244. data/test/hexapdf/type/acro_form/test_text_field.rb +17 -0
  245. data/test/hexapdf/type/test_catalog.rb +8 -0
  246. data/test/hexapdf/type/test_image.rb +45 -9
  247. data/test/hexapdf/type/test_names.rb +20 -0
  248. data/test/hexapdf/type/test_page_tree_node.rb +21 -1
  249. data/test/hexapdf/type/test_trailer.rb +3 -3
  250. data/test/hexapdf/type/test_xref_stream.rb +2 -1
  251. data/test/hexapdf/utils/test_sorted_tree_node.rb +11 -1
  252. data/test/test_helper.rb +5 -1
  253. metadata +29 -3
@@ -91,43 +91,6 @@ describe HexaPDF::Document do
91
91
  end
92
92
  end
93
93
 
94
- describe "object" do
95
- it "accepts a Reference object as argument" do
96
- assert_equal(10, @io_doc.object(HexaPDF::Reference.new(1, 0)).value)
97
- end
98
-
99
- it "accepts an object number as arguments" do
100
- assert_equal(10, @io_doc.object(1).value)
101
- end
102
-
103
- it "returns added objects" do
104
- obj = @io_doc.add(@io_doc.wrap({Type: :Test}, oid: 100))
105
- assert_equal(obj, @io_doc.object(100))
106
- end
107
-
108
- it "returns nil for unknown object references" do
109
- assert_nil(@io_doc.object(100))
110
- end
111
-
112
- it "returns only the newest version of an object" do
113
- assert_equal(200, @io_doc.object(2).value)
114
- assert_equal(200, @io_doc.object(HexaPDF::Reference.new(2, 0)).value)
115
- assert_nil(@io_doc.object(3).value)
116
- assert_nil(@io_doc.object(HexaPDF::Reference.new(3, 1)).value)
117
- assert_equal(30, @io_doc.object(HexaPDF::Reference.new(3, 0)).value)
118
- end
119
- end
120
-
121
- describe "object?" do
122
- it "works with a Reference object as argument" do
123
- assert(@io_doc.object?(HexaPDF::Reference.new(1, 0)))
124
- end
125
-
126
- it "works with an object number as arguments" do
127
- assert(@io_doc.object?(1))
128
- end
129
- end
130
-
131
94
  describe "deref" do
132
95
  it "returns a dereferenced object when given a Reference object" do
133
96
  assert_equal(@io_doc.object(1), @io_doc.deref(HexaPDF::Reference.new(1, 0)))
@@ -139,13 +102,6 @@ describe HexaPDF::Document do
139
102
  end
140
103
 
141
104
  describe "add" do
142
- it "automatically assigns free object numbers" do
143
- assert_equal(1, @doc.add(5).oid)
144
- assert_equal(2, @doc.add(5).oid)
145
- @doc.revisions.add
146
- assert_equal(3, @doc.add(5).oid)
147
- end
148
-
149
105
  it "assigns the object's document" do
150
106
  obj = @doc.add(5)
151
107
  assert_equal(@doc, obj.document)
@@ -166,82 +122,38 @@ describe HexaPDF::Document do
166
122
  assert_equal(5, obj.value)
167
123
  end
168
124
 
169
- it "returns the given object if it is already stored in the document" do
170
- obj = @doc.add(5)
171
- assert_same(obj, @doc.add(obj))
172
- end
173
-
174
- it "allows specifying a revision to which the object should be added" do
175
- @doc.revisions.add
176
- @doc.revisions.add
177
-
178
- @doc.add(@doc.wrap(5, oid: 1), revision: 0)
179
- assert_equal(5, @doc.object(1).value)
180
-
181
- @doc.add(@doc.wrap(10, oid: 1), revision: 2)
182
- assert_equal(10, @doc.object(1).value)
183
-
184
- @doc.add(@doc.wrap(7.5, oid: 1), revision: 1)
185
- assert_equal(10, @doc.object(1).value)
186
- end
187
-
188
- it "fails if the specified revision index is invalid" do
189
- assert_raises(ArgumentError) { @doc.add(5, revision: 5) }
190
- end
191
-
192
125
  it "fails if the object to be added is associated with another document" do
193
126
  doc = HexaPDF::Document.new
194
127
  obj = doc.add(5)
195
128
  assert_raises(HexaPDF::Error) { @doc.add(obj) }
196
129
  end
197
-
198
- it "fails if the object number is already associated with another object" do
199
- obj = @doc.add(5)
200
- assert_raises(HexaPDF::Error) { @doc.add(@doc.wrap(5, oid: obj.oid, gen: 1)) }
201
- end
202
130
  end
203
131
 
204
- describe "delete" do
205
- it "works with a Reference object as argument" do
206
- obj = @doc.add(5)
207
- @doc.delete(obj, mark_as_free: false)
208
- refute(@doc.object?(obj))
209
- end
210
-
211
- it "works with an object number as arguments" do
212
- @doc.add(5)
213
- @doc.delete(1, mark_as_free: false)
214
- refute(@doc.object?(1))
215
- end
216
-
217
- describe "with an object in multiple revisions" do
218
- before do
219
- @ref = HexaPDF::Reference.new(2, 3)
220
- obj = @doc.wrap(5, oid: @ref.oid, gen: @ref.gen)
221
- @doc.revisions.add
222
- @doc.add(obj, revision: 0)
223
- @doc.add(obj, revision: 1)
224
- end
225
-
226
- it "deletes an object for all revisions when revision = :all" do
227
- @doc.delete(@ref, revision: :all, mark_as_free: false)
228
- refute(@doc.object?(@ref))
229
- end
230
-
231
- it "deletes an object only in the current revision when revision = :current" do
232
- @doc.delete(@ref, revision: :current, mark_as_free: false)
233
- assert(@doc.object?(@ref))
234
- end
132
+ it "defers to @revisions for retrieving an object" do
133
+ revs = Minitest::Mock.new
134
+ revs.expect(:object, :retval, [:ref])
135
+ doc = HexaPDF::Document.new
136
+ doc.instance_variable_set(:@revisions, revs)
137
+ doc.object(:ref)
138
+ revs.verify
139
+ end
235
140
 
236
- it "marks the object as PDF null object when using mark_as_free=true" do
237
- @doc.delete(@ref, revision: :current)
238
- assert(@doc.object(@ref).null?)
239
- end
240
- end
141
+ it "defers to @revisions for checking for the existence of an object" do
142
+ revs = Minitest::Mock.new
143
+ revs.expect(:object?, :retval, [:ref])
144
+ doc = HexaPDF::Document.new
145
+ doc.instance_variable_set(:@revisions, revs)
146
+ doc.object?(:ref)
147
+ revs.verify
148
+ end
241
149
 
242
- it "fails if the revision argument is invalid" do
243
- assert_raises(ArgumentError) { @doc.delete(1, revision: :invalid) }
244
- end
150
+ it "defers to @revisions for deleting an object" do
151
+ revs = Minitest::Mock.new
152
+ revs.expect(:delete_object, :retval, [:ref])
153
+ doc = HexaPDF::Document.new
154
+ doc.instance_variable_set(:@revisions, revs)
155
+ doc.delete(:ref)
156
+ revs.verify
245
157
  end
246
158
 
247
159
  describe "import" do
@@ -388,28 +300,13 @@ describe HexaPDF::Document do
388
300
  end
389
301
  end
390
302
 
391
- describe "each" do
392
- it "iterates over the current objects" do
393
- assert_equal([10, 200, nil], @io_doc.each(only_current: true).sort.map(&:value))
394
- end
395
-
396
- it "iterates over all objects" do
397
- assert_equal([10, 200, 20, 30, nil], @io_doc.each(only_current: false).sort.map(&:value))
398
- end
399
-
400
- it "iterates over all loaded objects" do
401
- assert_equal(200, @io_doc.object(2).value)
402
- assert_equal([200], @io_doc.each(only_loaded: true).sort.map(&:value))
403
- end
404
-
405
- it "yields the revision as second argument if the block accepts exactly two arguments" do
406
- objs = [[10, 20, 30], [200, nil]]
407
- data = @io_doc.revisions.map.with_index {|rev, i| objs[i].map {|o| [o, rev] } }.reverse.flatten
408
- @io_doc.each(only_current: false) do |obj, rev|
409
- assert(data.shift == obj.value)
410
- assert_equal(data.shift, rev)
411
- end
412
- end
303
+ it "defers to @revisions for iterating over all objects" do
304
+ revs = Minitest::Mock.new
305
+ revs.expect(:each_object, :retval, only_current: true, only_loaded: true)
306
+ doc = HexaPDF::Document.new
307
+ doc.instance_variable_set(:@revisions, revs)
308
+ doc.each(only_current: true, only_loaded: true)
309
+ revs.verify
413
310
  end
414
311
 
415
312
  describe "encryption" do
@@ -431,7 +328,7 @@ describe HexaPDF::Document do
431
328
 
432
329
  it "doesn't decrypt the document if document.auto_encrypt=false" do
433
330
  test_file = File.join(TEST_DATA_DIR, 'standard-security-handler', 'nopwd-arc4-40bit-V1.pdf')
434
- doc = HexaPDF::Document.new(io: StringIO.new(File.read(test_file)),
331
+ doc = HexaPDF::Document.new(io: StringIO.new(File.binread(test_file)),
435
332
  config: {'document.auto_decrypt' => false})
436
333
  assert_kind_of(String, doc.trailer[:Info][:ModDate])
437
334
  handler = HexaPDF::Encryption::SecurityHandler.set_up_decryption(doc)
@@ -613,8 +510,8 @@ describe HexaPDF::Document do
613
510
 
614
511
  it "allows to conveniently sign a document" do
615
512
  mock = Minitest::Mock.new
616
- mock.expect(:handler, :handler, [{name: :handler, opt: :key}])
617
- mock.expect(:add, :added, [:io, :handler, {signature: :sig, write_options: :write_options}])
513
+ mock.expect(:handler, :handler, name: :handler, opt: :key)
514
+ mock.expect(:add, :added, [:io, :handler], signature: :sig, write_options: :write_options)
618
515
  @doc.instance_variable_set(:@signatures, mock)
619
516
  result = @doc.sign(:io, handler: :handler, write_options: :write_options, signature: :sig, opt: :key)
620
517
  assert_equal(:added, result)
@@ -62,7 +62,7 @@ describe HexaPDF::Filter do
62
62
 
63
63
  describe "source_from_file" do
64
64
  before do
65
- @file = Tempfile.new('hexapdf-filter')
65
+ @file = Tempfile.new('hexapdf-filter', binmode: true)
66
66
  @file.write(@str)
67
67
  @file.close
68
68
  end
@@ -543,14 +543,12 @@ describe HexaPDF::Parser do
543
543
  xref_section, trailer = @parser.load_revision(@parser.startxref_offset)
544
544
  assert_equal({Test: 'now'}, trailer)
545
545
  assert(xref_section[1].in_use?)
546
- refute(@parser.contains_xref_streams?)
547
546
  end
548
547
 
549
548
  it "works for a cross-reference stream" do
550
549
  xref_section, trailer = @parser.load_revision(212)
551
- assert_equal({Size: 2}, trailer)
550
+ assert_equal({Size: 2, Type: :XRef}, trailer)
552
551
  assert(xref_section[1].in_use?)
553
- assert(@parser.contains_xref_streams?)
554
552
  end
555
553
 
556
554
  it "fails if another object is found instead of a cross-reference stream" do
@@ -215,4 +215,18 @@ describe HexaPDF::Revision do
215
215
  assert_equal([], @rev.each_modified_object.to_a)
216
216
  end
217
217
  end
218
+
219
+ describe "reset_objects" do
220
+ it "deletes loaded objects" do
221
+ @rev.object(2)
222
+ @rev.reset_objects
223
+ assert(@rev.instance_variable_get(:@objects).oids.empty?)
224
+ end
225
+
226
+ it "deletes added objects" do
227
+ @rev.add(@obj)
228
+ @rev.reset_objects
229
+ assert(@rev.instance_variable_get(:@objects).oids.empty?)
230
+ end
231
+ end
218
232
  end
@@ -70,61 +70,169 @@ describe HexaPDF::Revisions do
70
70
  @revisions = @doc.revisions
71
71
  end
72
72
 
73
- describe "add" do
74
- it "adds an empty revision as the current revision" do
75
- rev = @revisions.add
76
- assert_equal({Size: 4}, rev.trailer.value)
77
- assert_equal(rev, @revisions.current)
73
+ describe "initialize" do
74
+ it "automatically loads all revisions from the underlying IO object" do
75
+ assert_kind_of(HexaPDF::Parser, @revisions.parser)
76
+ assert_equal(20, @revisions.all[0].object(2).value)
77
+ assert_equal(200, @revisions.all[1].object(2).value)
78
+ assert_equal(400, @revisions.all[2].object(2).value)
79
+ end
80
+
81
+ it "creates an empty revision when not using initial revisions" do
82
+ revisions = HexaPDF::Revisions.new(@doc)
83
+ assert_equal(1, revisions.all.count)
78
84
  end
79
85
  end
80
86
 
81
- describe "delete_revision" do
82
- it "allows deleting a revision by index" do
83
- rev = @revisions.revision(0)
84
- @revisions.delete(0)
85
- refute(@revisions.any? {|r| r == rev })
87
+ it "returns the next free oid" do
88
+ assert_equal(4, @revisions.next_oid)
89
+ end
90
+
91
+ describe "object" do
92
+ it "accepts a Reference object as argument" do
93
+ assert_equal(400, @revisions.object(HexaPDF::Reference.new(2, 0)).value)
94
+ end
95
+
96
+ it "accepts an object number as arguments" do
97
+ assert_equal(400, @revisions.object(2).value)
86
98
  end
87
99
 
88
- it "allows deleting a revision by specifying a revision" do
89
- rev = @revisions.revision(0)
90
- @revisions.delete(rev)
91
- refute(@revisions.any? {|r| r == rev })
100
+ it "returns nil for unknown object references" do
101
+ assert_nil(@revisions.object(100))
92
102
  end
93
103
 
94
- it "fails when trying to delete the only existing revision" do
95
- assert_raises(HexaPDF::Error) { @revisions.delete(0) while @revisions.current }
104
+ it "returns a null object for freed objects" do
105
+ @revisions.delete_object(2)
106
+ assert(@revisions.object(2).null?)
107
+ end
108
+ end
109
+
110
+ describe "object?" do
111
+ it "works with a Reference object as argument" do
112
+ assert(@revisions.object?(HexaPDF::Reference.new(2, 0)))
113
+ end
114
+
115
+ it "works with an object number as arguments" do
116
+ assert(@revisions.object?(2))
117
+ end
118
+
119
+ it "returns false when no object is found" do
120
+ refute(@revisions.object?(20))
121
+ end
122
+
123
+ it "returns true for freed objects" do
124
+ @revisions.delete_object(2)
125
+ assert(@revisions.object?(2))
126
+ end
127
+ end
128
+
129
+ describe "add_object" do
130
+ before do
131
+ @obj = HexaPDF::Object.new(5)
132
+ end
133
+
134
+ it "adds the object to the current revision" do
135
+ @revisions.add_object(@obj)
136
+ assert_same(@obj, @revisions.current.object(@obj))
137
+ end
138
+
139
+ it "returns the added object" do
140
+ obj = @revisions.add_object(@obj)
141
+ assert_same(@obj, obj)
142
+ end
143
+
144
+ it "returns the given object if it is already stored in the document" do
145
+ obj = @revisions.add_object(@obj)
146
+ assert_same(obj, @revisions.add_object(obj))
147
+ end
148
+
149
+ it "fails if the object number is already associated with another object" do
150
+ @revisions.add_object(@obj)
151
+ assert_raises(HexaPDF::Error) { @revisions.add_object(@doc.wrap(5, oid: @obj.oid)) }
152
+ end
153
+
154
+ it "automatically assign an object number for direct objects" do
155
+ assert_equal(4, @revisions.add_object(@obj).oid)
156
+ end
157
+ end
158
+
159
+ describe "delete_object" do
160
+ it "works with a Reference object as argument" do
161
+ @revisions.delete_object(@doc.object(2))
162
+ assert(@revisions.object(2).null?)
163
+ end
164
+
165
+ it "works with an object number as arguments" do
166
+ @revisions.delete_object(2)
167
+ assert(@revisions.object(2).null?)
168
+ end
169
+
170
+ it "deletes an object only in the most recent revision" do
171
+ @revisions.delete_object(2)
172
+ assert_equal(20, @revisions.all[0].object(2).value)
173
+ assert_equal(200, @revisions.all[1].object(2).value)
174
+ assert(@revisions.all[2].object(2).null?)
175
+ end
176
+ end
177
+
178
+ describe "each_object" do
179
+ before do
180
+ @obj3 = @revisions.object(3).value
181
+ end
182
+
183
+ it "iterates over the current objects" do
184
+ assert_equal([10, 400, @obj3], @revisions.each_object(only_current: true).sort.map(&:value))
185
+ end
186
+
187
+ it "iterates over all objects" do
188
+ assert_equal([@obj3, 400, 200, @obj3, 10, 20],
189
+ @revisions.each_object(only_current: false).map(&:value))
190
+ end
191
+
192
+ it "iterates over all loaded objects" do
193
+ assert_equal([@obj3], @revisions.each_object(only_loaded: true).map(&:value))
194
+ assert_equal(400, @revisions.object(2).value)
195
+ assert_equal([400, @obj3], @revisions.each_object(only_loaded: true).sort.map(&:value))
196
+ end
197
+
198
+ it "yields the revision as second argument if the block accepts exactly two arguments" do
199
+ data = [@obj3, @revisions.all[-1], 400, @revisions.all[-1], 10, @revisions.all[0]]
200
+ @revisions.each_object do |obj, rev|
201
+ assert_equal(data.shift, obj.value)
202
+ assert_equal(data.shift, rev)
203
+ end
204
+ assert(data.empty?)
205
+ end
206
+ end
207
+
208
+ describe "add" do
209
+ it "adds an empty revision as the current revision" do
210
+ rev = @revisions.add
211
+ assert_equal({Size: 4}, rev.trailer.value)
212
+ assert_equal(rev, @revisions.current)
96
213
  end
97
214
  end
98
215
 
99
216
  describe "merge" do
100
217
  it "does nothing when only one revision is specified" do
101
218
  @revisions.merge(1..1)
102
- assert_equal(3, @revisions.each.to_a.size)
219
+ assert_equal(3, @revisions.all.size)
103
220
  end
104
221
 
105
222
  it "merges the higher into the the lower revision" do
106
223
  @revisions.merge
107
- assert_equal(1, @revisions.each.to_a.size)
224
+ assert_equal(1, @revisions.all.size)
108
225
  assert_equal([10, 400, @doc.object(3).value], @revisions.current.each.to_a.sort.map(&:value))
109
226
  end
110
227
 
111
228
  it "handles objects correctly that are in multiple revisions" do
112
- @revisions.current.add(@revisions[0].object(1))
229
+ @revisions.current.add(@revisions.all[0].object(1))
113
230
  @revisions.merge
114
231
  assert_equal(1, @revisions.each.to_a.size)
115
232
  assert_equal([10, 400, @doc.object(3).value], @revisions.current.each.to_a.sort.map(&:value))
116
233
  end
117
234
  end
118
235
 
119
- describe "initialize" do
120
- it "automatically loads all revisions from the underlying IO object" do
121
- assert_kind_of(HexaPDF::Parser, @revisions.parser)
122
- assert_equal(20, @revisions.revision(0).object(2).value)
123
- assert_equal(300, @revisions[1].object(2).value)
124
- assert_equal(400, @revisions[2].object(2).value)
125
- end
126
- end
127
-
128
236
  it "handles invalid PDFs that have a loop via the xref /Prev or /XRefStm entries" do
129
237
  io = StringIO.new(<<~EOF)
130
238
  %PDF-1.7
@@ -191,6 +299,6 @@ describe HexaPDF::Revisions do
191
299
  EOF
192
300
  doc = HexaPDF::Document.new(io: io)
193
301
  assert_equal(2, doc.revisions.count)
194
- assert_same(doc.revisions[0].trailer.value, doc.revisions[1].trailer.value)
302
+ assert_same(doc.revisions.all[0].trailer.value, doc.revisions.all[1].trailer.value)
195
303
  end
196
304
  end
@@ -101,16 +101,12 @@ describe HexaPDF::Serializer do
101
101
  end
102
102
 
103
103
  it "serializes time like objects" do
104
- tz = ENV['TZ']
105
- ENV['TZ'] = 'Europe/Vienna'
106
104
  assert_serialized("(D:20150416094100)", Time.new(2015, 04, 16, 9, 41, 0, 0))
107
105
  assert_serialized("(D:20150416094100+01'00')", Time.new(2015, 04, 16, 9, 41, 0, 3600))
108
106
  assert_serialized("(D:20150416094100-01'20')", Time.new(2015, 04, 16, 9, 41, 0, -4800))
109
- assert_serialized("(D:20150416000000+02'00')", Date.parse("2015-04-16 9:41:00 +02:00"))
107
+ assert_serialized("(D:20150416000000)", Date.parse("2015-04-16 9:41:00 +02:00"))
110
108
  assert_serialized("(D:20150416094100+02'00')",
111
109
  Time.parse("2015-04-16 9:41:00 +02:00").to_datetime)
112
- ensure
113
- ENV['TZ'] = tz
114
110
  end
115
111
 
116
112
  it "serializes HexaPDF objects" do
@@ -40,13 +40,13 @@ describe HexaPDF::Writer do
40
40
  219
41
41
  %%EOF
42
42
  3 0 obj
43
- <</Producer(HexaPDF version 0.21.1)>>
43
+ <</Producer(HexaPDF version 0.24.0)>>
44
44
  endobj
45
45
  xref
46
46
  3 1
47
47
  0000000296 00000 n
48
48
  trailer
49
- <</Prev 219/Size 4/Root<</Type/Catalog>>/Info 3 0 R>>
49
+ <</Size 4/Root<</Type/Catalog>>/Info 3 0 R/Prev 219>>
50
50
  startxref
51
51
  349
52
52
  %%EOF
@@ -64,7 +64,7 @@ describe HexaPDF::Writer do
64
64
  20
65
65
  endobj
66
66
  3 0 obj
67
- <</Size 6/Type/XRef/W[1 1 2]/Index[0 4 5 1]/Filter/FlateDecode/DecodeParms<</Columns 4/Predictor 12>>/Length 31>>stream
67
+ <</Type/XRef/Size 6/W[1 1 2]/Index[0 4 5 1]/Filter/FlateDecode/DecodeParms<</Columns 4/Predictor 12>>/Length 31>>stream
68
68
  x\xDAcb`\xF8\xFF\x9F\x89\x89\x95\x91\x91\xE9\x7F\x19\x03\x03\x13\x83\x10\x88he`\x00\x00B4\x04\x1E
69
69
  endstream
70
70
  endobj
@@ -72,7 +72,7 @@ describe HexaPDF::Writer do
72
72
  141
73
73
  %%EOF
74
74
  6 0 obj
75
- <</Producer(HexaPDF version 0.21.1)>>
75
+ <</Producer(HexaPDF version 0.24.0)>>
76
76
  endobj
77
77
  2 0 obj
78
78
  <</Length 10>>stream
@@ -80,7 +80,7 @@ describe HexaPDF::Writer do
80
80
  endstream
81
81
  endobj
82
82
  4 0 obj
83
- <</Size 7/Prev 141/Root<</Type/Catalog>>/Info 6 0 R/Type/XRef/W[1 2 2]/Index[2 1 4 1 6 1]/Filter/FlateDecode/DecodeParms<</Columns 5/Predictor 12>>/Length 22>>stream
83
+ <</Type/XRef/Size 7/Root<</Type/Catalog>>/Info 6 0 R/Prev 141/W[1 2 2]/Index[2 1 4 1 6 1]/Filter/FlateDecode/DecodeParms<</Columns 5/Predictor 12>>/Length 22>>stream
84
84
  x\xDAcbdlg``b`\xB0\x04\x93\x93\x18\x18\x00\f\e\x01[
85
85
  endstream
86
86
  endobj
@@ -94,8 +94,9 @@ describe HexaPDF::Writer do
94
94
  document = HexaPDF::Document.new(io: input_io)
95
95
  document.trailer.info[:Producer] = "unknown"
96
96
  output_io = StringIO.new(''.force_encoding(Encoding::BINARY))
97
- xref_section = HexaPDF::Writer.write(document, output_io)
97
+ start_xref_offset, xref_section = HexaPDF::Writer.write(document, output_io)
98
98
  assert_kind_of(HexaPDF::XRefSection, xref_section)
99
+ assert_kind_of(Integer, start_xref_offset)
99
100
  assert_equal(input_io.string, output_io.string)
100
101
  end
101
102
 
@@ -112,7 +113,7 @@ describe HexaPDF::Writer do
112
113
  HexaPDF::Writer.write(doc, output_io, incremental: true)
113
114
  assert_equal(output_io.string[0, @std_input_io.string.length], @std_input_io.string)
114
115
  doc = HexaPDF::Document.new(io: output_io)
115
- assert_equal(4, doc.revisions.size)
116
+ assert_equal(4, doc.revisions.count)
116
117
  assert_equal(2, doc.revisions.current.each.to_a.size)
117
118
  end
118
119
 
@@ -136,29 +137,112 @@ describe HexaPDF::Writer do
136
137
  end
137
138
  end
138
139
 
140
+ it "moves modified objects into the last revision" do
141
+ io = StringIO.new
142
+ io2 = StringIO.new
143
+
144
+ document = HexaPDF::Document.new
145
+ document.pages.add
146
+ HexaPDF::Writer.new(document, io).write
147
+
148
+ document = HexaPDF::Document.new(io: io)
149
+ document.pages.add
150
+ HexaPDF::Writer.new(document, io2).write_incremental
151
+
152
+ document = HexaPDF::Document.new(io: io2)
153
+ document.revisions.add
154
+ document.pages.add
155
+ HexaPDF::Writer.new(document, io).write
156
+
157
+ document = HexaPDF::Document.new(io: io)
158
+ assert_equal(3, document.revisions.count)
159
+ assert_equal(1, document.revisions.all[0].object(2)[:Kids].length)
160
+ assert_equal(2, document.revisions.all[1].object(2)[:Kids].length)
161
+ assert_equal(3, document.revisions.all[2].object(2)[:Kids].length)
162
+ end
163
+
139
164
  it "creates an xref stream if no xref stream is in a revision but object streams are" do
140
165
  document = HexaPDF::Document.new
141
166
  document.add({Type: :ObjStm})
142
167
  HexaPDF::Writer.new(document, StringIO.new).write
143
- assert(:XRef, document.object(2).type)
168
+ assert_equal(:XRef, document.object(4).type)
144
169
  end
145
170
 
146
171
  it "creates an xref stream if a previous revision had one" do
147
172
  document = HexaPDF::Document.new
148
173
  document.pages.add
149
- document.revisions.add
174
+ io = StringIO.new
175
+ HexaPDF::Writer.new(document, io).write
176
+
177
+ document = HexaPDF::Document.new(io: io)
150
178
  document.pages.add
151
179
  document.add({Type: :ObjStm})
152
- document.revisions.add
180
+ io2 = StringIO.new
181
+ HexaPDF::Writer.new(document, io2).write_incremental
182
+
183
+ document = HexaPDF::Document.new(io: io2)
153
184
  document.pages.add
154
- io = StringIO.new
155
- HexaPDF::Writer.new(document, io).write
185
+ HexaPDF::Writer.new(document, io).write_incremental
156
186
 
157
187
  document = HexaPDF::Document.new(io: io)
158
188
  assert_equal(3, document.revisions.count)
159
- assert(document.revisions[0].none? {|obj| obj.type == :XRef })
160
- assert(document.revisions[1].one? {|obj| obj.type == :XRef })
161
- assert(document.revisions[2].one? {|obj| obj.type == :XRef })
189
+ assert(document.revisions.all[0].none? {|obj| obj.type == :XRef })
190
+ assert(document.revisions.all[1].one? {|obj| obj.type == :XRef })
191
+ assert(document.revisions.all[2].one? {|obj| obj.type == :XRef })
192
+ end
193
+
194
+ it "doesn't create an xref stream if one was just used for an XRefStm entry" do
195
+ # The following document's structure is built like a typical MS Word created PDF
196
+ input = StringIO.new(<<~EOF.force_encoding(Encoding::BINARY))
197
+ %PDF-1.2
198
+ %\xCF\xEC\xFF\xE8\xD7\xCB\xCD
199
+ 1 0 obj
200
+ <</Type/Catalog/Pages 2 0 R>>
201
+ endobj
202
+ 2 0 obj
203
+ <</Type/Pages/Kids[3 0 R]/Count 1>>
204
+ endobj
205
+ 3 0 obj
206
+ <</Type/Page/MediaBox[0 0 595 842]/Parent 2 0 R/Resources<<>>>>
207
+ endobj
208
+ 5 0 obj
209
+ <</Producer(HexaPDF version 0.24.0)>>
210
+ endobj
211
+ 4 0 obj
212
+ <</Root 1 0 R/Info 5 0 R/Size 6/Type/XRef/W[1 1 2]/Index[0 6]/Filter/FlateDecode/DecodeParms<</Columns 4/Predictor 12>>/Length 33>>stream
213
+ x\xDAcb`\xF8\xFF\x9F\x89Q\x88\x91\x91\x89A\x97\x81\x81\x89\xC1\x18D\xB4\x80\x88\xD3\f\f\x00C\xDE\x03\xCF
214
+ endstream
215
+ endobj
216
+ xref
217
+ 0 6
218
+ 0000000000 65535 f
219
+ 0000000018 00000 n
220
+ 0000000063 00000 n
221
+ 0000000114 00000 n
222
+ 0000000246 00000 n
223
+ 0000000193 00000 n
224
+ trailer
225
+ <</Root 1 0 R/Info 5 0 R/Size 6>>
226
+ startxref
227
+ 443
228
+ %%EOF
229
+
230
+ xref
231
+ 0 0
232
+ trailer
233
+ <</Root 1 0 R/Info 5 0 R/Prev 443/XRefStm 246>>
234
+ startxref
235
+ 629
236
+ %%EOF
237
+ EOF
238
+
239
+ document = HexaPDF::Document.new(io: input)
240
+ document.pages.add
241
+ io = StringIO.new
242
+ HexaPDF::Writer.new(document, io).write_incremental
243
+
244
+ document = HexaPDF::Document.new(io: io)
245
+ refute(document.trailer.key?(:Type))
162
246
  end
163
247
 
164
248
  it "raises an error if the class is misused and an xref section contains invalid entries" do
@@ -86,7 +86,8 @@ describe HexaPDF::Type::AcroForm::Form do
86
86
  before do
87
87
  @acro_form[:Fields] = [
88
88
  {T: "root only", Kids: [{Subtype: :Widget}]},
89
- {T: "children", Kids: [{T: "child", FT: :Btn}, {T: "sub", Kids: [{T: "child"}]}]},
89
+ {T: "children", Kids: [{T: "\xFE\xFF".b << "child".encode('UTF-16BE').b, FT: :Btn},
90
+ {T: "sub", Kids: [{T: "child"}]}]},
90
91
  ]
91
92
  end
92
93