hexapdf 0.1.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 (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,394 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ require 'test_helper'
4
+ require 'hexapdf/document'
5
+ require 'hexapdf/parser'
6
+ require 'stringio'
7
+
8
+ describe HexaPDF::Parser do
9
+ before do
10
+ @document = HexaPDF::Document.new
11
+ @document.add(@document.wrap(10, oid: 1, gen: 0))
12
+
13
+ create_parser(<<EOF)
14
+ %PDF-1.7
15
+
16
+ 1 0 obj
17
+ 10
18
+ endobj
19
+
20
+ 2 0 obj
21
+ [ 5 6 <</Length 10 >> (name) <4E6F762073 686D6F7A20 6B612070
22
+ 6F702E>]
23
+ endobj
24
+
25
+ 3 15 obj<< /Length 1 0 R/Hallo 6/Filter /Fl/DecodeParms<<>> >>stream
26
+ Hallo PDF!endstream
27
+ endobj
28
+
29
+ 4 0 obj
30
+ <</Type /XRef /Length 3 /W [1 1 1] /Index [1 1] /Size 2 >> stream
31
+ \x01\x0A\x00
32
+ endstream
33
+ endobj
34
+
35
+ xref
36
+ 0 4
37
+ 0000000000 65535 f
38
+ 0000000010 00000 n
39
+ 0000000029 00000 n
40
+ 0000000000 65535 f
41
+ 3 1
42
+ 0000000556 00000 n
43
+ trailer
44
+ << /Test (now) >>
45
+ startxref
46
+ 308
47
+ %%EOF
48
+ EOF
49
+ end
50
+
51
+ def create_parser(str)
52
+ @parser = HexaPDF::Parser.new(StringIO.new(str), @document)
53
+ end
54
+
55
+ describe "parse_indirect_object" do
56
+ it "reads indirect objects sequentially" do
57
+ object, oid, gen, stream = @parser.parse_indirect_object
58
+ assert_equal(1, oid)
59
+ assert_equal(0, gen)
60
+ assert_equal(10, object)
61
+ assert_nil(stream)
62
+
63
+ object, oid, gen, stream = @parser.parse_indirect_object
64
+ assert_equal(2, oid)
65
+ assert_equal(0, gen)
66
+ assert_equal([5, 6, {Length: 10}, "name", "Nov shmoz ka pop."], object)
67
+ assert_nil(stream)
68
+
69
+ object, oid, gen, stream = @parser.parse_indirect_object
70
+ assert_equal(3, oid)
71
+ assert_equal(15, gen)
72
+ assert_kind_of(HexaPDF::StreamData, stream)
73
+ assert_equal([:Fl], stream.filter)
74
+ assert_equal([{}], stream.decode_parms)
75
+ assert_equal({Length: 10, Hallo: 6, Filter: :Fl, DecodeParms: {}}, object)
76
+ end
77
+
78
+ it "handles empty indirect objects by using PDF null for them" do
79
+ create_parser("1 0 obj\nendobj")
80
+ object, * = @parser.parse_indirect_object
81
+ assert_nil(object)
82
+ end
83
+
84
+ it "handles keyword stream followed only by CR without LF" do
85
+ create_parser("1 0 obj<</Length 2>> stream\r12\nendstream endobj")
86
+ *, stream = @parser.parse_indirect_object
87
+ assert_equal('12', TestHelper.collector(stream.fiber))
88
+ end
89
+
90
+ it "recovers from an invalid stream length value" do
91
+ create_parser("1 0 obj<</Length 4>> stream\n12endstream endobj")
92
+ obj, _, _, stream = @parser.parse_indirect_object
93
+ assert_equal(2, obj[:Length])
94
+ assert_equal('12', TestHelper.collector(stream.fiber))
95
+ end
96
+
97
+ it "works even if the keyword endobj is missing or mangled" do
98
+ create_parser("1 0 obj<</Length 4>>5")
99
+ object, * = @parser.parse_indirect_object
100
+ assert_equal({Length: 4}, object)
101
+ create_parser("1 0 obj<</Length 4>>endobjk")
102
+ object, * = @parser.parse_indirect_object
103
+ assert_equal({Length: 4}, object)
104
+ end
105
+
106
+ it "fails if the oid, gen or 'obj' keyword is invalid" do
107
+ create_parser("a 0 obj\n5\nendobj")
108
+ exp = assert_raises(HexaPDF::MalformedPDFError) { @parser.parse_indirect_object }
109
+ assert_match(/No valid object/, exp.message)
110
+ create_parser("1 a obj\n5\nendobj")
111
+ exp = assert_raises(HexaPDF::MalformedPDFError) { @parser.parse_indirect_object }
112
+ assert_match(/No valid object/, exp.message)
113
+ create_parser("1 0 dobj\n5\nendobj")
114
+ exp = assert_raises(HexaPDF::MalformedPDFError) { @parser.parse_indirect_object }
115
+ assert_match(/No valid object/, exp.message)
116
+ end
117
+
118
+ it "fails if the value of a stream is not a dictionary" do
119
+ create_parser("1 0 obj\n(fail)\nstream\nendstream\nendobj\n")
120
+ exp = assert_raises(HexaPDF::MalformedPDFError) { @parser.parse_indirect_object }
121
+ assert_match(/stream.*dictionary/, exp.message)
122
+ end
123
+
124
+ it "fails if the 'stream' keyword isn't followed by EOL" do
125
+ create_parser("1 0 obj\n<< >>\nstream endstream\nendobj\n")
126
+ exp = assert_raises(HexaPDF::MalformedPDFError) { @parser.parse_indirect_object(0) }
127
+ assert_match(/stream.*followed by LF/, exp.message)
128
+ end
129
+
130
+ it "fails if the 'endstream' keyword is missing" do
131
+ create_parser("1 0 obj\n<< >>\nstream\nendobj\n")
132
+ exp = assert_raises(HexaPDF::MalformedPDFError) { @parser.parse_indirect_object(0) }
133
+ assert_match(/stream.*followed by.*endstream/i, exp.message)
134
+ end
135
+ end
136
+
137
+ describe "load_object" do
138
+ before do
139
+ @entry = HexaPDF::XRefSection.in_use_entry(2, 0, 29)
140
+ end
141
+
142
+ it "can load an indirect object" do
143
+ obj = @parser.load_object(@entry)
144
+ assert_kind_of(HexaPDF::Object, obj)
145
+ assert_equal(5, obj.value[0])
146
+ assert_equal(2, obj.oid)
147
+ assert_equal(0, obj.gen)
148
+ end
149
+
150
+ it "can load a free object" do
151
+ obj = @parser.load_object(HexaPDF::XRefSection.free_entry(0, 0))
152
+ assert_kind_of(HexaPDF::Object, obj)
153
+ assert_nil(obj.value)
154
+ end
155
+
156
+ it "can load a compressed object" do
157
+ def (@document).object(_oid)
158
+ obj = Object.new
159
+ def obj.parse_stream
160
+ HexaPDF::Type::ObjectStream::Data.new("5 [1 2]", [1, 2], [0, 2])
161
+ end
162
+ obj
163
+ end
164
+
165
+ obj = @parser.load_object(HexaPDF::XRefSection.compressed_entry(2, 3, 1))
166
+ assert_kind_of(HexaPDF::Object, obj)
167
+ assert_equal([1, 2], obj.value)
168
+ end
169
+
170
+ it "fails if another object is found instead of an object stream" do
171
+ def (@document).object(_oid)
172
+ :invalid
173
+ end
174
+ exp = assert_raises(HexaPDF::MalformedPDFError) do
175
+ @parser.load_object(HexaPDF::XRefSection.compressed_entry(2, 1, 1))
176
+ end
177
+ assert_match(/not an object stream/, exp.message)
178
+ end
179
+
180
+ it "fails if the xref entry type is invalid" do
181
+ exp = assert_raises(HexaPDF::MalformedPDFError) do
182
+ @parser.load_object(HexaPDF::XRefSection::Entry.new(:invalid))
183
+ end
184
+ assert_match(/invalid cross-reference type/i, exp.message)
185
+ end
186
+
187
+ it "fails if the object/generation numbers don't match" do
188
+ exp = assert_raises(HexaPDF::MalformedPDFError) do
189
+ @entry.gen = 2
190
+ @parser.load_object(@entry)
191
+ end
192
+ assert_match(/oid,gen.*don't match/, exp.message)
193
+ end
194
+ end
195
+
196
+ describe "startxref_offset" do
197
+ it "returns the correct offset" do
198
+ assert_equal(308, @parser.startxref_offset)
199
+ end
200
+
201
+ it "ignores garbage at the end of the file" do
202
+ create_parser("startxref\n5\n%%EOF" + "\nhallo" * 150)
203
+ assert_equal(5, @parser.startxref_offset)
204
+ end
205
+
206
+ it "uses the last startxref if there are more than one" do
207
+ create_parser("startxref\n5\n%%EOF\n\nsome garbage\n\nstartxref\n555\n%%EOF\n")
208
+ assert_equal(555, @parser.startxref_offset)
209
+ end
210
+
211
+ it "finds the startxref anywhere in file" do
212
+ create_parser("startxref\n5\n%%EOF" + "\nhallo" * 5000)
213
+ assert_equal(5, @parser.startxref_offset)
214
+ create_parser("startxref\n5\n%%EOF\n" + "h" * 1017)
215
+ assert_equal(5, @parser.startxref_offset)
216
+ end
217
+
218
+ it "fails even in big files when nothing is found" do
219
+ create_parser("\nhallo" * 5000)
220
+ exp = assert_raises(HexaPDF::MalformedPDFError) { @parser.startxref_offset }
221
+ assert_match(/end-of-file marker not found/, exp.message)
222
+ end
223
+
224
+ it "fails if the %%EOF marker is missing" do
225
+ create_parser("startxref\n5")
226
+ exp = assert_raises(HexaPDF::MalformedPDFError) { @parser.startxref_offset }
227
+ assert_match(/end-of-file marker not found/, exp.message)
228
+ end
229
+
230
+ it "fails if the startxref keyword is missing" do
231
+ create_parser("somexref\n5\n%%EOF")
232
+ exp = assert_raises(HexaPDF::MalformedPDFError) { @parser.startxref_offset }
233
+ assert_match(/missing startxref/, exp.message)
234
+ end
235
+ end
236
+
237
+ describe "file_header_version" do
238
+ it "returns the correct version" do
239
+ assert_equal('1.7', @parser.file_header_version)
240
+ end
241
+
242
+ it "fails if the header is mangled" do
243
+ create_parser("%PDF-1\n")
244
+ exp = assert_raises(HexaPDF::MalformedPDFError) { @parser.file_header_version }
245
+ assert_match(/file header/, exp.message)
246
+ end
247
+
248
+ it "ignores junk at the beginning of the file and correctly calculates offset" do
249
+ create_parser("junk" * 200 + "\n%PDF-1.4\n")
250
+ assert_equal('1.4', @parser.file_header_version)
251
+ assert_equal(801, @parser.instance_variable_get(:@header_offset))
252
+ end
253
+ end
254
+
255
+ it "xref_section?" do
256
+ assert(@parser.xref_section?(@parser.startxref_offset))
257
+ refute(@parser.xref_section?(53))
258
+ end
259
+
260
+ describe "parse_xref_section_and_trailer" do
261
+ it "works on a section with multiple sub sections" do
262
+ section, trailer = @parser.parse_xref_section_and_trailer(@parser.startxref_offset)
263
+ assert_equal({Test: 'now'}, trailer)
264
+ assert_equal(HexaPDF::XRefSection.free_entry(0, 65535), section[0, 65535])
265
+ assert_equal(HexaPDF::XRefSection.free_entry(3, 65535), section[3, 65535])
266
+ assert_equal(HexaPDF::XRefSection.in_use_entry(1, 0, 10), section[1])
267
+ end
268
+
269
+ it "works for an empty section" do
270
+ create_parser("xref\n0 0\ntrailer\n<</Name /Value >>\n")
271
+ _, trailer = @parser.parse_xref_section_and_trailer(0)
272
+ assert_equal({Name: :Value}, trailer)
273
+ end
274
+
275
+ it "handles xref type=n with offset=0" do
276
+ create_parser("xref\n0 2\n0000000000 00000 n \n0000000000 00000 n \ntrailer\n<<>>\n")
277
+ section, _trailer = @parser.parse_xref_section_and_trailer(0)
278
+ assert_equal(HexaPDF::XRefSection.free_entry(1, 0), section[1])
279
+ end
280
+
281
+ it "handles xref type=n with gen>65535" do
282
+ create_parser("xref\n0 2\n0000000000 00000 n \n0000000000 65536 n \ntrailer\n<<>>\n")
283
+ section, _trailer = @parser.parse_xref_section_and_trailer(0)
284
+ assert_equal(HexaPDF::XRefSection.free_entry(1, 65536), section[1])
285
+ end
286
+
287
+ it "fails if the xref keyword is missing/mangled" do
288
+ create_parser("xTEf\n0 d\n0000000000 00000 n \ntrailer\n<< >>\n")
289
+ exp = assert_raises(HexaPDF::MalformedPDFError) { @parser.parse_xref_section_and_trailer(0) }
290
+ assert_match(/keyword xref/, exp.message)
291
+ end
292
+
293
+ it "fails if a sub section header is mangled" do
294
+ create_parser("xref\n0 d\n0000000000 00000 n \ntrailer\n<< >>\n")
295
+ exp = assert_raises(HexaPDF::MalformedPDFError) { @parser.parse_xref_section_and_trailer(0) }
296
+ assert_match(/invalid cross-reference subsection/i, exp.message)
297
+ end
298
+
299
+ it "fails if there is no trailer" do
300
+ create_parser("xref\n0 1\n0000000000 00000 n \n")
301
+ exp = assert_raises(HexaPDF::MalformedPDFError) { @parser.parse_xref_section_and_trailer(0) }
302
+ assert_match(/keyword trailer/i, exp.message)
303
+ end
304
+
305
+ it "fails if the trailer is not a PDF dictionary" do
306
+ create_parser("xref\n0 1\n0000000000 00000 n \ntrailer\n(base)")
307
+ exp = assert_raises(HexaPDF::MalformedPDFError) { @parser.parse_xref_section_and_trailer(0) }
308
+ assert_match(/dictionary/, exp.message)
309
+ end
310
+ end
311
+
312
+ describe "load_revision" do
313
+ it "works for a simple cross-reference section" do
314
+ xref_section, trailer = @parser.load_revision(@parser.startxref_offset)
315
+ assert_equal({Test: 'now'}, trailer)
316
+ assert(xref_section[1].in_use?)
317
+ end
318
+
319
+ it "works for a cross-reference stream" do
320
+ xref_section, trailer = @parser.load_revision(212)
321
+ assert_equal({Size: 2}, trailer)
322
+ assert(xref_section[1].in_use?)
323
+ end
324
+
325
+ it "fails if another object is found instead of a cross-reference stream" do
326
+ exp = assert_raises(HexaPDF::MalformedPDFError) { @parser.load_revision(10) }
327
+ assert_match(/not a cross-reference stream/, exp.message)
328
+ end
329
+ end
330
+
331
+ describe "with strict parsing enabled" do
332
+ before do
333
+ @document.config['parser.on_correctable_error'] = proc { true }
334
+ end
335
+
336
+ it "startxref_offset fails if the startxref is not in the last part of the file" do
337
+ create_parser("startxref\n5\n%%EOF" + "\nhallo" * 5000)
338
+ exp = assert_raises(HexaPDF::MalformedPDFError) { @parser.startxref_offset }
339
+ assert_match(/end-of-file marker not found/, exp.message)
340
+ end
341
+
342
+ it "parse_xref_section_and_trailer fails if xref type=n with offset=0" do
343
+ create_parser("xref\n0 2\n0000000000 00000 n \n0000000000 00000 n \ntrailer\n<<>>\n")
344
+ exp = assert_raises(HexaPDF::MalformedPDFError) { @parser.parse_xref_section_and_trailer(0) }
345
+ assert_match(/invalid.*cross-reference entry/i, exp.message)
346
+ end
347
+
348
+ it "parse_xref_section_and_trailer fails xref type=n with gen>65535" do
349
+ create_parser("xref\n0 2\n0000000000 00000 n \n0000000000 65536 n \ntrailer\n<<>>\n")
350
+ exp = assert_raises(HexaPDF::MalformedPDFError) { @parser.parse_xref_section_and_trailer(0) }
351
+ assert_match(/invalid.*cross-reference entry/i, exp.message)
352
+ end
353
+
354
+ it "parse_indirect_object fails if an empty indirect object is found" do
355
+ create_parser("1 0 obj\nendobj")
356
+ exp = assert_raises(HexaPDF::MalformedPDFError) { @parser.parse_indirect_object }
357
+ assert_match(/no indirect object value/i, exp.message)
358
+ end
359
+
360
+ it "parse_indirect_object fails if keyword stream is followed only by CR without LF" do
361
+ create_parser("1 0 obj<</Length 2>> stream\r12\nendstream endobj")
362
+ exp = assert_raises(HexaPDF::MalformedPDFError) { @parser.parse_indirect_object }
363
+ assert_match(/not CR alone/, exp.message)
364
+ end
365
+
366
+ it "parse_indirect_object fails if the stream length value is invalid" do
367
+ create_parser("1 0 obj<</Length 4>> stream\n12endstream endobj")
368
+ exp = assert_raises(HexaPDF::MalformedPDFError) { @parser.parse_indirect_object }
369
+ assert_match(/invalid stream length/i, exp.message)
370
+ end
371
+
372
+ it "parse_indirect_object fails if the keyword endobj is missing or mangled" do
373
+ create_parser("1 0 obj\n<< >>\nendobjd\n")
374
+ exp = assert_raises(HexaPDF::MalformedPDFError) { @parser.parse_indirect_object }
375
+ assert_match(/keyword endobj/, exp.message)
376
+ create_parser("1 0 obj\n<< >>")
377
+ exp = assert_raises(HexaPDF::MalformedPDFError) { @parser.parse_indirect_object }
378
+ assert_match(/keyword endobj/, exp.message)
379
+ end
380
+
381
+ it "parse_indirect_object fails if there is data between 'endstream' and 'endobj'" do
382
+ create_parser("1 0 obj\n<< >>\nstream\nendstream\ntest\nendobj\n")
383
+ exp = assert_raises(HexaPDF::MalformedPDFError) { @parser.parse_indirect_object(0) }
384
+ assert_match(/keyword endobj/, exp.message)
385
+ end
386
+
387
+ it "load_revision fails if the cross-reference stream doesn't contain an entry for itself" do
388
+ create_parser("2 0 obj\n<</Type/XRef/Length 3/W [1 1 1]/Size 1>>" \
389
+ "stream\n\x01\x0A\x00\nendstream endobj")
390
+ exp = assert_raises(HexaPDF::MalformedPDFError) { @parser.load_revision(0) }
391
+ assert_match(/entry for itself/, exp.message)
392
+ end
393
+ end
394
+ end
@@ -0,0 +1,36 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ require 'test_helper'
4
+ require 'hexapdf/rectangle'
5
+
6
+ describe HexaPDF::Rectangle do
7
+ describe "after_data_change" do
8
+ it "fails if the value is not a array" do
9
+ assert_raises(ArgumentError) { HexaPDF::Rectangle.new(:Name) }
10
+ end
11
+
12
+ it "normalizes the array values" do
13
+ rect = HexaPDF::Rectangle.new([0, 1, 2, 3])
14
+ assert_equal([0, 1, 2, 3], rect.value)
15
+
16
+ rect = HexaPDF::Rectangle.new([2, 3, 0, 1])
17
+ assert_equal([0, 1, 2, 3], rect.value)
18
+
19
+ rect = HexaPDF::Rectangle.new([0, 3, 2, 1])
20
+ assert_equal([0, 1, 2, 3], rect.value)
21
+
22
+ rect = HexaPDF::Rectangle.new([2, 1, 0, 3])
23
+ assert_equal([0, 1, 2, 3], rect.value)
24
+ end
25
+ end
26
+
27
+ it "returns individual fields of the rectangle" do
28
+ rect = HexaPDF::Rectangle.new([2, 1, 0, 5])
29
+ assert_equal(0, rect.left)
30
+ assert_equal(2, rect.right)
31
+ assert_equal(1, rect.bottom)
32
+ assert_equal(5, rect.top)
33
+ assert_equal(2, rect.width)
34
+ assert_equal(4, rect.height)
35
+ end
36
+ end
@@ -0,0 +1,41 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ require 'test_helper'
4
+ require 'hexapdf/reference'
5
+
6
+ describe HexaPDF::Reference do
7
+ it "correctly assigns oid and gen on initialization" do
8
+ r = HexaPDF::Reference.new(5, 7)
9
+ assert_equal(5, r.oid)
10
+ assert_equal(7, r.gen)
11
+ end
12
+
13
+ it "raises an error when invalid objects are supplied on initialization" do
14
+ assert_raises(ArgumentError) { HexaPDF::Reference.new('a', 7) }
15
+ assert_raises(ArgumentError) { HexaPDF::Reference.new(5, 'b') }
16
+ end
17
+
18
+ it "is sortable" do
19
+ assert_equal([HexaPDF::Reference.new(1, 0), HexaPDF::Reference.new(1, 1),
20
+ HexaPDF::Reference.new(5, 7)],
21
+ [HexaPDF::Reference.new(5, 7), HexaPDF::Reference.new(1, 1),
22
+ HexaPDF::Reference.new(1, 0)].sort)
23
+ end
24
+
25
+ it "is comparable to itself" do
26
+ assert_equal(HexaPDF::Reference.new(5, 7), HexaPDF::Reference.new(5, 7))
27
+ refute_equal(HexaPDF::Reference.new(5, 7), HexaPDF::Reference.new(5, 8))
28
+ refute_equal(HexaPDF::Reference.new(5, 7), HexaPDF::Reference.new(4, 7))
29
+ end
30
+
31
+ it "behaves correctly as hash key" do
32
+ h = {}
33
+ h[HexaPDF::Reference.new(5, 7)] = true
34
+ assert(h.key?(HexaPDF::Reference.new(5, 7)))
35
+ refute(h.key?(HexaPDF::Reference.new(5, 8)))
36
+ end
37
+
38
+ it "shows oid and gen on inspection" do
39
+ assert_match(/\[5, 7\]/, HexaPDF::Reference.new(5, 7).inspect)
40
+ end
41
+ end
@@ -0,0 +1,139 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ require 'test_helper'
4
+ require 'hexapdf/revision'
5
+ require 'hexapdf/object'
6
+ require 'hexapdf/reference'
7
+ require 'hexapdf/xref_section'
8
+ require 'stringio'
9
+
10
+ describe HexaPDF::Revision do
11
+ before do
12
+ @xref_section = HexaPDF::XRefSection.new
13
+ @xref_section.add_in_use_entry(2, 0, 5000)
14
+ @xref_section.add_free_entry(3, 0)
15
+ @obj = HexaPDF::Object.new(:val, oid: 1, gen: 0)
16
+ @ref = HexaPDF::Reference.new(1, 0)
17
+
18
+ @loader = lambda do |entry|
19
+ if entry.type == :free
20
+ HexaPDF::Object.new(nil, oid: entry.oid, gen: entry.gen)
21
+ else
22
+ HexaPDF::Object.new(:Test, oid: entry.oid, gen: entry.gen)
23
+ end
24
+ end
25
+ @rev = HexaPDF::Revision.new({}, xref_section: @xref_section, loader: @loader)
26
+ end
27
+
28
+ it "needs the trailer as first argument on initialization" do
29
+ rev = HexaPDF::Revision.new({})
30
+ assert_equal({}, rev.trailer)
31
+ end
32
+
33
+ it "takes an xref section and/or a parser on initialization" do
34
+ rev = HexaPDF::Revision.new({}, loader: @loader, xref_section: @xref_section)
35
+ assert_equal(:Test, rev.object(2).value)
36
+ end
37
+
38
+ it "returns the next free object number" do
39
+ assert_equal(4, @rev.next_free_oid)
40
+ @obj.oid = 4
41
+ @rev.add(@obj)
42
+ assert_equal(5, @rev.next_free_oid)
43
+ end
44
+
45
+ describe "add" do
46
+ it "works correctly" do
47
+ @rev.add(@obj)
48
+ assert(@rev.object?(@ref))
49
+ end
50
+
51
+ it "also returns the supplied object" do
52
+ assert_equal(@obj, @rev.add(@obj))
53
+ end
54
+
55
+ it "fails if the revision already has an object with the same object number" do
56
+ @rev.add(@obj)
57
+ assert_raises(HexaPDF::Error) { @rev.add(@obj) }
58
+ assert_raises(HexaPDF::Error) { @rev.add(HexaPDF::Object.new(:val, oid: 2)) }
59
+ end
60
+
61
+ it "fails if the given object has an object number of zero" do
62
+ assert_raises(HexaPDF::Error) { @rev.add(HexaPDF::Object.new(:val)) }
63
+ end
64
+ end
65
+
66
+ describe "object" do
67
+ it "returns nil if no object is found" do
68
+ assert_nil(@rev.object(@ref))
69
+ assert_nil(@rev.object(1))
70
+ end
71
+
72
+ it "returns an object that was added before" do
73
+ @rev.add(@obj)
74
+ assert_equal(@obj, @rev.object(@ref))
75
+ assert_equal(@obj, @rev.object(1))
76
+ end
77
+
78
+ it "loads an object that is defined in the cross-reference section" do
79
+ obj = @rev.object(HexaPDF::Reference.new(2, 0))
80
+ assert_equal(:Test, obj.value)
81
+ assert_equal(2, obj.oid)
82
+ assert_equal(0, obj.gen)
83
+ end
84
+
85
+ it "loads an object that is defined in the cross-reference section by using only the object number" do
86
+ obj = @rev.object(2)
87
+ refute_nil(obj)
88
+ end
89
+
90
+ it "loads free entries in the cross-reference section as special PDF null objects" do
91
+ obj = @rev.object(HexaPDF::Reference.new(3, 0))
92
+ assert_nil(obj.value)
93
+ end
94
+ end
95
+
96
+ describe "delete" do
97
+ before do
98
+ @rev.add(@obj)
99
+ end
100
+
101
+ it "deletes objects specified by reference" do
102
+ @rev.delete(@ref, mark_as_free: false)
103
+ refute(@rev.object?(@ref))
104
+ assert(@obj.null?)
105
+ end
106
+
107
+ it "deletes objects specified by object number" do
108
+ @rev.delete(@ref.oid, mark_as_free: false)
109
+ refute(@rev.object?(@ref.oid))
110
+ assert(@obj.null?)
111
+ end
112
+
113
+ it "marks the object as PDF null object when using mark_as_free=true" do
114
+ refute(@obj.null?)
115
+ @rev.delete(@ref)
116
+ assert(@rev.object(@ref).null?)
117
+ assert(@obj.null?)
118
+ end
119
+ end
120
+
121
+ describe "object iteration" do
122
+ it "iterates over all objects via each" do
123
+ @rev.add(@obj)
124
+ obj2 = @rev.object(2)
125
+ obj3 = @rev.object(3)
126
+ assert_equal([@obj, obj2, obj3], @rev.each.to_a)
127
+ end
128
+ end
129
+
130
+ it "works without a cross-reference section" do
131
+ rev = HexaPDF::Revision.new({})
132
+ rev.add(@obj)
133
+ assert_equal(@obj, rev.object(@ref))
134
+ assert(rev.object?(@ref))
135
+ assert_equal([@obj], rev.each.to_a)
136
+ rev.delete(@ref, mark_as_free: false)
137
+ refute(rev.object?(@ref))
138
+ end
139
+ end
@@ -0,0 +1,93 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ require 'test_helper'
4
+ require 'hexapdf/revisions'
5
+ require 'hexapdf/document'
6
+ require 'stringio'
7
+
8
+ describe HexaPDF::Revisions do
9
+ before do
10
+ @io = StringIO.new(<<EOF)
11
+ %PDF-1.7
12
+ 1 0 obj
13
+ 10
14
+ endobj
15
+
16
+ 2 0 obj
17
+ 20
18
+ endobj
19
+
20
+ xref
21
+ 0 3
22
+ 0000000000 65535 f
23
+ 0000000009 00000 n
24
+ 0000000028 00000 n
25
+ trailer
26
+ << /Size 3 >>
27
+ startxref
28
+ 47
29
+ %%EOF
30
+
31
+ 2 0 obj
32
+ 200
33
+ endobj
34
+
35
+ xref
36
+ 2 1
37
+ 0000000158 00000 n
38
+ trailer
39
+ << /Size 3 /Prev 47 >>
40
+ startxref
41
+ 178
42
+ %%EOF
43
+ EOF
44
+ @doc = HexaPDF::Document.new(io: @io)
45
+ @revisions = @doc.revisions
46
+ end
47
+
48
+ describe "add" do
49
+ it "adds an empty revision as the current revision" do
50
+ rev = @revisions.add
51
+ assert_equal({Size: 3}, rev.trailer.value)
52
+ assert_equal(rev, @revisions.current)
53
+ end
54
+ end
55
+
56
+ describe "delete_revision" do
57
+ it "allows deleting a revision by index" do
58
+ rev = @revisions.revision(0)
59
+ @revisions.delete(0)
60
+ refute(@revisions.any? {|r| r == rev})
61
+ end
62
+
63
+ it "allows deleting a revision by specifying a revision" do
64
+ rev = @revisions.revision(0)
65
+ @revisions.delete(rev)
66
+ refute(@revisions.any? {|r| r == rev})
67
+ end
68
+
69
+ it "fails when trying to delete the only existing revision" do
70
+ assert_raises(HexaPDF::Error) { @revisions.delete(0) while @revisions.current }
71
+ end
72
+ end
73
+
74
+ describe "merge" do
75
+ it "does nothing when only one revision is specified" do
76
+ @revisions.merge(1..1)
77
+ assert_equal(2, @revisions.each.to_a.size)
78
+ end
79
+
80
+ it "merges the higher into the the lower revision" do
81
+ @revisions.merge
82
+ assert_equal(1, @revisions.each.to_a.size)
83
+ assert_equal([10, 200], @revisions.current.each.to_a.sort.map(&:value))
84
+ end
85
+ end
86
+
87
+ describe "initialize" do
88
+ it "automatically loads all revisions from the underlying IO object" do
89
+ assert_equal(20, @revisions.revision(0).object(2).value)
90
+ assert_equal(200, @revisions[1].object(2).value)
91
+ end
92
+ end
93
+ end