hexapdf 0.11.9 → 0.13.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 (270) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +157 -0
  3. data/LICENSE +1 -1
  4. data/examples/001-hello_world.rb +1 -1
  5. data/examples/002-graphics.rb +1 -1
  6. data/examples/003-arcs.rb +1 -1
  7. data/examples/004-optimizing.rb +1 -1
  8. data/examples/005-merging.rb +1 -1
  9. data/examples/006-standard_pdf_fonts.rb +1 -1
  10. data/examples/007-truetype.rb +1 -1
  11. data/examples/008-show_char_bboxes.rb +1 -1
  12. data/examples/009-text_layouter_alignment.rb +1 -1
  13. data/examples/010-text_layouter_inline_boxes.rb +1 -1
  14. data/examples/011-text_layouter_line_wrapping.rb +1 -1
  15. data/examples/012-text_layouter_styling.rb +1 -1
  16. data/examples/013-text_layouter_shapes.rb +1 -1
  17. data/examples/014-text_in_polygon.rb +1 -1
  18. data/examples/015-boxes.rb +1 -1
  19. data/examples/016-frame_automatic_box_placement.rb +1 -1
  20. data/examples/017-frame_text_flow.rb +1 -1
  21. data/examples/018-composer.rb +1 -1
  22. data/examples/019-acro_form.rb +51 -0
  23. data/lib/hexapdf.rb +1 -1
  24. data/lib/hexapdf/cli.rb +3 -1
  25. data/lib/hexapdf/cli/batch.rb +1 -1
  26. data/lib/hexapdf/cli/command.rb +22 -11
  27. data/lib/hexapdf/cli/files.rb +1 -1
  28. data/lib/hexapdf/cli/form.rb +240 -0
  29. data/lib/hexapdf/cli/image2pdf.rb +3 -2
  30. data/lib/hexapdf/cli/images.rb +1 -1
  31. data/lib/hexapdf/cli/info.rb +52 -3
  32. data/lib/hexapdf/cli/inspect.rb +31 -9
  33. data/lib/hexapdf/cli/merge.rb +2 -2
  34. data/lib/hexapdf/cli/modify.rb +1 -1
  35. data/lib/hexapdf/cli/optimize.rb +1 -1
  36. data/lib/hexapdf/cli/split.rb +1 -1
  37. data/lib/hexapdf/cli/watermark.rb +1 -1
  38. data/lib/hexapdf/composer.rb +2 -2
  39. data/lib/hexapdf/configuration.rb +81 -11
  40. data/lib/hexapdf/content.rb +3 -1
  41. data/lib/hexapdf/content/canvas.rb +5 -18
  42. data/lib/hexapdf/content/color_space.rb +111 -32
  43. data/lib/hexapdf/content/graphic_object.rb +1 -1
  44. data/lib/hexapdf/content/graphic_object/arc.rb +4 -4
  45. data/lib/hexapdf/content/graphic_object/endpoint_arc.rb +1 -1
  46. data/lib/hexapdf/content/graphic_object/geom2d.rb +1 -1
  47. data/lib/hexapdf/content/graphic_object/solid_arc.rb +1 -1
  48. data/lib/hexapdf/content/graphics_state.rb +1 -1
  49. data/lib/hexapdf/content/operator.rb +9 -9
  50. data/lib/hexapdf/content/parser.rb +18 -5
  51. data/lib/hexapdf/content/processor.rb +1 -1
  52. data/lib/hexapdf/content/transformation_matrix.rb +1 -1
  53. data/lib/hexapdf/data_dir.rb +1 -1
  54. data/lib/hexapdf/dictionary.rb +5 -5
  55. data/lib/hexapdf/dictionary_fields.rb +2 -10
  56. data/lib/hexapdf/document.rb +45 -17
  57. data/lib/hexapdf/document/files.rb +1 -2
  58. data/lib/hexapdf/document/fonts.rb +1 -1
  59. data/lib/hexapdf/document/images.rb +1 -1
  60. data/lib/hexapdf/document/pages.rb +3 -14
  61. data/lib/hexapdf/encryption.rb +1 -1
  62. data/lib/hexapdf/encryption/aes.rb +1 -1
  63. data/lib/hexapdf/encryption/arc4.rb +1 -1
  64. data/lib/hexapdf/encryption/fast_aes.rb +1 -1
  65. data/lib/hexapdf/encryption/fast_arc4.rb +2 -2
  66. data/lib/hexapdf/encryption/identity.rb +1 -1
  67. data/lib/hexapdf/encryption/ruby_aes.rb +1 -1
  68. data/lib/hexapdf/encryption/ruby_arc4.rb +1 -1
  69. data/lib/hexapdf/encryption/security_handler.rb +2 -1
  70. data/lib/hexapdf/encryption/standard_security_handler.rb +2 -1
  71. data/lib/hexapdf/error.rb +1 -1
  72. data/lib/hexapdf/filter.rb +3 -3
  73. data/lib/hexapdf/filter/ascii85_decode.rb +1 -1
  74. data/lib/hexapdf/filter/ascii_hex_decode.rb +1 -1
  75. data/lib/hexapdf/filter/encryption.rb +1 -1
  76. data/lib/hexapdf/filter/flate_decode.rb +1 -1
  77. data/lib/hexapdf/filter/lzw_decode.rb +1 -1
  78. data/lib/hexapdf/filter/{jpx_decode.rb → pass_through.rb} +5 -5
  79. data/lib/hexapdf/filter/predictor.rb +1 -1
  80. data/lib/hexapdf/filter/run_length_decode.rb +1 -1
  81. data/lib/hexapdf/font/cmap.rb +2 -5
  82. data/lib/hexapdf/font/cmap/parser.rb +1 -1
  83. data/lib/hexapdf/font/cmap/writer.rb +1 -1
  84. data/lib/hexapdf/font/encoding.rb +1 -1
  85. data/lib/hexapdf/font/encoding/base.rb +9 -1
  86. data/lib/hexapdf/font/encoding/difference_encoding.rb +7 -1
  87. data/lib/hexapdf/font/encoding/glyph_list.rb +1 -1
  88. data/lib/hexapdf/font/encoding/mac_expert_encoding.rb +1 -1
  89. data/lib/hexapdf/font/encoding/mac_roman_encoding.rb +1 -1
  90. data/lib/hexapdf/font/encoding/standard_encoding.rb +1 -1
  91. data/lib/hexapdf/font/encoding/symbol_encoding.rb +1 -1
  92. data/lib/hexapdf/font/encoding/win_ansi_encoding.rb +1 -1
  93. data/lib/hexapdf/font/encoding/zapf_dingbats_encoding.rb +1 -1
  94. data/lib/hexapdf/font/invalid_glyph.rb +1 -1
  95. data/lib/hexapdf/font/true_type.rb +1 -1
  96. data/lib/hexapdf/font/true_type/builder.rb +1 -1
  97. data/lib/hexapdf/font/true_type/font.rb +1 -1
  98. data/lib/hexapdf/font/true_type/optimizer.rb +1 -1
  99. data/lib/hexapdf/font/true_type/subsetter.rb +1 -1
  100. data/lib/hexapdf/font/true_type/table.rb +1 -1
  101. data/lib/hexapdf/font/true_type/table/cmap.rb +1 -1
  102. data/lib/hexapdf/font/true_type/table/cmap_subtable.rb +1 -1
  103. data/lib/hexapdf/font/true_type/table/directory.rb +1 -1
  104. data/lib/hexapdf/font/true_type/table/glyf.rb +1 -1
  105. data/lib/hexapdf/font/true_type/table/head.rb +2 -1
  106. data/lib/hexapdf/font/true_type/table/hhea.rb +1 -1
  107. data/lib/hexapdf/font/true_type/table/hmtx.rb +1 -1
  108. data/lib/hexapdf/font/true_type/table/kern.rb +1 -1
  109. data/lib/hexapdf/font/true_type/table/loca.rb +1 -1
  110. data/lib/hexapdf/font/true_type/table/maxp.rb +1 -1
  111. data/lib/hexapdf/font/true_type/table/name.rb +1 -1
  112. data/lib/hexapdf/font/true_type/table/os2.rb +3 -1
  113. data/lib/hexapdf/font/true_type/table/post.rb +1 -1
  114. data/lib/hexapdf/font/true_type_wrapper.rb +54 -51
  115. data/lib/hexapdf/font/type1.rb +1 -1
  116. data/lib/hexapdf/font/type1/afm_parser.rb +1 -1
  117. data/lib/hexapdf/font/type1/character_metrics.rb +1 -1
  118. data/lib/hexapdf/font/type1/font.rb +1 -1
  119. data/lib/hexapdf/font/type1/font_metrics.rb +1 -1
  120. data/lib/hexapdf/font/type1/pfb_parser.rb +1 -1
  121. data/lib/hexapdf/font/type1_wrapper.rb +68 -52
  122. data/lib/hexapdf/font_loader.rb +1 -1
  123. data/lib/hexapdf/font_loader/from_configuration.rb +1 -1
  124. data/lib/hexapdf/font_loader/from_file.rb +1 -1
  125. data/lib/hexapdf/font_loader/standard14.rb +1 -1
  126. data/lib/hexapdf/image_loader.rb +1 -1
  127. data/lib/hexapdf/image_loader/jpeg.rb +1 -1
  128. data/lib/hexapdf/image_loader/pdf.rb +1 -1
  129. data/lib/hexapdf/image_loader/png.rb +4 -3
  130. data/lib/hexapdf/importer.rb +2 -4
  131. data/lib/hexapdf/layout.rb +1 -1
  132. data/lib/hexapdf/layout/box.rb +1 -1
  133. data/lib/hexapdf/layout/frame.rb +1 -1
  134. data/lib/hexapdf/layout/image_box.rb +1 -1
  135. data/lib/hexapdf/layout/inline_box.rb +1 -1
  136. data/lib/hexapdf/layout/line.rb +2 -2
  137. data/lib/hexapdf/layout/numeric_refinements.rb +1 -1
  138. data/lib/hexapdf/layout/style.rb +24 -24
  139. data/lib/hexapdf/layout/text_box.rb +1 -1
  140. data/lib/hexapdf/layout/text_fragment.rb +1 -1
  141. data/lib/hexapdf/layout/text_layouter.rb +1 -1
  142. data/lib/hexapdf/layout/text_shaper.rb +4 -3
  143. data/lib/hexapdf/layout/width_from_polygon.rb +1 -1
  144. data/lib/hexapdf/name_tree_node.rb +1 -1
  145. data/lib/hexapdf/number_tree_node.rb +1 -1
  146. data/lib/hexapdf/object.rb +32 -27
  147. data/lib/hexapdf/parser.rb +69 -6
  148. data/lib/hexapdf/pdf_array.rb +10 -3
  149. data/lib/hexapdf/rectangle.rb +31 -1
  150. data/lib/hexapdf/reference.rb +1 -1
  151. data/lib/hexapdf/revision.rb +2 -1
  152. data/lib/hexapdf/revisions.rb +30 -22
  153. data/lib/hexapdf/serializer.rb +2 -2
  154. data/lib/hexapdf/stream.rb +1 -1
  155. data/lib/hexapdf/task.rb +1 -1
  156. data/lib/hexapdf/task/dereference.rb +1 -1
  157. data/lib/hexapdf/task/optimize.rb +7 -5
  158. data/lib/hexapdf/tokenizer.rb +5 -4
  159. data/lib/hexapdf/type.rb +1 -1
  160. data/lib/hexapdf/type/acro_form.rb +7 -1
  161. data/lib/hexapdf/type/acro_form/appearance_generator.rb +405 -0
  162. data/lib/hexapdf/type/acro_form/button_field.rb +305 -0
  163. data/lib/hexapdf/type/acro_form/choice_field.rb +220 -0
  164. data/lib/hexapdf/type/acro_form/field.rb +250 -17
  165. data/lib/hexapdf/type/acro_form/form.rb +159 -7
  166. data/lib/hexapdf/type/acro_form/text_field.rb +187 -0
  167. data/lib/hexapdf/type/acro_form/variable_text_field.rb +122 -0
  168. data/lib/hexapdf/type/action.rb +1 -1
  169. data/lib/hexapdf/type/actions.rb +1 -1
  170. data/lib/hexapdf/type/actions/go_to.rb +1 -1
  171. data/lib/hexapdf/type/actions/go_to_r.rb +1 -1
  172. data/lib/hexapdf/type/actions/launch.rb +1 -1
  173. data/lib/hexapdf/type/actions/uri.rb +4 -3
  174. data/lib/hexapdf/type/annotation.rb +73 -3
  175. data/lib/hexapdf/type/annotations.rb +1 -1
  176. data/lib/hexapdf/type/annotations/link.rb +2 -2
  177. data/lib/hexapdf/type/annotations/markup_annotation.rb +1 -1
  178. data/lib/hexapdf/type/annotations/text.rb +1 -1
  179. data/lib/hexapdf/type/annotations/widget.rb +238 -2
  180. data/lib/hexapdf/type/catalog.rb +23 -3
  181. data/lib/hexapdf/type/cid_font.rb +1 -1
  182. data/lib/hexapdf/type/embedded_file.rb +1 -1
  183. data/lib/hexapdf/type/file_specification.rb +2 -2
  184. data/lib/hexapdf/type/font.rb +18 -1
  185. data/lib/hexapdf/type/font_descriptor.rb +2 -2
  186. data/lib/hexapdf/type/font_simple.rb +4 -2
  187. data/lib/hexapdf/type/font_true_type.rb +7 -3
  188. data/lib/hexapdf/type/font_type0.rb +2 -2
  189. data/lib/hexapdf/type/font_type1.rb +16 -1
  190. data/lib/hexapdf/type/font_type3.rb +1 -1
  191. data/lib/hexapdf/type/form.rb +12 -2
  192. data/lib/hexapdf/type/graphics_state_parameter.rb +1 -1
  193. data/lib/hexapdf/type/icon_fit.rb +1 -1
  194. data/lib/hexapdf/type/image.rb +5 -3
  195. data/lib/hexapdf/type/info.rb +1 -1
  196. data/lib/hexapdf/type/names.rb +1 -1
  197. data/lib/hexapdf/type/object_stream.rb +1 -1
  198. data/lib/hexapdf/type/page.rb +36 -12
  199. data/lib/hexapdf/type/page_tree_node.rb +37 -16
  200. data/lib/hexapdf/type/resources.rb +17 -3
  201. data/lib/hexapdf/type/trailer.rb +4 -6
  202. data/lib/hexapdf/type/viewer_preferences.rb +1 -1
  203. data/lib/hexapdf/type/xref_stream.rb +1 -1
  204. data/lib/hexapdf/utils/bit_field.rb +38 -24
  205. data/lib/hexapdf/utils/bit_stream.rb +1 -1
  206. data/lib/hexapdf/utils/graphics_helpers.rb +1 -1
  207. data/lib/hexapdf/utils/lru_cache.rb +1 -1
  208. data/lib/hexapdf/utils/math_helpers.rb +1 -1
  209. data/lib/hexapdf/utils/object_hash.rb +1 -1
  210. data/lib/hexapdf/utils/pdf_doc_encoding.rb +1 -1
  211. data/lib/hexapdf/utils/sorted_tree_node.rb +19 -16
  212. data/lib/hexapdf/version.rb +2 -2
  213. data/lib/hexapdf/writer.rb +1 -1
  214. data/lib/hexapdf/xref_section.rb +1 -1
  215. data/test/hexapdf/common_tokenizer_tests.rb +6 -1
  216. data/test/hexapdf/content/common.rb +2 -2
  217. data/test/hexapdf/content/graphic_object/test_arc.rb +4 -4
  218. data/test/hexapdf/content/test_canvas.rb +3 -3
  219. data/test/hexapdf/content/test_color_space.rb +71 -8
  220. data/test/hexapdf/content/test_operator.rb +22 -22
  221. data/test/hexapdf/content/test_parser.rb +14 -0
  222. data/test/hexapdf/document/test_fonts.rb +1 -1
  223. data/test/hexapdf/document/test_pages.rb +6 -6
  224. data/test/hexapdf/encryption/test_aes.rb +4 -4
  225. data/test/hexapdf/encryption/test_standard_security_handler.rb +11 -11
  226. data/test/hexapdf/filter/test_ascii85_decode.rb +1 -1
  227. data/test/hexapdf/filter/test_ascii_hex_decode.rb +1 -1
  228. data/test/hexapdf/font/encoding/test_base.rb +10 -0
  229. data/test/hexapdf/font/encoding/test_difference_encoding.rb +8 -0
  230. data/test/hexapdf/font/test_true_type_wrapper.rb +10 -7
  231. data/test/hexapdf/font/test_type1_wrapper.rb +33 -8
  232. data/test/hexapdf/layout/test_style.rb +1 -1
  233. data/test/hexapdf/layout/test_text_layouter.rb +3 -4
  234. data/test/hexapdf/test_configuration.rb +2 -2
  235. data/test/hexapdf/test_dictionary.rb +3 -1
  236. data/test/hexapdf/test_dictionary_fields.rb +2 -2
  237. data/test/hexapdf/test_document.rb +16 -4
  238. data/test/hexapdf/test_object.rb +44 -26
  239. data/test/hexapdf/test_parser.rb +125 -55
  240. data/test/hexapdf/test_pdf_array.rb +7 -0
  241. data/test/hexapdf/test_rectangle.rb +14 -0
  242. data/test/hexapdf/test_revision.rb +3 -0
  243. data/test/hexapdf/test_revisions.rb +35 -0
  244. data/test/hexapdf/test_writer.rb +2 -2
  245. data/test/hexapdf/type/acro_form/test_appearance_generator.rb +521 -0
  246. data/test/hexapdf/type/acro_form/test_button_field.rb +281 -0
  247. data/test/hexapdf/type/acro_form/test_choice_field.rb +137 -0
  248. data/test/hexapdf/type/acro_form/test_field.rb +163 -6
  249. data/test/hexapdf/type/acro_form/test_form.rb +189 -22
  250. data/test/hexapdf/type/acro_form/test_text_field.rb +121 -0
  251. data/test/hexapdf/type/acro_form/test_variable_text_field.rb +77 -0
  252. data/test/hexapdf/type/annotations/test_text.rb +1 -1
  253. data/test/hexapdf/type/annotations/test_widget.rb +199 -0
  254. data/test/hexapdf/type/test_annotation.rb +45 -0
  255. data/test/hexapdf/type/test_catalog.rb +18 -0
  256. data/test/hexapdf/type/test_font.rb +5 -0
  257. data/test/hexapdf/type/test_font_simple.rb +2 -1
  258. data/test/hexapdf/type/test_font_true_type.rb +6 -0
  259. data/test/hexapdf/type/test_font_type1.rb +8 -0
  260. data/test/hexapdf/type/test_form.rb +19 -1
  261. data/test/hexapdf/type/test_image.rb +7 -0
  262. data/test/hexapdf/type/test_page.rb +45 -7
  263. data/test/hexapdf/type/test_page_tree_node.rb +62 -12
  264. data/test/hexapdf/type/test_resources.rb +20 -0
  265. data/test/hexapdf/type/test_trailer.rb +4 -0
  266. data/test/hexapdf/utils/test_bit_field.rb +15 -1
  267. data/test/hexapdf/utils/test_sorted_tree_node.rb +10 -9
  268. data/test/test_helper.rb +1 -1
  269. metadata +34 -21
  270. data/lib/hexapdf/filter/dct_decode.rb +0 -60
@@ -222,7 +222,7 @@ describe HexaPDF::DictionaryFields do
222
222
 
223
223
  it "allows conversion to a Rectangle from an Array" do
224
224
  doc = Minitest::Mock.new
225
- doc.expect(:wrap, :data, [[0, 1, 2, 3], type: HexaPDF::Rectangle])
225
+ doc.expect(:wrap, :data, [[0, 1, 2, 3], {type: HexaPDF::Rectangle}])
226
226
  @field.convert([0, 1, 2, 3], doc)
227
227
  doc.verify
228
228
  end
@@ -230,7 +230,7 @@ describe HexaPDF::DictionaryFields do
230
230
  it "allows conversion to a Rectangle from a HexaPDF::PDFArray" do
231
231
  data = HexaPDF::PDFArray.new([0, 1, 2, 3])
232
232
  doc = Minitest::Mock.new
233
- doc.expect(:wrap, :data, [data, type: HexaPDF::Rectangle])
233
+ doc.expect(:wrap, :data, [data, {type: HexaPDF::Rectangle}])
234
234
  @field.convert(data, doc)
235
235
  doc.verify
236
236
  end
@@ -441,21 +441,21 @@ describe HexaPDF::Document do
441
441
 
442
442
  describe "validate" do
443
443
  before do
444
- @doc.trailer.validate # to create a valid document
444
+ @doc.validate # to create a valid document
445
445
  end
446
446
 
447
447
  it "validates indirect objects" do
448
- obj = @doc.add({Type: :Catalog})
448
+ obj = @doc.add({Type: :Page, MediaBox: [1, 1, 1, 1], Parent: @doc.pages.root})
449
449
  refute(@doc.validate(auto_correct: false))
450
450
 
451
451
  called = false
452
- assert(@doc.validate {|o| assert_same(obj, o); called = true })
452
+ assert(@doc.validate {|_, _, o| assert_same(obj, o); called = true })
453
453
  assert(called)
454
454
  end
455
455
 
456
456
  it "validates the trailer object" do
457
457
  @doc.trailer[:ID] = :Symbol
458
- refute(@doc.validate {|obj| assert_same(@doc.trailer, obj) })
458
+ refute(@doc.validate {|_, _, obj| assert_same(@doc.trailer, obj) })
459
459
  end
460
460
 
461
461
  it "validates only loaded objects" do
@@ -584,6 +584,18 @@ describe HexaPDF::Document do
584
584
  end
585
585
  end
586
586
 
587
+ describe "acro_form" do
588
+ it "returns the main AcroForm object" do
589
+ assert_nil(@doc.acro_form)
590
+ @doc.catalog[:AcroForm] = 5
591
+ assert_equal(5, @doc.acro_form)
592
+ end
593
+
594
+ it "create the AcroForm object if instructed" do
595
+ assert_equal(:XXAcroForm, @doc.acro_form(create: true).type)
596
+ end
597
+ end
598
+
587
599
  describe "listener interface" do
588
600
  it "allows registering and dispatching messages" do
589
601
  args = []
@@ -6,15 +6,6 @@ require 'hexapdf/reference'
6
6
 
7
7
  describe HexaPDF::Object do
8
8
  describe "class.deep_copy" do
9
- it "handles not-duplicatable classes" do
10
- assert_equal(5, HexaPDF::Object.deep_copy(5))
11
- assert_equal(5.5, HexaPDF::Object.deep_copy(5.5))
12
- assert_nil(HexaPDF::Object.deep_copy(nil))
13
- assert_equal(true, HexaPDF::Object.deep_copy(true))
14
- assert_equal(false, HexaPDF::Object.deep_copy(false))
15
- assert_equal(:Name, HexaPDF::Object.deep_copy(:Name))
16
- end
17
-
18
9
  it "handles general, duplicatable classes" do
19
10
  x = "test"
20
11
  assert_equal("test", HexaPDF::Object.deep_copy(x))
@@ -103,30 +94,57 @@ describe HexaPDF::Object do
103
94
  end
104
95
 
105
96
  describe "validate" do
106
- it "invokes perform_validation correctly via #validate" do
107
- obj = HexaPDF::Object.new(5)
108
- invoked = {}
109
- obj.define_singleton_method(:perform_validation) do |&block|
110
- invoked[:method] = true
97
+ before do
98
+ @obj = HexaPDF::Object.new(5)
99
+ end
100
+
101
+ it "invokes perform_validation correctly" do
102
+ invoked = false
103
+ @obj.define_singleton_method(:perform_validation) { invoked = true }
104
+ assert(@obj.validate)
105
+ assert(invoked)
106
+ end
107
+
108
+ it "yields all arguments yieled by perform_validation" do
109
+ invoked = []
110
+ @obj.define_singleton_method(:perform_validation) do |&block|
111
+ block.call("error", true, :object)
112
+ end
113
+ assert(@obj.validate {|*a| invoked << a })
114
+ assert_equal([["error", true, :object]], invoked)
115
+ end
116
+
117
+ it "provides self as third argument if none is yielded by perform_validation" do
118
+ invoked = []
119
+ @obj.define_singleton_method(:perform_validation) do |&block|
111
120
  block.call("error", true)
112
121
  end
113
- assert(obj.validate {|*a| invoked[:block] = a })
114
- assert_equal([:method, :block], invoked.keys)
115
- assert_equal(["error", true], invoked[:block])
122
+ assert(@obj.validate {|*a| invoked << a })
123
+ assert_equal([["error", true, @obj]], invoked)
124
+ end
116
125
 
117
- refute(obj.validate(auto_correct: false))
126
+ it "yields all problems when auto_correct is true" do
127
+ invoked = []
128
+ @obj.define_singleton_method(:perform_validation) do |&block|
129
+ invoked << :before
130
+ block.call("error", false)
131
+ invoked << :after
132
+ block.call("error2", true)
133
+ invoked << :last
134
+ end
135
+ refute(@obj.validate)
136
+ assert_equal([:before, :after, :last], invoked)
118
137
  end
119
138
 
120
- it "stops validating on an uncorrectable problem" do
121
- obj = HexaPDF::Object.new(5)
122
- invoked = {}
123
- obj.define_singleton_method(:perform_validation) do |&block|
124
- invoked[:before] = true
139
+ it "stops at the first uncorrectable problem if auto_correct is false" do
140
+ invoked = []
141
+ @obj.define_singleton_method(:perform_validation) do |&block|
142
+ invoked << :before
125
143
  block.call("error", false)
126
- invoked[:after] = true
144
+ invoked << :after
127
145
  end
128
- refute(obj.validate {|*a| invoked[:block] = a })
129
- refute(invoked.key?(:after))
146
+ refute(@obj.validate(auto_correct: false))
147
+ assert_equal([:before], invoked)
130
148
  end
131
149
  end
132
150
 
@@ -8,6 +8,7 @@ require 'stringio'
8
8
  describe HexaPDF::Parser do
9
9
  before do
10
10
  @document = HexaPDF::Document.new
11
+ @document.config['parser.try_xref_reconstruction'] = false
11
12
  @document.add(@document.wrap(10, oid: 1, gen: 0))
12
13
 
13
14
  create_parser(<<~EOF)
@@ -132,6 +133,48 @@ describe HexaPDF::Parser do
132
133
  exp = assert_raises(HexaPDF::MalformedPDFError) { @parser.parse_indirect_object(0) }
133
134
  assert_match(/stream.*followed by.*endstream/i, exp.message)
134
135
  end
136
+
137
+ describe "with strict parsing" do
138
+ before do
139
+ @document.config['parser.on_correctable_error'] = proc { true }
140
+ end
141
+
142
+ it "fails if an empty indirect object is found" do
143
+ create_parser("1 0 obj\nendobj")
144
+ exp = assert_raises(HexaPDF::MalformedPDFError) { @parser.parse_indirect_object }
145
+ assert_match(/no indirect object value/i, exp.message)
146
+ end
147
+
148
+ it "fails if keyword stream is followed only by CR without LF" do
149
+ create_parser("1 0 obj<</Length 2>> stream\r12\nendstream endobj")
150
+ exp = assert_raises(HexaPDF::MalformedPDFError) { @parser.parse_indirect_object }
151
+ assert_match(/not CR alone/, exp.message)
152
+ end
153
+
154
+ it "fails if the stream length value is invalid" do
155
+ create_parser("1 0 obj<</Length 4>> stream\n12endstream endobj")
156
+ exp = assert_raises(HexaPDF::MalformedPDFError) { @parser.parse_indirect_object }
157
+ assert_match(/invalid stream length/i, exp.message)
158
+ end
159
+
160
+ it "fails if the keyword endobj is mangled" do
161
+ create_parser("1 0 obj\n<< >>\nendobjd\n")
162
+ exp = assert_raises(HexaPDF::MalformedPDFError) { @parser.parse_indirect_object }
163
+ assert_match(/keyword endobj/, exp.message)
164
+ end
165
+
166
+ it "fails if the keyword endobj is missing" do
167
+ create_parser("1 0 obj\n<< >>")
168
+ exp = assert_raises(HexaPDF::MalformedPDFError) { @parser.parse_indirect_object }
169
+ assert_match(/keyword endobj/, exp.message)
170
+ end
171
+
172
+ it "fails if there is data between 'endstream' and 'endobj'" do
173
+ create_parser("1 0 obj\n<< >>\nstream\nendstream\ntest\nendobj\n")
174
+ exp = assert_raises(HexaPDF::MalformedPDFError) { @parser.parse_indirect_object(0) }
175
+ assert_match(/keyword endobj/, exp.message)
176
+ end
177
+ end
135
178
  end
136
179
 
137
180
  describe "load_object" do
@@ -205,7 +248,7 @@ describe HexaPDF::Parser do
205
248
  end
206
249
 
207
250
  it "ignores garbage at the end of the file" do
208
- create_parser("startxref\n5\n%%EOF" + "\nhallo" * 150)
251
+ create_parser("startxref\n5\n%%EOF" << "\nhallo" * 150)
209
252
  assert_equal(5, @parser.startxref_offset)
210
253
  end
211
254
 
@@ -215,9 +258,9 @@ describe HexaPDF::Parser do
215
258
  end
216
259
 
217
260
  it "finds the startxref anywhere in file" do
218
- create_parser("startxref\n5\n%%EOF" + "\nhallo" * 5000)
261
+ create_parser("startxref\n5\n%%EOF" << "\nhallo" * 5000)
219
262
  assert_equal(5, @parser.startxref_offset)
220
- create_parser("startxref\n5\n%%EOF\n" + "h" * 1017)
263
+ create_parser("startxref\n5\n%%EOF\n" << "h" * 1017)
221
264
  assert_equal(5, @parser.startxref_offset)
222
265
  end
223
266
 
@@ -231,6 +274,10 @@ describe HexaPDF::Parser do
231
274
  create_parser("startxref\n5")
232
275
  exp = assert_raises(HexaPDF::MalformedPDFError) { @parser.startxref_offset }
233
276
  assert_match(/end-of-file marker not found/, exp.message)
277
+
278
+ create_parser("")
279
+ exp = assert_raises(HexaPDF::MalformedPDFError) { @parser.startxref_offset }
280
+ assert_match(/end-of-file marker not found/, exp.message)
234
281
  end
235
282
 
236
283
  it "fails if the startxref keyword is missing" do
@@ -238,6 +285,13 @@ describe HexaPDF::Parser do
238
285
  exp = assert_raises(HexaPDF::MalformedPDFError) { @parser.startxref_offset }
239
286
  assert_match(/missing startxref/, exp.message)
240
287
  end
288
+
289
+ it "fails on strict parsing if the startxref is not in the last part of the file" do
290
+ @document.config['parser.on_correctable_error'] = proc { true }
291
+ create_parser("startxref\n5\n%%EOF" << "\nhallo" * 5000)
292
+ exp = assert_raises(HexaPDF::MalformedPDFError) { @parser.startxref_offset }
293
+ assert_match(/end-of-file marker not found/, exp.message)
294
+ end
241
295
  end
242
296
 
243
297
  describe "file_header_version" do
@@ -251,8 +305,14 @@ describe HexaPDF::Parser do
251
305
  assert_match(/file header/, exp.message)
252
306
  end
253
307
 
308
+ it "fails if the header is missing" do
309
+ create_parser("no header")
310
+ exp = assert_raises(HexaPDF::MalformedPDFError) { @parser.file_header_version }
311
+ assert_match(/file header/, exp.message)
312
+ end
313
+
254
314
  it "ignores junk at the beginning of the file and correctly calculates offset" do
255
- create_parser("junk" * 200 + "\n%PDF-1.4\n")
315
+ create_parser("junk" * 200 << "\n%PDF-1.4\n")
256
316
  assert_equal('1.4', @parser.file_header_version)
257
317
  assert_equal(801, @parser.instance_variable_get(:@header_offset))
258
318
  end
@@ -308,6 +368,12 @@ describe HexaPDF::Parser do
308
368
  assert_match(/invalid cross-reference subsection/i, exp.message)
309
369
  end
310
370
 
371
+ it "fails if a sub section entry is mangled" do
372
+ create_parser("xref\n0 2\n000a000000 00000 n\n0000000000 65535 n\ntrailer\n<<>>\n")
373
+ exp = assert_raises(HexaPDF::MalformedPDFError) { @parser.parse_xref_section_and_trailer(0) }
374
+ assert_match(/invalid cross-reference entry/i, exp.message)
375
+ end
376
+
311
377
  it "fails if there is no trailer" do
312
378
  create_parser("xref\n0 1\n0000000000 00000 n \n")
313
379
  exp = assert_raises(HexaPDF::MalformedPDFError) { @parser.parse_xref_section_and_trailer(0) }
@@ -319,6 +385,30 @@ describe HexaPDF::Parser do
319
385
  exp = assert_raises(HexaPDF::MalformedPDFError) { @parser.parse_xref_section_and_trailer(0) }
320
386
  assert_match(/dictionary/, exp.message)
321
387
  end
388
+
389
+ describe "with strict parsing" do
390
+ before do
391
+ @document.config['parser.on_correctable_error'] = proc { true }
392
+ end
393
+
394
+ it "fails if xref type=n with offset=0" do
395
+ create_parser("xref\n0 2\n0000000000 00000 n \n0000000000 00000 n \ntrailer\n<<>>\n")
396
+ exp = assert_raises(HexaPDF::MalformedPDFError) { @parser.parse_xref_section_and_trailer(0) }
397
+ assert_match(/invalid.*cross-reference entry/i, exp.message)
398
+ end
399
+
400
+ it " fails xref type=n with gen>65535" do
401
+ create_parser("xref\n0 2\n0000000000 00000 n \n0000000000 65536 n \ntrailer\n<<>>\n")
402
+ exp = assert_raises(HexaPDF::MalformedPDFError) { @parser.parse_xref_section_and_trailer(0) }
403
+ assert_match(/invalid.*cross-reference entry/i, exp.message)
404
+ end
405
+
406
+ it "fails if trailing second whitespace is missing" do
407
+ create_parser("xref\n0 1\n0000000000 00000 n\ntrailer\n<<>>\n")
408
+ exp = assert_raises(HexaPDF::MalformedPDFError) { @parser.parse_xref_section_and_trailer(0) }
409
+ assert_match(/invalid.*cross-reference entry/i, exp.message)
410
+ end
411
+ end
322
412
  end
323
413
 
324
414
  describe "load_revision" do
@@ -338,75 +428,55 @@ describe HexaPDF::Parser do
338
428
  exp = assert_raises(HexaPDF::MalformedPDFError) { @parser.load_revision(10) }
339
429
  assert_match(/not a cross-reference stream/, exp.message)
340
430
  end
341
- end
342
431
 
343
- describe "with strict parsing enabled" do
344
- before do
432
+ it "fails on strict parsing if the cross-reference stream doesn't contain an entry for itself" do
345
433
  @document.config['parser.on_correctable_error'] = proc { true }
434
+ create_parser("2 0 obj\n<</Type/XRef/Length 3/W [1 1 1]/Size 1>>" \
435
+ "stream\n\x01\x0A\x00\nendstream endobj")
436
+ exp = assert_raises(HexaPDF::MalformedPDFError) { @parser.load_revision(0) }
437
+ assert_match(/entry for itself/, exp.message)
346
438
  end
439
+ end
347
440
 
348
- it "startxref_offset fails if the startxref is not in the last part of the file" do
349
- create_parser("startxref\n5\n%%EOF" + "\nhallo" * 5000)
350
- exp = assert_raises(HexaPDF::MalformedPDFError) { @parser.startxref_offset }
351
- assert_match(/end-of-file marker not found/, exp.message)
352
- end
353
-
354
- it "parse_xref_section_and_trailer fails if xref type=n with offset=0" do
355
- create_parser("xref\n0 2\n0000000000 00000 n \n0000000000 00000 n \ntrailer\n<<>>\n")
356
- exp = assert_raises(HexaPDF::MalformedPDFError) { @parser.parse_xref_section_and_trailer(0) }
357
- assert_match(/invalid.*cross-reference entry/i, exp.message)
358
- end
359
-
360
- it "parse_xref_section_and_trailer fails xref type=n with gen>65535" do
361
- create_parser("xref\n0 2\n0000000000 00000 n \n0000000000 65536 n \ntrailer\n<<>>\n")
362
- exp = assert_raises(HexaPDF::MalformedPDFError) { @parser.parse_xref_section_and_trailer(0) }
363
- assert_match(/invalid.*cross-reference entry/i, exp.message)
441
+ describe "reconstruct_revision" do
442
+ before do
443
+ @document.config['parser.try_xref_reconstruction'] = true
444
+ @xref = HexaPDF::XRefSection.in_use_entry(1, 0, 100)
364
445
  end
365
446
 
366
- it "parse_xref_section_and_trailer fails if trailing second whitespace is missing" do
367
- create_parser("xref\n0 1\n0000000000 00000 n\ntrailer\n<<>>\n")
368
- exp = assert_raises(HexaPDF::MalformedPDFError) { @parser.parse_xref_section_and_trailer(0) }
369
- assert_match(/invalid.*cross-reference subsection entry/i, exp.message)
447
+ it "serially parses the contents" do
448
+ create_parser("1 0 obj\n5\nendobj\n1 0 obj\n6\nendobj\ntrailer\n<</Size 1>>")
449
+ assert_equal(6, @parser.load_object(@xref).value)
370
450
  end
371
451
 
372
- it "parse_indirect_object fails if an empty indirect object is found" do
373
- create_parser("1 0 obj\nendobj")
374
- exp = assert_raises(HexaPDF::MalformedPDFError) { @parser.parse_indirect_object }
375
- assert_match(/no indirect object value/i, exp.message)
452
+ it "ignores parts where the starting line is split across lines" do
453
+ create_parser("1 0 obj\n5\nendobj\n1 0\nobj\n6\nendobj\ntrailer\n<</Size 1>>")
454
+ assert_equal(5, @parser.load_object(@xref).value)
376
455
  end
377
456
 
378
- it "parse_indirect_object fails if keyword stream is followed only by CR without LF" do
379
- create_parser("1 0 obj<</Length 2>> stream\r12\nendstream endobj")
380
- exp = assert_raises(HexaPDF::MalformedPDFError) { @parser.parse_indirect_object }
381
- assert_match(/not CR alone/, exp.message)
457
+ it "ignores invalid objects" do
458
+ create_parser("1 x obj\n5\nendobj\n1 0 xobj\n6\nendobj\n1 0 obj 4\nendobj\ntrailer\n<</Size 1>>")
459
+ assert_equal(4, @parser.load_object(@xref).value)
382
460
  end
383
461
 
384
- it "parse_indirect_object fails if the stream length value is invalid" do
385
- create_parser("1 0 obj<</Length 4>> stream\n12endstream endobj")
386
- exp = assert_raises(HexaPDF::MalformedPDFError) { @parser.parse_indirect_object }
387
- assert_match(/invalid stream length/i, exp.message)
462
+ it "ignores invalid lines" do
463
+ create_parser("1 0 obj\n5\nendobj\nhello there\n1 0 obj\n6\nendobj\ntrailer\n<</Size 1>>")
464
+ assert_equal(6, @parser.load_object(@xref).value)
388
465
  end
389
466
 
390
- it "parse_indirect_object fails if the keyword endobj is missing or mangled" do
391
- create_parser("1 0 obj\n<< >>\nendobjd\n")
392
- exp = assert_raises(HexaPDF::MalformedPDFError) { @parser.parse_indirect_object }
393
- assert_match(/keyword endobj/, exp.message)
394
- create_parser("1 0 obj\n<< >>")
395
- exp = assert_raises(HexaPDF::MalformedPDFError) { @parser.parse_indirect_object }
396
- assert_match(/keyword endobj/, exp.message)
467
+ it "uses the last trailer" do
468
+ create_parser("trailer <</Size 1>>\ntrailer <</Size 2/Prev 342>>")
469
+ assert_equal({Size: 2}, @parser.reconstructed_revision.trailer.value)
397
470
  end
398
471
 
399
- it "parse_indirect_object fails if there is data between 'endstream' and 'endobj'" do
400
- create_parser("1 0 obj\n<< >>\nstream\nendstream\ntest\nendobj\n")
401
- exp = assert_raises(HexaPDF::MalformedPDFError) { @parser.parse_indirect_object(0) }
402
- assert_match(/keyword endobj/, exp.message)
472
+ it "uses the first trailer in case of a linearized file" do
473
+ create_parser("trailer <</Size 1/Prev 342>>\ntrailer <</Size 2>>")
474
+ assert_equal({Size: 1}, @parser.reconstructed_revision.trailer.value)
403
475
  end
404
476
 
405
- it "load_revision fails if the cross-reference stream doesn't contain an entry for itself" do
406
- create_parser("2 0 obj\n<</Type/XRef/Length 3/W [1 1 1]/Size 1>>" \
407
- "stream\n\x01\x0A\x00\nendstream endobj")
408
- exp = assert_raises(HexaPDF::MalformedPDFError) { @parser.load_revision(0) }
409
- assert_match(/entry for itself/, exp.message)
477
+ it "fails if no valid trailer is found" do
478
+ create_parser("1 0 obj\n5\nendobj")
479
+ assert_raises(HexaPDF::MalformedPDFError) { @parser.load_object(@xref) }
410
480
  end
411
481
  end
412
482
  end
@@ -107,6 +107,13 @@ describe HexaPDF::PDFArray do
107
107
  assert_equal([1, :data, @array[2]], @array[0, 5])
108
108
  end
109
109
 
110
+ it "allows deleting an object" do
111
+ obj = @array.value[1]
112
+ assert_same(obj, @array.delete(obj))
113
+ ref = HexaPDF::Object.new(:test, oid: 1)
114
+ assert_equal(ref, @array.delete(ref))
115
+ end
116
+
110
117
  describe "slice!" do
111
118
  it "allows deleting a single element" do
112
119
  @array.slice!(2)
@@ -36,6 +36,20 @@ describe HexaPDF::Rectangle do
36
36
  assert_equal(4, rect.height)
37
37
  end
38
38
 
39
+ it "allows setting all fields of the rectangle" do
40
+ rect = HexaPDF::Rectangle.new([2, 1, 0, 5])
41
+ rect.left = 5
42
+ rect.right = 1
43
+ rect.bottom = 2
44
+ rect.top = 3
45
+ assert_equal([5, 2, 1, 3], rect.value)
46
+
47
+ rect.width = 10
48
+ assert_equal(15, rect.right)
49
+ rect.height = 10
50
+ assert_equal(12, rect.top)
51
+ end
52
+
39
53
  it "allows comparison to arrays" do
40
54
  rect = HexaPDF::Rectangle.new([0, 1, 2, 5])
41
55
  assert(rect == [0, 1, 2, 5])