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,589 @@
1
+ # -*- encoding: utf-8 -*-
2
+ #
3
+ #--
4
+ # This file is part of HexaPDF.
5
+ #
6
+ # HexaPDF - A Versatile PDF Creation and Manipulation Library For Ruby
7
+ # Copyright (C) 2016 Thomas Leitner
8
+ #
9
+ # HexaPDF is free software: you can redistribute it and/or modify it
10
+ # under the terms of the GNU Affero General Public License version 3 as
11
+ # published by the Free Software Foundation with the addition of the
12
+ # following permission added to Section 15 as permitted in Section 7(a):
13
+ # FOR ANY PART OF THE COVERED WORK IN WHICH THE COPYRIGHT IS OWNED BY
14
+ # THOMAS LEITNER, THOMAS LEITNER DISCLAIMS THE WARRANTY OF NON
15
+ # INFRINGEMENT OF THIRD PARTY RIGHTS.
16
+ #
17
+ # HexaPDF is distributed in the hope that it will be useful, but WITHOUT
18
+ # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
19
+ # FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
20
+ # License for more details.
21
+ #
22
+ # You should have received a copy of the GNU Affero General Public License
23
+ # along with HexaPDF. If not, see <http://www.gnu.org/licenses/>.
24
+ #
25
+ # The interactive user interfaces in modified source and object code
26
+ # versions of HexaPDF must display Appropriate Legal Notices, as required
27
+ # under Section 5 of the GNU Affero General Public License version 3.
28
+ #
29
+ # In accordance with Section 7(b) of the GNU Affero General Public
30
+ # License, a covered work must retain the producer line in every PDF that
31
+ # is created or manipulated using HexaPDF.
32
+ #++
33
+
34
+ require 'stringio'
35
+ require 'hexapdf/error'
36
+ require 'hexapdf/content'
37
+ require 'hexapdf/configuration'
38
+ require 'hexapdf/reference'
39
+ require 'hexapdf/object'
40
+ require 'hexapdf/stream'
41
+ require 'hexapdf/revisions'
42
+ require 'hexapdf/type'
43
+ require 'hexapdf/task'
44
+ require 'hexapdf/encryption'
45
+ require 'hexapdf/writer'
46
+ require 'hexapdf/importer'
47
+ require 'hexapdf/image_loader'
48
+ require 'hexapdf/document_utils'
49
+ require 'hexapdf/font_utils'
50
+
51
+
52
+ # == HexaPDF API Documentation
53
+ #
54
+ # Here are some pointers to more in depth information:
55
+ #
56
+ # * For information about the command line application, see the HexaPDF::CLI module.
57
+ # * HexaPDF::Document provides information about how to work with a PDF file.
58
+ # * HexaPDF::Content::Canvas provides the canvas API for drawing/writing on a page or form XObject
59
+ module HexaPDF
60
+
61
+ # Represents one PDF document.
62
+ #
63
+ # A PDF document consists of (indirect) objects, so the main job of this class is to provide
64
+ # methods for working with these objects. However, since a PDF document may also be
65
+ # incrementally updated and can therefore contain one or more revisions, there are also methods
66
+ # to work with these revisions.
67
+ #
68
+ # Note: This class provides everything to work on PDF documents on a low-level basis. This means
69
+ # that there are no convenience methods for higher PDF functionality whatsoever.
70
+ class Document
71
+
72
+ # :call-seq:
73
+ # Document.open(filename, **docargs) -> doc
74
+ # Document.open(filename, **docargs) {|doc| block} -> obj
75
+ #
76
+ # Creates a new PDF Document object for the given file.
77
+ #
78
+ # Depending on whether a block is provided, the functionality is different:
79
+ #
80
+ # * If no block is provided, the whole file is instantly read into memory and the PDF Document
81
+ # created for it is returned.
82
+ #
83
+ # * If a block is provided, the file is opened and a PDF Document is created for it. The
84
+ # created document is passed as an argument to the block and when the block returns the
85
+ # associated file object is closed. The value of the block will be returned.
86
+ #
87
+ # The block version is useful, for example, when you are dealing with a large file and you
88
+ # only need a small portion of it.
89
+ #
90
+ # The provided keyword arguments (except +io+) are passed on unchanged to Document.new.
91
+ def self.open(filename, **kwargs)
92
+ if block_given?
93
+ File.open(filename, 'rb') do |file|
94
+ yield(new(**kwargs, io: file))
95
+ end
96
+ else
97
+ new(**kwargs, io: StringIO.new(File.binread(filename)))
98
+ end
99
+ end
100
+
101
+ # The configuration for the document.
102
+ attr_reader :config
103
+
104
+ # The revisions of the document.
105
+ attr_reader :revisions
106
+
107
+ # Creates a new PDF document, either an empty one or one read from the provided +io+.
108
+ #
109
+ # When an IO object is provided and it contains an encrypted PDF file, it is automatically
110
+ # decrypted behind the scenes. The +decryption_opts+ argument has to be set appropriately in
111
+ # this case.
112
+ #
113
+ # Options:
114
+ #
115
+ # io:: If an IO object is provided, then this document can read PDF objects from this IO
116
+ # object, otherwise it can only contain created PDF objects.
117
+ #
118
+ # decryption_opts:: A hash with options for decrypting the PDF objects loaded from the IO.
119
+ #
120
+ # config:: A hash with configuration options that is deep-merged into the default
121
+ # configuration (see DefaultDocumentConfiguration), meaning that direct sub-hashes
122
+ # are merged instead of overwritten.
123
+ def initialize(io: nil, decryption_opts: {}, config: {})
124
+ @config = Configuration.with_defaults(config)
125
+ @version = '1.2'
126
+
127
+ @revisions = Revisions.from_io(self, io)
128
+ if encrypted? && @config['document.auto_decrypt']
129
+ @security_handler = Encryption::SecurityHandler.set_up_decryption(self, decryption_opts)
130
+ else
131
+ @security_handler = nil
132
+ end
133
+
134
+ @listeners = {}
135
+ end
136
+
137
+ # :call-seq:
138
+ # doc.object(ref) -> obj or nil
139
+ # doc.object(oid) -> obj or nil
140
+ #
141
+ # Returns the current version of the indirect object for the given exact reference or for the
142
+ # given object number.
143
+ #
144
+ # For references to unknown objects, +nil+ is returned but free objects are represented by a
145
+ # PDF Null object, not by +nil+!
146
+ #
147
+ # See: PDF1.7 s7.3.9
148
+ def object(ref)
149
+ i = @revisions.size - 1
150
+ while i >= 0
151
+ return @revisions[i].object(ref) if @revisions[i].object?(ref)
152
+ i -= 1
153
+ end
154
+ nil
155
+ end
156
+
157
+ # Dereferences the given object.
158
+ #
159
+ # Return the object itself if it is not a reference, or the indirect object specified by the
160
+ # reference.
161
+ def deref(obj)
162
+ obj.kind_of?(Reference) ? object(obj) : obj
163
+ end
164
+
165
+ # :call-seq:
166
+ # doc.object?(ref) -> true or false
167
+ # doc.object?(oid) -> true or false
168
+ #
169
+ # Returns +true+ if the the document contains an indirect object for the given exact reference
170
+ # or for the given object number.
171
+ #
172
+ # Even though this method might return +true+ for some references, #object may return +nil+
173
+ # because this method takes *all* revisions into account. Also see the discussion on #each for
174
+ # more information.
175
+ def object?(ref)
176
+ @revisions.any? {|rev| rev.object?(ref)}
177
+ end
178
+
179
+ # :call-seq:
180
+ # doc.add(obj, revision: :current, **wrap_opts) -> indirect_object
181
+ #
182
+ # Adds the object to the specified revision of the document and returns the wrapped indirect
183
+ # object.
184
+ #
185
+ # The object can either be a native Ruby object (Hash, Array, Integer, ...) or a
186
+ # HexaPDF::Object. If it is not the latter, #wrap is called with the object and the
187
+ # additional keyword arguments.
188
+ #
189
+ # If the +revision+ option is +:current+, the current revision is used. Otherwise +revision+
190
+ # should be a revision index.
191
+ def add(obj, revision: :current, **wrap_opts)
192
+ obj = wrap(obj, wrap_opts) unless obj.kind_of?(HexaPDF::Object)
193
+
194
+ revision = (revision == :current ? @revisions.current : @revisions.revision(revision))
195
+ if revision.nil?
196
+ raise ArgumentError, "Invalid revision index specified"
197
+ end
198
+
199
+ if obj.document? && obj.document != self
200
+ raise HexaPDF::Error, "Can't add object that is already attached to another document"
201
+ end
202
+ obj.document = self
203
+
204
+ if obj.indirect? && (rev_obj = revision.object(obj.oid))
205
+ if rev_obj.equal?(obj)
206
+ return obj
207
+ else
208
+ raise HexaPDF::Error, "Can't add object because the specified revision already has " \
209
+ "an object with object number #{obj.oid}"
210
+ end
211
+ end
212
+
213
+ obj.oid = @revisions.map(&:next_free_oid).max unless obj.indirect?
214
+
215
+ revision.add(obj)
216
+ end
217
+
218
+ # :call-seq:
219
+ # doc.delete(ref, revision: :all)
220
+ # doc.delete(oid, revision: :all)
221
+ #
222
+ # Deletes the indirect object specified by an exact reference or by an object number from the
223
+ # document.
224
+ #
225
+ # Options:
226
+ #
227
+ # revision:: Specifies from which revisions the object should be deleted:
228
+ #
229
+ # :all:: Delete the object from all revisions.
230
+ # :current:: Delete the object only from the current revision.
231
+ #
232
+ # mark_as_free:: If +true+, objects are only marked as free objects instead of being actually
233
+ # deleted.
234
+ def delete(ref, revision: :all, mark_as_free: true)
235
+ case revision
236
+ when :current
237
+ @revisions.current.delete(ref, mark_as_free: mark_as_free)
238
+ when :all
239
+ @revisions.each {|rev| rev.delete(ref, mark_as_free: mark_as_free)}
240
+ else
241
+ raise ArgumentError, "Unsupported option revision: #{revision}"
242
+ end
243
+ end
244
+
245
+ # :call-seq:
246
+ # doc.import(obj) -> imported_object
247
+ #
248
+ # Imports the given, with a different document associated PDF object and returns the imported
249
+ # object.
250
+ #
251
+ # If the same argument is provided in multiple invocations, the import is done only once and
252
+ # the previously imoprted object is returned.
253
+ #
254
+ # See: Importer
255
+ def import(obj)
256
+ if !obj.kind_of?(HexaPDF::Object) || !obj.document? || obj.document == self
257
+ raise ArgumentError, "Importing only works for PDF objects associated " \
258
+ "with another document"
259
+ end
260
+ HexaPDF::Importer.for(source: obj.document, destination: self).import(obj)
261
+ end
262
+
263
+ # Wraps the given object inside a HexaPDF::Object class which allows one to use
264
+ # convenience functions to work with the object.
265
+ #
266
+ # The +obj+ argument can also be a HexaPDF::Object object so that it can be re-wrapped if
267
+ # needed.
268
+ #
269
+ # The class of the returned object is always a subclass of HexaPDF::Object (or of
270
+ # HexaPDF::Stream if a +stream+ is given). Which subclass is used, depends on the values
271
+ # of the +type+ and +subtype+ options as well as on the 'object.type_map' and
272
+ # 'object.subtype_map' global configuration options:
273
+ #
274
+ # * If *only* +type+ or +subtype+ is provided and a mapping is found, the resulting class is
275
+ # used.
276
+ #
277
+ # * If both +type+ and +subtype+ are provided and and a mapping for +subtype+ is found, the
278
+ # resulting class is used. If no mapping is found but there is a mapping for +type+, the
279
+ # mapped class is used.
280
+ #
281
+ # * If there is no valid class after the above steps, HexaPDF::Stream is used if a stream
282
+ # is given, HexaPDF::Dictionary is used if the given objecct is a hash or else
283
+ # HexaPDF::Object is used.
284
+ #
285
+ # Options:
286
+ #
287
+ # :type:: (Symbol or Class) The type of a PDF object that should be used for wrapping. This
288
+ # could be, for example, :Pages. If a class object is provided, it is used directly
289
+ # instead of the type detection system.
290
+ #
291
+ # :subtype:: (Symbol) The subtype of a PDF object which further qualifies a type. For
292
+ # example, image objects in PDF have a type of :XObject and a subtype of :Image.
293
+ #
294
+ # :oid:: (Integer) The object number that should be set on the wrapped object. Defaults to 0
295
+ # or the value of the given object's object number.
296
+ #
297
+ # :gen:: (Integer) The generation number that should be set on the wrapped object. Defaults to
298
+ # 0 or the value of the given object's generation number.
299
+ #
300
+ # :stream:: (String or StreamData) The stream object which should be set on the wrapped
301
+ # object.
302
+ def wrap(obj, type: nil, subtype: nil, oid: nil, gen: nil, stream: nil)
303
+ data = if obj.kind_of?(HexaPDF::Object)
304
+ obj.data
305
+ else
306
+ HexaPDF::PDFData.new(obj)
307
+ end
308
+ data.oid = oid if oid
309
+ data.gen = gen if gen
310
+ data.stream = stream if stream
311
+
312
+ if type.kind_of?(Class)
313
+ klass = type
314
+ else
315
+ default = if data.stream
316
+ HexaPDF::Stream
317
+ elsif data.value.kind_of?(Hash)
318
+ HexaPDF::Dictionary
319
+ else
320
+ HexaPDF::Object
321
+ end
322
+ if data.value.kind_of?(Hash)
323
+ type ||= deref(data.value[:Type])
324
+ subtype ||= deref(data.value[:Subtype])
325
+ end
326
+
327
+ if subtype
328
+ klass = GlobalConfiguration.constantize('object.subtype_map'.freeze, subtype)
329
+ end
330
+ if type && !klass
331
+ klass = GlobalConfiguration.constantize('object.type_map'.freeze, type)
332
+ end
333
+ klass ||= default
334
+ end
335
+
336
+ klass.new(data, document: self)
337
+ end
338
+
339
+ # :call-seq:
340
+ # document.unwrap(obj) -> unwrapped_obj
341
+ #
342
+ # Recursively unwraps the object to get native Ruby objects (i.e. Hash, Array, Integer, ...
343
+ # instead of HexaPDF::Reference and HexaPDF::Object).
344
+ def unwrap(object, seen = {})
345
+ object = deref(object)
346
+ object = object.data if object.kind_of?(HexaPDF::Object)
347
+ if seen.key?(object)
348
+ raise HexaPDF::Error, "Can't unwrap a recursive structure"
349
+ end
350
+
351
+ case object
352
+ when Hash
353
+ seen[object] = true
354
+ object.each_with_object({}) {|(key, val), memo| memo[key] = unwrap(val, seen.dup)}
355
+ when Array
356
+ seen[object] = true
357
+ object.map {|inner_o| unwrap(inner_o, seen.dup)}
358
+ when HexaPDF::PDFData
359
+ seen[object] = true
360
+ unwrap(object.value, seen.dup)
361
+ else
362
+ object
363
+ end
364
+ end
365
+
366
+ # :call-seq:
367
+ # doc.each(current: true) {|obj| block } -> doc
368
+ # doc.each(current: true) {|obj, rev| block } -> doc
369
+ # doc.each(current: true) -> Enumerator
370
+ #
371
+ # Calls the given block once for every object in the PDF document. The block may either accept
372
+ # only the object or the object and the revision it is in.
373
+ #
374
+ # By default, only the current version of each object is returned which implies that each
375
+ # object number is yielded exactly once. If the +current+ option is +false+, all stored
376
+ # objects from newest to oldest are returned, not only the current version of each object.
377
+ #
378
+ # The +current+ option can make a difference because the document can contain multiple
379
+ # revisions:
380
+ #
381
+ # * Multiple revisions may contain objects with the same object and generation numbers, e.g.
382
+ # two (different) objects with oid/gen [3,0].
383
+ #
384
+ # * Additionally, there may also be objects with the same object number but different
385
+ # generation numbers in different revisions, e.g. one object with oid/gen [3,0] and one with
386
+ # oid/gen [3,1].
387
+ def each(current: true, &block)
388
+ return to_enum(__method__, current: current) unless block_given?
389
+
390
+ yield_rev = (block.arity == 2)
391
+ oids = {}
392
+ @revisions.reverse_each do |rev|
393
+ rev.each do |obj|
394
+ next if current && oids.include?(obj.oid)
395
+ (yield_rev ? yield(obj, rev) : yield(obj))
396
+ oids[obj.oid] = true
397
+ end
398
+ end
399
+ self
400
+ end
401
+
402
+ # :call-seq:
403
+ # doc.register_listener(name, callable) -> callable
404
+ # doc.register_listener(name) {|*args| block} -> block
405
+ #
406
+ # Registers the given listener for the message +name+.
407
+ def register_listener(name, callable = nil, &block)
408
+ callable ||= block
409
+ (@listeners[name] ||= []) << callable
410
+ callable
411
+ end
412
+
413
+ # Dispatches the message +name+ with the given arguments to all registered listeners.
414
+ def dispatch_message(name, *args)
415
+ @listeners[name] && @listeners[name].each {|obj| obj.call(*args)}
416
+ end
417
+
418
+ # Returns a DocumentUtils object that provides convenience methods for often used
419
+ # functionality like adding images.
420
+ def utils
421
+ @utils ||= DocumentUtils.new(self)
422
+ end
423
+
424
+ # Returns the FontUtils object that provides convenience methods for working with fonts.
425
+ def fonts
426
+ @font_utils ||= FontUtils.new(self)
427
+ end
428
+
429
+ # Executes the given task and returns its result.
430
+ #
431
+ # Tasks provide an extensible way for performing operations on a PDF document without
432
+ # cluttering the Document interface.
433
+ #
434
+ # See Task for more information.
435
+ def task(name, **opts, &block)
436
+ task = GlobalConfiguration.constantize('task.map'.freeze, name) do
437
+ raise HexaPDF::Error, "No task named '#{name}' is available"
438
+ end
439
+ task.call(self, **opts, &block)
440
+ end
441
+
442
+ # Returns the trailer dictionary for the document.
443
+ def trailer
444
+ @revisions.current.trailer
445
+ end
446
+
447
+ # Returns the document's catalog, the root of the object tree.
448
+ def catalog
449
+ trailer.catalog
450
+ end
451
+
452
+ # Returns the root node of the document's page tree.
453
+ #
454
+ # See: HexaPDF::Type::PageTreeNode
455
+ def pages
456
+ catalog.pages
457
+ end
458
+
459
+ # Returns the PDF document's version as string (e.g. '1.4').
460
+ #
461
+ # This method takes the file header version and the catalog's /Version key into account. If a
462
+ # version has been set manually and the catalog's /Version key refers to a later version, the
463
+ # later version is used.
464
+ #
465
+ # See: PDF1.7 s7.2.2
466
+ def version
467
+ catalog_version = (catalog[:Version] || '1.0'.freeze).to_s
468
+ (@version < catalog_version ? catalog_version : @version)
469
+ end
470
+
471
+ # Sets the version of the PDF document. The argument must be a string in the format 'M.N'
472
+ # where M is the major version and N the minor version (e.g. '1.4' or '2.0').
473
+ def version=(value)
474
+ raise ArgumentError, "PDF version must follow format M.N" unless value.to_s =~ /\A\d\.\d\z/
475
+ @version = value.to_s
476
+ end
477
+
478
+ # Returns +true+ if the document is encrypted.
479
+ def encrypted?
480
+ !trailer[:Encrypt].nil?
481
+ end
482
+
483
+ # Encrypts the document.
484
+ #
485
+ # This is done by setting up a security handler for this purpose and populating the trailer's
486
+ # Encrypt dictionary accordingly. The actual encryption, however, is only done when writing the
487
+ # document.
488
+ #
489
+ # The security handler used for encrypting is selected via the +name+ argument. All other
490
+ # arguments are passed on the security handler.
491
+ #
492
+ # If the document should not be encrypted, the +name+ argument has to be set to +nil+. This
493
+ # removes the security handler and deletes the trailer's Encrypt dictionary.
494
+ #
495
+ # See: HexaPDF::Encryption::SecurityHandler#set_up_encryption and
496
+ # HexaPDF::Encryption::StandardSecurityHandler::EncryptionOptions for possible encryption
497
+ # options.
498
+ def encrypt(name: :Standard, **options)
499
+ if name.nil?
500
+ trailer.delete(:Encrypt)
501
+ @security_handler = nil
502
+ else
503
+ @security_handler = Encryption::SecurityHandler.set_up_encryption(self, name, **options)
504
+ end
505
+ end
506
+
507
+ # Returns the security handler that is used for decrypting or encrypting the document, or +nil+
508
+ # if none is set.
509
+ #
510
+ # * If the document was created by reading an existing file and the document was automatically
511
+ # decrypted, then this method returns the handler for decrypting.
512
+ #
513
+ # * Once the #encrypt method is called, the specified security handler for encrypting is
514
+ # returned.
515
+ def security_handler
516
+ @security_handler
517
+ end
518
+
519
+ # :call-seq:
520
+ # doc.validate(auto_correct: true) -> true or false
521
+ # doc.validate(auto_correct: true) {|msg, correctable| block } -> true or false
522
+ #
523
+ # Validates all objects of the document, with optional auto-correction, and returns +true+ if
524
+ # everything is fine.
525
+ #
526
+ # If a block is given, it is called on validation problems.
527
+ #
528
+ # See HexaPDF::Object#validate for more information.
529
+ def validate(auto_correct: true, &block)
530
+ result = trailer.validate(auto_correct: auto_correct, &block)
531
+ each(current: false) do |obj|
532
+ result &&= obj.validate(auto_correct: auto_correct, &block)
533
+ end
534
+ result
535
+ end
536
+
537
+ # :call-seq:
538
+ # doc.write(filename, validate: true, update_fields: true, optimize: false)
539
+ # doc.write(io, validate: true, update_fields: true, optimize: false)
540
+ #
541
+ # Writes the document to the given file (in case +io+ is a String) or IO stream.
542
+ #
543
+ # Before the document is written, it is validated using #validate and an error is raised if the
544
+ # document is not valid. However, this step can be skipped if needed.
545
+ #
546
+ # Options:
547
+ #
548
+ # validate::
549
+ # Validates the document and raises an error if an uncorrectable problem is found.
550
+ #
551
+ # update_fields::
552
+ # Updates the /ID field in the trailer dictionary as well as the /ModDate field in the
553
+ # trailer's /Info dictionary so that it is clear that the document has been updated.
554
+ #
555
+ # optimize::
556
+ # Optimize the file size by using object and cross-reference streams. This will raise the PDF
557
+ # version to at least 1.5.
558
+ def write(file_or_io, validate: true, update_fields: true, optimize: false)
559
+ dispatch_message(:complete_objects)
560
+
561
+ if update_fields
562
+ trailer.update_id
563
+ trailer.info[:ModDate] = Time.now
564
+ end
565
+
566
+ if validate
567
+ self.validate(auto_correct: true) do |msg, correctable|
568
+ next if correctable
569
+ raise HexaPDF::Error, "Validation error: #{msg}"
570
+ end
571
+ end
572
+
573
+ if optimize
574
+ task(:optimize, object_streams: :generate)
575
+ self.version = '1.5' if version < '1.5'
576
+ end
577
+
578
+ dispatch_message(:before_write)
579
+
580
+ if file_or_io.kind_of?(String)
581
+ File.open(file_or_io, 'w+') {|file| Writer.write(self, file)}
582
+ else
583
+ Writer.write(self, file_or_io)
584
+ end
585
+ end
586
+
587
+ end
588
+
589
+ end