hexapdf 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (346) hide show
  1. checksums.yaml +7 -0
  2. data/CONTRIBUTERS +3 -0
  3. data/LICENSE +26 -0
  4. data/README.md +88 -0
  5. data/Rakefile +121 -0
  6. data/VERSION +1 -0
  7. data/agpl-3.0.txt +661 -0
  8. data/bin/hexapdf +6 -0
  9. data/data/hexapdf/afm/Courier-Bold.afm +342 -0
  10. data/data/hexapdf/afm/Courier-BoldOblique.afm +342 -0
  11. data/data/hexapdf/afm/Courier-Oblique.afm +342 -0
  12. data/data/hexapdf/afm/Courier.afm +342 -0
  13. data/data/hexapdf/afm/Helvetica-Bold.afm +2827 -0
  14. data/data/hexapdf/afm/Helvetica-BoldOblique.afm +2827 -0
  15. data/data/hexapdf/afm/Helvetica-Oblique.afm +3051 -0
  16. data/data/hexapdf/afm/Helvetica.afm +3051 -0
  17. data/data/hexapdf/afm/MustRead.html +1 -0
  18. data/data/hexapdf/afm/Symbol.afm +213 -0
  19. data/data/hexapdf/afm/Times-Bold.afm +2588 -0
  20. data/data/hexapdf/afm/Times-BoldItalic.afm +2384 -0
  21. data/data/hexapdf/afm/Times-Italic.afm +2667 -0
  22. data/data/hexapdf/afm/Times-Roman.afm +2419 -0
  23. data/data/hexapdf/afm/ZapfDingbats.afm +225 -0
  24. data/data/hexapdf/encoding/glyphlist.txt +4305 -0
  25. data/data/hexapdf/encoding/zapfdingbats.txt +225 -0
  26. data/examples/arc.rb +50 -0
  27. data/examples/graphics.rb +274 -0
  28. data/examples/hello_world.rb +16 -0
  29. data/examples/machupicchu.jpg +0 -0
  30. data/examples/merging.rb +24 -0
  31. data/examples/optimizing.rb +20 -0
  32. data/examples/show_char_bboxes.rb +55 -0
  33. data/examples/standard_pdf_fonts.rb +72 -0
  34. data/examples/truetype.rb +45 -0
  35. data/lib/hexapdf/cli/extract.rb +128 -0
  36. data/lib/hexapdf/cli/info.rb +121 -0
  37. data/lib/hexapdf/cli/inspect.rb +157 -0
  38. data/lib/hexapdf/cli/modify.rb +218 -0
  39. data/lib/hexapdf/cli.rb +121 -0
  40. data/lib/hexapdf/configuration.rb +392 -0
  41. data/lib/hexapdf/content/canvas.rb +1974 -0
  42. data/lib/hexapdf/content/color_space.rb +364 -0
  43. data/lib/hexapdf/content/graphic_object/arc.rb +267 -0
  44. data/lib/hexapdf/content/graphic_object/endpoint_arc.rb +208 -0
  45. data/lib/hexapdf/content/graphic_object/solid_arc.rb +173 -0
  46. data/lib/hexapdf/content/graphic_object.rb +81 -0
  47. data/lib/hexapdf/content/graphics_state.rb +579 -0
  48. data/lib/hexapdf/content/operator.rb +1072 -0
  49. data/lib/hexapdf/content/parser.rb +204 -0
  50. data/lib/hexapdf/content/processor.rb +451 -0
  51. data/lib/hexapdf/content/transformation_matrix.rb +172 -0
  52. data/lib/hexapdf/content.rb +47 -0
  53. data/lib/hexapdf/data_dir.rb +51 -0
  54. data/lib/hexapdf/dictionary.rb +303 -0
  55. data/lib/hexapdf/dictionary_fields.rb +382 -0
  56. data/lib/hexapdf/document.rb +589 -0
  57. data/lib/hexapdf/document_utils.rb +209 -0
  58. data/lib/hexapdf/encryption/aes.rb +206 -0
  59. data/lib/hexapdf/encryption/arc4.rb +93 -0
  60. data/lib/hexapdf/encryption/fast_aes.rb +79 -0
  61. data/lib/hexapdf/encryption/fast_arc4.rb +67 -0
  62. data/lib/hexapdf/encryption/identity.rb +63 -0
  63. data/lib/hexapdf/encryption/ruby_aes.rb +447 -0
  64. data/lib/hexapdf/encryption/ruby_arc4.rb +96 -0
  65. data/lib/hexapdf/encryption/security_handler.rb +494 -0
  66. data/lib/hexapdf/encryption/standard_security_handler.rb +616 -0
  67. data/lib/hexapdf/encryption.rb +94 -0
  68. data/lib/hexapdf/error.rb +73 -0
  69. data/lib/hexapdf/filter/ascii85_decode.rb +160 -0
  70. data/lib/hexapdf/filter/ascii_hex_decode.rb +87 -0
  71. data/lib/hexapdf/filter/dct_decode.rb +57 -0
  72. data/lib/hexapdf/filter/encryption.rb +59 -0
  73. data/lib/hexapdf/filter/flate_decode.rb +93 -0
  74. data/lib/hexapdf/filter/jpx_decode.rb +56 -0
  75. data/lib/hexapdf/filter/lzw_decode.rb +191 -0
  76. data/lib/hexapdf/filter/predictor.rb +266 -0
  77. data/lib/hexapdf/filter/run_length_decode.rb +108 -0
  78. data/lib/hexapdf/filter.rb +176 -0
  79. data/lib/hexapdf/font/cmap/parser.rb +146 -0
  80. data/lib/hexapdf/font/cmap/writer.rb +176 -0
  81. data/lib/hexapdf/font/cmap.rb +90 -0
  82. data/lib/hexapdf/font/encoding/base.rb +77 -0
  83. data/lib/hexapdf/font/encoding/difference_encoding.rb +64 -0
  84. data/lib/hexapdf/font/encoding/glyph_list.rb +150 -0
  85. data/lib/hexapdf/font/encoding/mac_expert_encoding.rb +221 -0
  86. data/lib/hexapdf/font/encoding/mac_roman_encoding.rb +265 -0
  87. data/lib/hexapdf/font/encoding/standard_encoding.rb +205 -0
  88. data/lib/hexapdf/font/encoding/symbol_encoding.rb +244 -0
  89. data/lib/hexapdf/font/encoding/win_ansi_encoding.rb +280 -0
  90. data/lib/hexapdf/font/encoding/zapf_dingbats_encoding.rb +250 -0
  91. data/lib/hexapdf/font/encoding.rb +68 -0
  92. data/lib/hexapdf/font/true_type/font.rb +179 -0
  93. data/lib/hexapdf/font/true_type/table/cmap.rb +103 -0
  94. data/lib/hexapdf/font/true_type/table/cmap_subtable.rb +384 -0
  95. data/lib/hexapdf/font/true_type/table/directory.rb +92 -0
  96. data/lib/hexapdf/font/true_type/table/glyf.rb +166 -0
  97. data/lib/hexapdf/font/true_type/table/head.rb +143 -0
  98. data/lib/hexapdf/font/true_type/table/hhea.rb +109 -0
  99. data/lib/hexapdf/font/true_type/table/hmtx.rb +79 -0
  100. data/lib/hexapdf/font/true_type/table/loca.rb +79 -0
  101. data/lib/hexapdf/font/true_type/table/maxp.rb +112 -0
  102. data/lib/hexapdf/font/true_type/table/name.rb +218 -0
  103. data/lib/hexapdf/font/true_type/table/os2.rb +200 -0
  104. data/lib/hexapdf/font/true_type/table/post.rb +230 -0
  105. data/lib/hexapdf/font/true_type/table.rb +155 -0
  106. data/lib/hexapdf/font/true_type.rb +48 -0
  107. data/lib/hexapdf/font/true_type_wrapper.rb +240 -0
  108. data/lib/hexapdf/font/type1/afm_parser.rb +230 -0
  109. data/lib/hexapdf/font/type1/character_metrics.rb +67 -0
  110. data/lib/hexapdf/font/type1/font.rb +123 -0
  111. data/lib/hexapdf/font/type1/font_metrics.rb +117 -0
  112. data/lib/hexapdf/font/type1/pfb_parser.rb +71 -0
  113. data/lib/hexapdf/font/type1.rb +52 -0
  114. data/lib/hexapdf/font/type1_wrapper.rb +193 -0
  115. data/lib/hexapdf/font_loader/from_configuration.rb +70 -0
  116. data/lib/hexapdf/font_loader/standard14.rb +98 -0
  117. data/lib/hexapdf/font_loader.rb +85 -0
  118. data/lib/hexapdf/font_utils.rb +89 -0
  119. data/lib/hexapdf/image_loader/jpeg.rb +166 -0
  120. data/lib/hexapdf/image_loader/pdf.rb +89 -0
  121. data/lib/hexapdf/image_loader/png.rb +410 -0
  122. data/lib/hexapdf/image_loader.rb +68 -0
  123. data/lib/hexapdf/importer.rb +139 -0
  124. data/lib/hexapdf/name_tree_node.rb +78 -0
  125. data/lib/hexapdf/number_tree_node.rb +67 -0
  126. data/lib/hexapdf/object.rb +363 -0
  127. data/lib/hexapdf/parser.rb +349 -0
  128. data/lib/hexapdf/rectangle.rb +99 -0
  129. data/lib/hexapdf/reference.rb +98 -0
  130. data/lib/hexapdf/revision.rb +206 -0
  131. data/lib/hexapdf/revisions.rb +194 -0
  132. data/lib/hexapdf/serializer.rb +326 -0
  133. data/lib/hexapdf/stream.rb +279 -0
  134. data/lib/hexapdf/task/dereference.rb +109 -0
  135. data/lib/hexapdf/task/optimize.rb +230 -0
  136. data/lib/hexapdf/task.rb +68 -0
  137. data/lib/hexapdf/tokenizer.rb +406 -0
  138. data/lib/hexapdf/type/catalog.rb +107 -0
  139. data/lib/hexapdf/type/embedded_file.rb +87 -0
  140. data/lib/hexapdf/type/file_specification.rb +232 -0
  141. data/lib/hexapdf/type/font.rb +81 -0
  142. data/lib/hexapdf/type/font_descriptor.rb +109 -0
  143. data/lib/hexapdf/type/font_simple.rb +190 -0
  144. data/lib/hexapdf/type/font_true_type.rb +47 -0
  145. data/lib/hexapdf/type/font_type1.rb +162 -0
  146. data/lib/hexapdf/type/form.rb +103 -0
  147. data/lib/hexapdf/type/graphics_state_parameter.rb +79 -0
  148. data/lib/hexapdf/type/image.rb +73 -0
  149. data/lib/hexapdf/type/info.rb +70 -0
  150. data/lib/hexapdf/type/names.rb +69 -0
  151. data/lib/hexapdf/type/object_stream.rb +224 -0
  152. data/lib/hexapdf/type/page.rb +355 -0
  153. data/lib/hexapdf/type/page_tree_node.rb +269 -0
  154. data/lib/hexapdf/type/resources.rb +212 -0
  155. data/lib/hexapdf/type/trailer.rb +128 -0
  156. data/lib/hexapdf/type/viewer_preferences.rb +73 -0
  157. data/lib/hexapdf/type/xref_stream.rb +204 -0
  158. data/lib/hexapdf/type.rb +67 -0
  159. data/lib/hexapdf/utils/bit_field.rb +87 -0
  160. data/lib/hexapdf/utils/bit_stream.rb +148 -0
  161. data/lib/hexapdf/utils/lru_cache.rb +65 -0
  162. data/lib/hexapdf/utils/math_helpers.rb +55 -0
  163. data/lib/hexapdf/utils/object_hash.rb +130 -0
  164. data/lib/hexapdf/utils/pdf_doc_encoding.rb +93 -0
  165. data/lib/hexapdf/utils/sorted_tree_node.rb +339 -0
  166. data/lib/hexapdf/version.rb +39 -0
  167. data/lib/hexapdf/writer.rb +199 -0
  168. data/lib/hexapdf/xref_section.rb +152 -0
  169. data/lib/hexapdf.rb +34 -0
  170. data/man/man1/hexapdf.1 +249 -0
  171. data/test/data/aes-test-vectors/CBCGFSbox-128-decrypt.data.gz +0 -0
  172. data/test/data/aes-test-vectors/CBCGFSbox-128-encrypt.data.gz +0 -0
  173. data/test/data/aes-test-vectors/CBCGFSbox-192-decrypt.data.gz +0 -0
  174. data/test/data/aes-test-vectors/CBCGFSbox-192-encrypt.data.gz +0 -0
  175. data/test/data/aes-test-vectors/CBCGFSbox-256-decrypt.data.gz +0 -0
  176. data/test/data/aes-test-vectors/CBCGFSbox-256-encrypt.data.gz +0 -0
  177. data/test/data/aes-test-vectors/CBCKeySbox-128-decrypt.data.gz +0 -0
  178. data/test/data/aes-test-vectors/CBCKeySbox-128-encrypt.data.gz +0 -0
  179. data/test/data/aes-test-vectors/CBCKeySbox-192-decrypt.data.gz +0 -0
  180. data/test/data/aes-test-vectors/CBCKeySbox-192-encrypt.data.gz +0 -0
  181. data/test/data/aes-test-vectors/CBCKeySbox-256-decrypt.data.gz +0 -0
  182. data/test/data/aes-test-vectors/CBCKeySbox-256-encrypt.data.gz +0 -0
  183. data/test/data/aes-test-vectors/CBCVarKey-128-decrypt.data.gz +0 -0
  184. data/test/data/aes-test-vectors/CBCVarKey-128-encrypt.data.gz +0 -0
  185. data/test/data/aes-test-vectors/CBCVarKey-192-decrypt.data.gz +0 -0
  186. data/test/data/aes-test-vectors/CBCVarKey-192-encrypt.data.gz +0 -0
  187. data/test/data/aes-test-vectors/CBCVarKey-256-decrypt.data.gz +0 -0
  188. data/test/data/aes-test-vectors/CBCVarKey-256-encrypt.data.gz +0 -0
  189. data/test/data/aes-test-vectors/CBCVarTxt-128-decrypt.data.gz +0 -0
  190. data/test/data/aes-test-vectors/CBCVarTxt-128-encrypt.data.gz +0 -0
  191. data/test/data/aes-test-vectors/CBCVarTxt-192-decrypt.data.gz +0 -0
  192. data/test/data/aes-test-vectors/CBCVarTxt-192-encrypt.data.gz +0 -0
  193. data/test/data/aes-test-vectors/CBCVarTxt-256-decrypt.data.gz +0 -0
  194. data/test/data/aes-test-vectors/CBCVarTxt-256-encrypt.data.gz +0 -0
  195. data/test/data/fonts/Ubuntu-Title.ttf +0 -0
  196. data/test/data/images/cmyk.jpg +0 -0
  197. data/test/data/images/fillbytes.jpg +0 -0
  198. data/test/data/images/gray.jpg +0 -0
  199. data/test/data/images/greyscale-1bit.png +0 -0
  200. data/test/data/images/greyscale-2bit.png +0 -0
  201. data/test/data/images/greyscale-4bit.png +0 -0
  202. data/test/data/images/greyscale-8bit.png +0 -0
  203. data/test/data/images/greyscale-alpha-8bit.png +0 -0
  204. data/test/data/images/greyscale-trns-8bit.png +0 -0
  205. data/test/data/images/greyscale-with-gamma1.0.png +0 -0
  206. data/test/data/images/greyscale-with-gamma1.5.png +0 -0
  207. data/test/data/images/indexed-1bit.png +0 -0
  208. data/test/data/images/indexed-2bit.png +0 -0
  209. data/test/data/images/indexed-4bit.png +0 -0
  210. data/test/data/images/indexed-8bit.png +0 -0
  211. data/test/data/images/indexed-alpha-4bit.png +0 -0
  212. data/test/data/images/indexed-alpha-8bit.png +0 -0
  213. data/test/data/images/rgb.jpg +0 -0
  214. data/test/data/images/truecolour-8bit.png +0 -0
  215. data/test/data/images/truecolour-alpha-8bit.png +0 -0
  216. data/test/data/images/truecolour-gama-chrm-8bit.png +0 -0
  217. data/test/data/images/truecolour-srgb-8bit.png +0 -0
  218. data/test/data/minimal.pdf +44 -0
  219. data/test/data/standard-security-handler/README +9 -0
  220. data/test/data/standard-security-handler/bothpwd-aes-128bit-V4.pdf +44 -0
  221. data/test/data/standard-security-handler/bothpwd-aes-256bit-V5.pdf +0 -0
  222. data/test/data/standard-security-handler/bothpwd-arc4-128bit-V2.pdf +43 -0
  223. data/test/data/standard-security-handler/bothpwd-arc4-128bit-V4.pdf +43 -0
  224. data/test/data/standard-security-handler/bothpwd-arc4-40bit-V1.pdf +0 -0
  225. data/test/data/standard-security-handler/nopwd-aes-128bit-V4.pdf +43 -0
  226. data/test/data/standard-security-handler/nopwd-aes-256bit-V5.pdf +0 -0
  227. data/test/data/standard-security-handler/nopwd-arc4-128bit-V2.pdf +43 -0
  228. data/test/data/standard-security-handler/nopwd-arc4-128bit-V4.pdf +43 -0
  229. data/test/data/standard-security-handler/nopwd-arc4-40bit-V1.pdf +43 -0
  230. data/test/data/standard-security-handler/ownerpwd-aes-128bit-V4.pdf +0 -0
  231. data/test/data/standard-security-handler/ownerpwd-aes-256bit-V5.pdf +43 -0
  232. data/test/data/standard-security-handler/ownerpwd-arc4-128bit-V2.pdf +43 -0
  233. data/test/data/standard-security-handler/ownerpwd-arc4-128bit-V4.pdf +43 -0
  234. data/test/data/standard-security-handler/ownerpwd-arc4-40bit-V1.pdf +43 -0
  235. data/test/data/standard-security-handler/userpwd-aes-128bit-V4.pdf +43 -0
  236. data/test/data/standard-security-handler/userpwd-aes-256bit-V5.pdf +43 -0
  237. data/test/data/standard-security-handler/userpwd-arc4-128bit-V2.pdf +0 -0
  238. data/test/data/standard-security-handler/userpwd-arc4-128bit-V4.pdf +0 -0
  239. data/test/data/standard-security-handler/userpwd-arc4-40bit-V1.pdf +43 -0
  240. data/test/hexapdf/common_tokenizer_tests.rb +204 -0
  241. data/test/hexapdf/content/common.rb +31 -0
  242. data/test/hexapdf/content/graphic_object/test_arc.rb +93 -0
  243. data/test/hexapdf/content/graphic_object/test_endpoint_arc.rb +91 -0
  244. data/test/hexapdf/content/graphic_object/test_solid_arc.rb +86 -0
  245. data/test/hexapdf/content/test_canvas.rb +1113 -0
  246. data/test/hexapdf/content/test_color_space.rb +97 -0
  247. data/test/hexapdf/content/test_graphics_state.rb +138 -0
  248. data/test/hexapdf/content/test_operator.rb +619 -0
  249. data/test/hexapdf/content/test_parser.rb +66 -0
  250. data/test/hexapdf/content/test_processor.rb +156 -0
  251. data/test/hexapdf/content/test_transformation_matrix.rb +64 -0
  252. data/test/hexapdf/encryption/common.rb +87 -0
  253. data/test/hexapdf/encryption/test_aes.rb +121 -0
  254. data/test/hexapdf/encryption/test_arc4.rb +39 -0
  255. data/test/hexapdf/encryption/test_fast_aes.rb +17 -0
  256. data/test/hexapdf/encryption/test_fast_arc4.rb +12 -0
  257. data/test/hexapdf/encryption/test_identity.rb +21 -0
  258. data/test/hexapdf/encryption/test_ruby_aes.rb +23 -0
  259. data/test/hexapdf/encryption/test_ruby_arc4.rb +20 -0
  260. data/test/hexapdf/encryption/test_security_handler.rb +356 -0
  261. data/test/hexapdf/encryption/test_standard_security_handler.rb +274 -0
  262. data/test/hexapdf/filter/common.rb +53 -0
  263. data/test/hexapdf/filter/test_ascii85_decode.rb +60 -0
  264. data/test/hexapdf/filter/test_ascii_hex_decode.rb +33 -0
  265. data/test/hexapdf/filter/test_encryption.rb +24 -0
  266. data/test/hexapdf/filter/test_flate_decode.rb +35 -0
  267. data/test/hexapdf/filter/test_lzw_decode.rb +52 -0
  268. data/test/hexapdf/filter/test_predictor.rb +183 -0
  269. data/test/hexapdf/filter/test_run_length_decode.rb +32 -0
  270. data/test/hexapdf/font/cmap/test_parser.rb +67 -0
  271. data/test/hexapdf/font/cmap/test_writer.rb +58 -0
  272. data/test/hexapdf/font/encoding/test_base.rb +35 -0
  273. data/test/hexapdf/font/encoding/test_difference_encoding.rb +21 -0
  274. data/test/hexapdf/font/encoding/test_glyph_list.rb +59 -0
  275. data/test/hexapdf/font/encoding/test_zapf_dingbats_encoding.rb +16 -0
  276. data/test/hexapdf/font/test_encoding.rb +27 -0
  277. data/test/hexapdf/font/test_true_type_wrapper.rb +110 -0
  278. data/test/hexapdf/font/test_type1_wrapper.rb +66 -0
  279. data/test/hexapdf/font/true_type/common.rb +19 -0
  280. data/test/hexapdf/font/true_type/table/test_cmap.rb +59 -0
  281. data/test/hexapdf/font/true_type/table/test_cmap_subtable.rb +133 -0
  282. data/test/hexapdf/font/true_type/table/test_directory.rb +35 -0
  283. data/test/hexapdf/font/true_type/table/test_glyf.rb +58 -0
  284. data/test/hexapdf/font/true_type/table/test_head.rb +76 -0
  285. data/test/hexapdf/font/true_type/table/test_hhea.rb +40 -0
  286. data/test/hexapdf/font/true_type/table/test_hmtx.rb +38 -0
  287. data/test/hexapdf/font/true_type/table/test_loca.rb +43 -0
  288. data/test/hexapdf/font/true_type/table/test_maxp.rb +62 -0
  289. data/test/hexapdf/font/true_type/table/test_name.rb +95 -0
  290. data/test/hexapdf/font/true_type/table/test_os2.rb +65 -0
  291. data/test/hexapdf/font/true_type/table/test_post.rb +89 -0
  292. data/test/hexapdf/font/true_type/test_font.rb +120 -0
  293. data/test/hexapdf/font/true_type/test_table.rb +41 -0
  294. data/test/hexapdf/font/type1/test_afm_parser.rb +51 -0
  295. data/test/hexapdf/font/type1/test_font.rb +68 -0
  296. data/test/hexapdf/font/type1/test_pfb_parser.rb +37 -0
  297. data/test/hexapdf/font_loader/test_from_configuration.rb +28 -0
  298. data/test/hexapdf/font_loader/test_standard14.rb +22 -0
  299. data/test/hexapdf/image_loader/test_jpeg.rb +83 -0
  300. data/test/hexapdf/image_loader/test_pdf.rb +47 -0
  301. data/test/hexapdf/image_loader/test_png.rb +258 -0
  302. data/test/hexapdf/task/test_dereference.rb +46 -0
  303. data/test/hexapdf/task/test_optimize.rb +137 -0
  304. data/test/hexapdf/test_configuration.rb +82 -0
  305. data/test/hexapdf/test_data_dir.rb +32 -0
  306. data/test/hexapdf/test_dictionary.rb +284 -0
  307. data/test/hexapdf/test_dictionary_fields.rb +185 -0
  308. data/test/hexapdf/test_document.rb +574 -0
  309. data/test/hexapdf/test_document_utils.rb +144 -0
  310. data/test/hexapdf/test_filter.rb +96 -0
  311. data/test/hexapdf/test_font_utils.rb +47 -0
  312. data/test/hexapdf/test_importer.rb +78 -0
  313. data/test/hexapdf/test_object.rb +177 -0
  314. data/test/hexapdf/test_parser.rb +394 -0
  315. data/test/hexapdf/test_rectangle.rb +36 -0
  316. data/test/hexapdf/test_reference.rb +41 -0
  317. data/test/hexapdf/test_revision.rb +139 -0
  318. data/test/hexapdf/test_revisions.rb +93 -0
  319. data/test/hexapdf/test_serializer.rb +169 -0
  320. data/test/hexapdf/test_stream.rb +262 -0
  321. data/test/hexapdf/test_tokenizer.rb +30 -0
  322. data/test/hexapdf/test_writer.rb +120 -0
  323. data/test/hexapdf/test_xref_section.rb +35 -0
  324. data/test/hexapdf/type/test_catalog.rb +30 -0
  325. data/test/hexapdf/type/test_embedded_file.rb +16 -0
  326. data/test/hexapdf/type/test_file_specification.rb +148 -0
  327. data/test/hexapdf/type/test_font.rb +35 -0
  328. data/test/hexapdf/type/test_font_descriptor.rb +51 -0
  329. data/test/hexapdf/type/test_font_simple.rb +190 -0
  330. data/test/hexapdf/type/test_font_type1.rb +128 -0
  331. data/test/hexapdf/type/test_form.rb +60 -0
  332. data/test/hexapdf/type/test_info.rb +14 -0
  333. data/test/hexapdf/type/test_names.rb +9 -0
  334. data/test/hexapdf/type/test_object_stream.rb +84 -0
  335. data/test/hexapdf/type/test_page.rb +260 -0
  336. data/test/hexapdf/type/test_page_tree_node.rb +255 -0
  337. data/test/hexapdf/type/test_resources.rb +167 -0
  338. data/test/hexapdf/type/test_trailer.rb +109 -0
  339. data/test/hexapdf/type/test_xref_stream.rb +131 -0
  340. data/test/hexapdf/utils/test_bit_field.rb +47 -0
  341. data/test/hexapdf/utils/test_lru_cache.rb +22 -0
  342. data/test/hexapdf/utils/test_object_hash.rb +115 -0
  343. data/test/hexapdf/utils/test_pdf_doc_encoding.rb +18 -0
  344. data/test/hexapdf/utils/test_sorted_tree_node.rb +232 -0
  345. data/test/test_helper.rb +56 -0
  346. metadata +427 -0
@@ -0,0 +1,84 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ require 'test_helper'
4
+ require 'hexapdf/type/object_stream'
5
+
6
+ describe HexaPDF::Type::ObjectStream::Data do
7
+ before do
8
+ @data = HexaPDF::Type::ObjectStream::Data.new("5 [1 2]", [1, 5], [0, 2])
9
+ end
10
+
11
+ it "returns the correct [object, oid] pair for a given index" do
12
+ assert_equal([5, 1], @data.object_by_index(0))
13
+ assert_equal([[1, 2], 5], @data.object_by_index(1))
14
+ end
15
+
16
+ it "fails if the index is out of bounds" do
17
+ assert_raises(ArgumentError) { @data.object_by_index(5) }
18
+ assert_raises(ArgumentError) { @data.object_by_index(-1) }
19
+ end
20
+ end
21
+
22
+
23
+ describe HexaPDF::Type::ObjectStream do
24
+ before do
25
+ @doc = Object.new
26
+ @doc.instance_variable_set(:@version, '1.5')
27
+ def (@doc).trailer
28
+ @trailer ||= {Encrypt: HexaPDF::Object.new({}, oid: 9)}
29
+ end
30
+ @obj = HexaPDF::Type::ObjectStream.new({}, oid: 1, document: @doc)
31
+ end
32
+
33
+ it "correctly parses stream data" do
34
+ @obj.value = {N: 2, First: 8}
35
+ @obj.stream = "1 0 5 2 5 [1 2]"
36
+ data = @obj.parse_stream
37
+ assert_equal([5, 1], data.object_by_index(0))
38
+ assert_equal([[1, 2], 5], data.object_by_index(1))
39
+ end
40
+
41
+ it "allows adding and deleting object as well as determining their index" do
42
+ @obj.add_object(5)
43
+ @obj.add_object(7)
44
+ @obj.add_object(9)
45
+ assert_equal(0, @obj.object_index(5))
46
+ assert_equal(1, @obj.object_index(7))
47
+ assert_equal(2, @obj.object_index(9))
48
+
49
+ @obj.delete_object(5)
50
+ assert_equal(0, @obj.object_index(9))
51
+ assert_equal(1, @obj.object_index(7))
52
+ assert_equal(nil, @obj.object_index(5))
53
+
54
+ @obj.delete_object(7)
55
+ @obj.delete_object(9)
56
+ assert_equal(nil, @obj.object_index(5))
57
+ end
58
+
59
+ it "allows writing the objects to the stream" do
60
+ @obj.stream = 'something'
61
+ @obj.add_object(HexaPDF::Object.new(5, oid: 1))
62
+ @obj.add_object(HexaPDF::Object.new(:will_be_deleted, oid: 3, gen: 1))
63
+ @obj.add_object(HexaPDF::Object.new([1, 2], oid: 5))
64
+ @obj.add_object(HexaPDF::Object.new(nil, oid: 7))
65
+ @obj.add_object(@doc.trailer[:Encrypt])
66
+
67
+ revision = Object.new
68
+ def revision.object(obj); obj; end
69
+ @obj.write_objects(revision)
70
+
71
+ assert_equal(2, @obj.value[:N])
72
+ assert_equal(8, @obj.value[:First])
73
+ assert_equal("1 0 5 2 5 [1 2] ", @obj.stream)
74
+ end
75
+
76
+ it "fails validation if gen != 0" do
77
+ assert(@obj.validate(auto_correct: false))
78
+ @obj.gen = 1
79
+ refute(@obj.validate(auto_correct: false) do |msg, correctable|
80
+ assert_match(/invalid generation/, msg)
81
+ refute(correctable)
82
+ end)
83
+ end
84
+ end
@@ -0,0 +1,260 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ require 'test_helper'
4
+ require_relative '../content/common'
5
+ require 'stringio'
6
+ require 'hexapdf/document'
7
+ require 'hexapdf/type/page'
8
+
9
+ describe HexaPDF::Type::Page do
10
+ before do
11
+ @doc = HexaPDF::Document.new
12
+ end
13
+
14
+ # Asserts that the page's contents contains the operators.
15
+ def assert_operators(page, operators)
16
+ processor = TestHelper::OperatorRecorder.new
17
+ page.process_contents(processor)
18
+ assert_equal(operators, processor.recorded_ops)
19
+ end
20
+
21
+ it "must always be indirect" do
22
+ page = @doc.add(Type: :Page)
23
+ page.must_be_indirect = false
24
+ assert(page.must_be_indirect?)
25
+ end
26
+
27
+ describe "[]" do
28
+ before do
29
+ @root = @doc.add(Type: :Pages)
30
+ @kid = @doc.add(Type: :Pages, Parent: @root)
31
+ @page = @doc.add(Type: :Page, Parent: @kid)
32
+ end
33
+
34
+ it "works normal for non-inheritable fields" do
35
+ assert_equal(:Page, @page[:Type])
36
+ assert_nil(@page[:Dur])
37
+ end
38
+
39
+ it "automatically retrieves inherited values" do
40
+ @root[:MediaBox] = :media
41
+ assert_equal(:media, @page[:MediaBox])
42
+
43
+ @root[:Resources] = :root_res
44
+ @kid[:Resources] = :res
45
+ assert_equal(:res, @page[:Resources])
46
+
47
+ @page[:CropBox] = :cropbox
48
+ assert_equal(:cropbox, @page[:CropBox])
49
+
50
+ @kid[:Rotate] = :kid_rotate
51
+ assert_equal(:kid_rotate, @page[:Rotate])
52
+ @kid.delete(:Rotate)
53
+ assert_equal(0, @page[:Rotate])
54
+ end
55
+
56
+ it "fails if no parent node is associated" do
57
+ page = @doc.add(Type: :Page)
58
+ assert_raises(HexaPDF::InvalidPDFObjectError) { page[:Resources] }
59
+ end
60
+ end
61
+
62
+ describe "validation" do
63
+ it "fails if a required inheritable field is not set" do
64
+ root = @doc.add(Type: :Pages)
65
+ page = @doc.add(Type: :Page, Parent: root)
66
+ message = ''
67
+ refute(page.validate {|m, _| message = m})
68
+ assert_match(/inheritable.*MediaBox/i, message)
69
+ end
70
+ end
71
+
72
+ describe "box" do
73
+ before do
74
+ @page = @doc.pages.add_page
75
+ end
76
+
77
+ it "returns the correct media box" do
78
+ @page[:MediaBox] = :media
79
+ assert_equal(:media, @page.box(:media))
80
+ end
81
+
82
+ it "returns the correct crop box" do
83
+ @page[:MediaBox] = :media
84
+ assert_equal(:media, @page.box(:crop))
85
+ @page[:CropBox] = :crop
86
+ assert_equal(:crop, @page.box(:crop))
87
+ end
88
+
89
+ it "returns the correct bleed, trim and art boxes" do
90
+ @page[:CropBox] = :crop
91
+ assert_equal(:crop, @page.box(:bleed))
92
+ assert_equal(:crop, @page.box(:trim))
93
+ assert_equal(:crop, @page.box(:art))
94
+ @page[:BleedBox] = :bleed
95
+ @page[:TrimBox] = :trim
96
+ @page[:ArtBox] = :art
97
+ assert_equal(:bleed, @page.box(:bleed))
98
+ assert_equal(:trim, @page.box(:trim))
99
+ assert_equal(:art, @page.box(:art))
100
+ end
101
+
102
+ it "fails if an unknown box type is supplied" do
103
+ assert_raises(ArgumentError) { @page.box(:undefined) }
104
+ end
105
+ end
106
+
107
+ describe "contents" do
108
+ it "returns the contents of a single content stream" do
109
+ page = @doc.pages.add_page
110
+ page[:Contents] = @doc.wrap({}, stream: 'q 10 w Q')
111
+ assert_equal('q 10 w Q', page.contents)
112
+ end
113
+
114
+ it "returns the concatenated contents of multiple content stream" do
115
+ page = @doc.pages.add_page
116
+ page[:Contents] = [@doc.wrap({}, stream: 'q 10'), @doc.wrap({}, stream: 'w Q')]
117
+ assert_equal('q 10 w Q', page.contents)
118
+ end
119
+ end
120
+
121
+ describe "contents=" do
122
+ it "creates a content stream if none already exist" do
123
+ page = @doc.pages.add_page
124
+ page.contents = 'test'
125
+ assert_equal('test', page[:Contents].stream)
126
+ end
127
+
128
+ it "reuses an existing content stream" do
129
+ page = @doc.pages.add_page
130
+ page[:Contents] = content = @doc.wrap({}, stream: 'q 10 w Q')
131
+ page.contents = 'test'
132
+ assert_equal(content, page[:Contents])
133
+ assert_equal('test', content.stream)
134
+ end
135
+
136
+ it "reuses the first content stream and deletes the rest if more than one exist" do
137
+ page = @doc.pages.add_page
138
+ page[:Contents] = [content = @doc.add({}, stream: 'q 10 w Q'), @doc.add({}, stream: 'q Q')]
139
+ page.contents = 'test'
140
+ assert_equal(content, page[:Contents])
141
+ assert_equal('test', content.stream)
142
+ end
143
+ end
144
+
145
+ describe "resources" do
146
+ it "creates the resource dictionary if it is not found" do
147
+ page = @doc.add(Type: :Page, Parent: @doc.pages)
148
+ resources = page.resources
149
+ assert_equal(:XXResources, resources.type)
150
+ assert_equal({}, resources.value)
151
+ end
152
+
153
+ it "returns the already used resource dictionary" do
154
+ @doc.pages[:Resources] = {Font: {F1: nil}}
155
+ page = @doc.pages.add_page(@doc.add(Type: :Page))
156
+ resources = page.resources
157
+ assert_equal(:XXResources, resources.type)
158
+ assert_equal(@doc.pages[:Resources], resources)
159
+ end
160
+ end
161
+
162
+ describe "process_contents" do
163
+ it "parses the contents and processes it" do
164
+ page = @doc.pages.add_page
165
+ page[:Contents] = @doc.wrap({}, stream: 'q 10 w Q')
166
+ assert_operators(page, [[:save_graphics_state], [:set_line_width, [10]],
167
+ [:restore_graphics_state]])
168
+ end
169
+ end
170
+
171
+ describe "canvas" do
172
+ before do
173
+ @page = @doc.pages.add_page
174
+ end
175
+
176
+ it "works correctly if invoked on an empty page, using type :page in first invocation" do
177
+ @page.canvas.line_width = 10
178
+ assert_operators(@page, [[:set_line_width, [10]]])
179
+
180
+ @page.canvas(type: :overlay).line_width = 5
181
+ assert_operators(@page, [[:save_graphics_state], [:restore_graphics_state],
182
+ [:save_graphics_state], [:set_line_width, [10]],
183
+ [:restore_graphics_state], [:set_line_width, [5]]])
184
+
185
+ @page.canvas(type: :underlay).line_width = 2
186
+ assert_operators(@page, [[:save_graphics_state], [:set_line_width, [2]],
187
+ [:restore_graphics_state], [:save_graphics_state],
188
+ [:set_line_width, [10]],
189
+ [:restore_graphics_state], [:set_line_width, [5]]])
190
+ end
191
+
192
+ it "works correctly if invoked on an empty page, using type :underlay in first invocation" do
193
+ @page.canvas(type: :underlay).line_width = 2
194
+ assert_operators(@page, [[:save_graphics_state], [:set_line_width, [2]],
195
+ [:restore_graphics_state], [:save_graphics_state],
196
+ [:restore_graphics_state]])
197
+
198
+ @page.canvas.line_width = 10
199
+ assert_operators(@page, [[:save_graphics_state], [:set_line_width, [2]],
200
+ [:restore_graphics_state], [:save_graphics_state],
201
+ [:set_line_width, [10]], [:restore_graphics_state]])
202
+
203
+ @page.canvas(type: :overlay).line_width = 5
204
+ assert_operators(@page, [[:save_graphics_state], [:set_line_width, [2]],
205
+ [:restore_graphics_state], [:save_graphics_state],
206
+ [:set_line_width, [10]],
207
+ [:restore_graphics_state], [:set_line_width, [5]]])
208
+ end
209
+
210
+ it "works correctly if invoked on a page with existing contents" do
211
+ @page.contents = "10 w"
212
+
213
+ @page.canvas(type: :overlay).line_width = 5
214
+ assert_operators(@page, [[:save_graphics_state], [:restore_graphics_state],
215
+ [:save_graphics_state], [:set_line_width, [10]],
216
+ [:restore_graphics_state], [:set_line_width, [5]]])
217
+
218
+ @page.canvas(type: :underlay).line_width = 2
219
+ assert_operators(@page, [[:save_graphics_state], [:set_line_width, [2]],
220
+ [:restore_graphics_state], [:save_graphics_state],
221
+ [:set_line_width, [10]],
222
+ [:restore_graphics_state], [:set_line_width, [5]]])
223
+ end
224
+
225
+ it "fails if the page canvas is requested for a page with existing contents" do
226
+ @page.contents = "q Q"
227
+ assert_raises(HexaPDF::Error) { @page.canvas }
228
+ end
229
+
230
+ it "fails if called with an incorrect type argument" do
231
+ assert_raises(ArgumentError) { @page.canvas(type: :something) }
232
+ end
233
+ end
234
+
235
+ describe "to_form_xobject" do
236
+ it "creates an independent form xobject" do
237
+ page = @doc.pages.add_page
238
+ page.contents = "test"
239
+ form = page.to_form_xobject
240
+ refute(form.indirect?)
241
+ assert_equal(form.box.value, page.box.value)
242
+ end
243
+
244
+ it "works for pages without content" do
245
+ page = @doc.pages.add_page
246
+ form = page.to_form_xobject
247
+ assert_equal('', form.stream)
248
+ end
249
+
250
+ it "uses the raw stream data if possible to avoid unnecessary work" do
251
+ page = @doc.pages.add_page
252
+ page.contents = HexaPDF::StreamData.new(StringIO.new("test"))
253
+ form = page.to_form_xobject
254
+ assert_same(form.raw_stream, page[:Contents].raw_stream)
255
+
256
+ form = page.to_form_xobject(reference: false)
257
+ refute_same(form.raw_stream, page[:Contents].raw_stream)
258
+ end
259
+ end
260
+ end
@@ -0,0 +1,255 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ require 'test_helper'
4
+ require 'hexapdf/document'
5
+ require 'hexapdf/type/page_tree_node'
6
+
7
+ describe HexaPDF::Type::PageTreeNode do
8
+ before do
9
+ @doc = HexaPDF::Document.new
10
+ @root = @doc.add(Type: :Pages)
11
+ end
12
+
13
+ # Defines the following page tree:
14
+ #
15
+ # @root
16
+ # @kid1
17
+ # @kid11
18
+ # @pages[0]
19
+ # @pages[1]
20
+ # @kid12
21
+ # @pages[2]
22
+ # @pages[3]
23
+ # @pages[4]
24
+ # @pages[5]
25
+ # @kid2
26
+ # @pages[6]
27
+ # @pages[7]
28
+ def define_multilevel_page_tree
29
+ @pages = 8.times.map { @doc.add(Type: :Page) }
30
+ @kid1 = @doc.add(Type: :Pages, Parent: @root, Count: 5)
31
+ @kid11 = @doc.add(Type: :Pages, Parent: @kid1)
32
+ @kid11.add_page(@pages[0])
33
+ @kid11.add_page(@pages[1])
34
+ @kid12 = @doc.add(Type: :Pages, Parent: @kid1)
35
+ @kid12.add_page(@pages[2])
36
+ @kid12.add_page(@pages[3])
37
+ @kid12.add_page(@pages[4])
38
+ @kid1[:Kids] << @kid11 << @kid12
39
+ @root[:Kids] << @kid1
40
+
41
+ @pages[5][:Parent] = @root
42
+ @root[:Kids] << @pages[5]
43
+
44
+ @kid2 = @doc.add(Type: :Pages, Parent: @root)
45
+ @kid2.add_page(@pages[6])
46
+ @kid2.add_page(@pages[7])
47
+ @root[:Kids] << @kid2
48
+ @root[:Count] = 8
49
+ end
50
+
51
+ it "must always be indirect" do
52
+ pages = @doc.add(Type: :Pages)
53
+ pages.must_be_indirect = false
54
+ assert(pages.must_be_indirect?)
55
+ end
56
+
57
+ describe "page" do
58
+ before do
59
+ define_multilevel_page_tree
60
+ end
61
+
62
+ it "returns the page for a given index" do
63
+ assert_equal(@pages[0], @root.page(0))
64
+ assert_equal(@pages[3], @root.page(3))
65
+ assert_equal(@pages[5], @root.page(5))
66
+ assert_equal(@pages[7], @root.page(7))
67
+ end
68
+
69
+ it "works with negative indicies counting backwards from the end" do
70
+ assert_equal(@pages[0], @root.page(-8))
71
+ assert_equal(@pages[3], @root.page(-5))
72
+ assert_equal(@pages[5], @root.page(-3))
73
+ assert_equal(@pages[7], @root.page(-1))
74
+ end
75
+
76
+ it "returns nil for bad indices" do
77
+ assert_nil(@root.page(20))
78
+ assert_nil(@root.page(-20))
79
+ end
80
+ end
81
+
82
+ describe "insert_page" do
83
+ it "uses an empty new page when none is provided" do
84
+ page = @root.insert_page(3)
85
+ assert_equal([page], @root[:Kids])
86
+ assert_equal(1, @root.page_count)
87
+ assert_equal(:Page, page[:Type])
88
+ assert_equal(@root, page[:Parent])
89
+ assert_kind_of(HexaPDF::Rectangle, page[:MediaBox])
90
+ assert_equal({}, page[:Resources].value)
91
+ refute(@root.value.key?(:Parent))
92
+ end
93
+
94
+ it "doesn't create a /Resources entry if an inherited one exists" do
95
+ @root[:Resources] = {Font: {F1: nil}}
96
+ page = @root.insert_page(3)
97
+ assert_equal(@root[:Resources], page[:Resources])
98
+ end
99
+
100
+ it "inserts the provided page at the given index" do
101
+ page = @doc.wrap(Type: :Page)
102
+ assert_equal(page, @root.insert_page(3, page))
103
+ assert_equal([page], @root[:Kids])
104
+ assert_equal(@root, page[:Parent])
105
+ refute(@root.value.key?(:Parent))
106
+ end
107
+
108
+ it "inserts multiple pages correctly in an empty root node" do
109
+ page3 = @root.insert_page(5)
110
+ page1 = @root.insert_page(0)
111
+ page2 = @root.insert_page(1)
112
+ assert_equal([page1, page2, page3], @root[:Kids])
113
+ assert_equal(3, @root.page_count)
114
+ end
115
+
116
+ it "inserts multiple pages correctly in a multilevel page tree" do
117
+ define_multilevel_page_tree
118
+ page = @root.insert_page(2)
119
+ assert_equal([@pages[0], @pages[1], page], @kid11[:Kids])
120
+ assert_equal(3, @kid11.page_count)
121
+ assert_equal(6, @kid1.page_count)
122
+ assert_equal(9, @root.page_count)
123
+
124
+ page = @root.insert_page(4)
125
+ assert_equal([@pages[2], page, @pages[3], @pages[4]], @kid12[:Kids])
126
+ assert_equal(4, @kid12.page_count)
127
+ assert_equal(7, @kid1.page_count)
128
+ assert_equal(10, @root.page_count)
129
+
130
+ page = @root.insert_page(8)
131
+ assert_equal([@kid1, @pages[5], page, @kid2], @root[:Kids])
132
+ assert_equal(11, @root.page_count)
133
+
134
+ page = @root.insert_page(100)
135
+ assert_equal([@kid1, @pages[5], @root[:Kids][2], @kid2, page], @root[:Kids])
136
+ assert_equal(12, @root.page_count)
137
+ end
138
+
139
+ it "allows negative indices to be specified" do
140
+ define_multilevel_page_tree
141
+ page = @root.insert_page(-1)
142
+ assert_equal(page, @root[:Kids].last)
143
+
144
+ page = @root.insert_page(-4)
145
+ assert_equal(page, @root[:Kids][2])
146
+ end
147
+ end
148
+
149
+ describe "delete_page" do
150
+ before do
151
+ define_multilevel_page_tree
152
+ end
153
+
154
+ it "does nothing if the page index is not valid" do
155
+ assert_nil(@root.delete_page(20))
156
+ assert_nil(@root.delete_page(-20))
157
+ assert_equal(8, @root.page_count)
158
+ end
159
+
160
+ it "deletes the correct page" do
161
+ assert_equal(@pages[2], @root.delete_page(2))
162
+ assert_equal(2, @kid12.page_count)
163
+ assert_equal(4, @kid1.page_count)
164
+ assert_equal(7, @root.page_count)
165
+
166
+ assert_equal(@pages[5], @root.delete_page(4))
167
+ assert_equal(6, @root.page_count)
168
+ end
169
+
170
+ it "deletes intermediate page tree nodes if they contain only one child after deletion" do
171
+ assert_equal(@pages[0], @root.delete_page(0))
172
+ assert_equal(4, @kid1.page_count)
173
+ assert_equal(7, @root.page_count)
174
+ assert_nil(@doc.object(@kid11).value)
175
+ assert_equal(@pages[1], @kid1[:Kids][0])
176
+ end
177
+
178
+ it "deletes intermediate page tree nodes if they don't have any children after deletion" do
179
+ node = @doc.add(Type: :Pages, Parent: @root)
180
+ page = node.add_page
181
+ @root[:Kids] << node
182
+ @root[:Count] += 1
183
+
184
+ assert_equal(page, @root.delete_page(-1))
185
+ assert_nil(@doc.object(node).value)
186
+ refute_equal(node, @root[:Kids].last)
187
+ assert(8, @root.page_count)
188
+ end
189
+ end
190
+
191
+ describe "each_page" do
192
+ before do
193
+ define_multilevel_page_tree
194
+ end
195
+
196
+ it "iterates over a simple, one-level page tree" do
197
+ assert_equal([@pages[2], @pages[3], @pages[4]], @kid12.each_page.to_a)
198
+ end
199
+
200
+ it "iterates over a multilevel page tree" do
201
+ assert_equal(@pages, @root.each_page.to_a)
202
+ end
203
+ end
204
+
205
+ describe "validation" do
206
+ it "corrects faulty /Count entries" do
207
+ define_multilevel_page_tree
208
+ root_count = @root.page_count
209
+ @root[:Count] = -5
210
+ kid_count = @kid12.page_count
211
+ @kid12[:Count] = 100
212
+
213
+ called_msg = ''
214
+ refute(@root.validate(auto_correct: false) {|msg, _| called_msg = msg})
215
+ assert_match(/Count.*invalid/, called_msg)
216
+
217
+ assert(@root.validate)
218
+ assert_equal(root_count, @root.page_count)
219
+ assert_equal(kid_count, @kid12.page_count)
220
+ end
221
+
222
+ it "corrects faulty /Parent entries" do
223
+ define_multilevel_page_tree
224
+ @kid12.delete(:Parent)
225
+ @kid2.delete(:Parent)
226
+
227
+ called_msg = ''
228
+ refute(@root.validate(auto_correct: false) {|msg, _| called_msg = msg})
229
+ assert_match(/Parent.*invalid/, called_msg)
230
+
231
+ assert(@root.validate)
232
+ assert_equal(@kid1, @kid12[:Parent])
233
+ assert_equal(@root, @kid2[:Parent])
234
+ end
235
+
236
+ it "removes invalid objects from the page tree (like null objects)" do
237
+ define_multilevel_page_tree
238
+ assert(@root.validate(auto_correct: false) {|m, _| p m})
239
+
240
+ @doc.delete(@pages[3])
241
+ refute(@root.validate(auto_correct: false)) do |msg, _|
242
+ assert_match(/invalid object/i, msg)
243
+ end
244
+ assert(@root.validate)
245
+ assert_equal(2, @kid12[:Count])
246
+ assert_equal([@pages[2], @pages[4]], @kid12[:Kids])
247
+ end
248
+
249
+ it "needs at least one page node" do
250
+ refute(@root.validate(auto_correct: false))
251
+ assert(@root.validate)
252
+ assert_equal(1, @root.page_count)
253
+ end
254
+ end
255
+ end