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,224 @@
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 'set'
35
+ require 'stringio'
36
+ require 'hexapdf/error'
37
+ require 'hexapdf/stream'
38
+ require 'hexapdf/reference'
39
+ require 'hexapdf/tokenizer'
40
+ require 'hexapdf/serializer'
41
+
42
+ module HexaPDF
43
+ module Type
44
+
45
+ # Represents PDF type ObjStm, object streams.
46
+ #
47
+ # An object stream is a stream that can hold multiple indirect objects. Since the objects are
48
+ # stored inside the stream, filters can be used to compress the stream content and therefore
49
+ # represent the indirect objects more compactly than would be possible otherwise.
50
+ #
51
+ # == How are Object Streams Used?
52
+ #
53
+ # When an indirect object that resides in an object stream needs to be loaded, the object stream
54
+ # itself is parsed and loaded and #parse_stream is invoked to get an ObjectStream::Data object
55
+ # representing the stored indirect objects. After that the requested indirect object itself is
56
+ # loaded and returned using this ObjectStream::Data object. From a user's perspective nothing
57
+ # changes when an object is located inside an object stream instead of directly in a PDF file.
58
+ #
59
+ # The indirect objects initially stored in the object stream are automatically added to the
60
+ # list of to-be-stored objects when #parse_stream is invoked. Additional objects can be
61
+ # assigned to the object stream via #add_object or deleted from it via #delete_object.
62
+ #
63
+ # Before an object stream is written, it is necessary to invoke #write_objects so that the
64
+ # to-be-stored objects are serialized to the stream. This is automatically done by the Writer.
65
+ # A user thus only has to define which objects should reside in the object stream.
66
+ #
67
+ # However, only objects that can be written to the object stream are actually written. The
68
+ # other objects are deleted from the object stream (#delete_object) and written normally.
69
+ #
70
+ # See PDF1.7 s7.5.7
71
+ class ObjectStream < HexaPDF::Stream
72
+
73
+ # Holds all necessary information to load objects for an object stream.
74
+ class Data
75
+
76
+ # Initializes the data object with the needed values.
77
+ def initialize(stream_data, oids, offsets)
78
+ @tokenizer = Tokenizer.new(StringIO.new(stream_data))
79
+ @offsets = offsets
80
+ @oids = oids
81
+ end
82
+
83
+ # Returns the object specified by the given index together with its object number.
84
+ #
85
+ # Objects are not pre-loaded, so every time this method is invoked the associated stream
86
+ # data is parsed and a new object returned.
87
+ def object_by_index(index)
88
+ if index >= @offsets.size || index < 0
89
+ raise ArgumentError, "Invalid index into object stream given"
90
+ end
91
+
92
+ @tokenizer.pos = @offsets[index]
93
+ [@tokenizer.next_object, @oids[index]]
94
+ end
95
+
96
+ end
97
+
98
+
99
+ define_field :Type, type: Symbol, required: true, default: :ObjStm, version: '1.5'
100
+ define_field :N, type: Integer # not required, will be auto-filled on #write_objects
101
+ define_field :First, type: Integer # not required, will be auto-filled on #write_objects
102
+ define_field :Extends, type: Stream
103
+
104
+ # Parses the stream and returns an ObjectStream::Data object that can be used for retrieving
105
+ # the objects defined by this object stream.
106
+ #
107
+ # The object references are also added to this object stream so that they are included when
108
+ # the object gets written.
109
+ def parse_stream
110
+ data = stream
111
+ oids, offsets = parse_oids_and_offsets(data)
112
+ oids.each {|oid| add_object(Reference.new(oid, 0))}
113
+ Data.new(data, oids, offsets)
114
+ end
115
+
116
+ # Adds the given object to the list of objects that should be stored in this object stream.
117
+ #
118
+ # The +ref+ argument can either be a reference or any PDF object.
119
+ def add_object(ref)
120
+ return if object_index(ref)
121
+
122
+ index = objects.size / 2
123
+ objects[index] = ref
124
+ objects[ref] = index
125
+ end
126
+
127
+ # Deletes the given object from the list of objects that should be stored in this object
128
+ # stream.
129
+ #
130
+ # The +ref+ argument can either be a reference or a PDF object.
131
+ def delete_object(ref)
132
+ index = objects[ref]
133
+ return unless index
134
+
135
+ move_index = objects.size / 2 - 1
136
+
137
+ objects[index] = objects[move_index]
138
+ objects[objects[index]] = index
139
+ objects.delete(ref)
140
+ objects.delete(move_index)
141
+ end
142
+
143
+ # Returns the index into the array containing the to-be-stored objects for the given
144
+ # reference/PDF object.
145
+ def object_index(obj)
146
+ objects[obj]
147
+ end
148
+
149
+ # :call-seq:
150
+ # objstm.write_objects(revision) -> obj_to_stm_hash
151
+ #
152
+ # Writes the added objects to the stream and returns a hash mapping all written objects to
153
+ # this object stream.
154
+ #
155
+ # There are some reasons why an added object may not be stored in the stream:
156
+ #
157
+ # * It has a generation number other than 0.
158
+ # * It is a stream object.
159
+ # * It doesn't reside in the given Revision object.
160
+ #
161
+ # Such objects are additionally deleted from the list of to-be-stored objects and are later
162
+ # written as indirect objects.
163
+ def write_objects(revision)
164
+ index = 0
165
+ object_info = ''.b
166
+ data = ''.b
167
+ serializer = Serializer.new
168
+ obj_to_stm = {}
169
+
170
+ encrypt_dict = document.trailer[:Encrypt]
171
+ while index < objects.size / 2
172
+ obj = revision.object(objects[index])
173
+ if obj.nil? || obj.null? || obj.gen != 0 || obj.kind_of?(Stream) || obj == encrypt_dict
174
+ delete_object(objects[index])
175
+ next
176
+ end
177
+
178
+ obj_to_stm[obj] = self
179
+ object_info << "#{obj.oid} #{data.size} "
180
+ data << serializer.serialize(obj) << " "
181
+ index += 1
182
+ end
183
+
184
+ value[:Type] = :ObjStm
185
+ value[:N] = objects.size / 2
186
+ value[:First] = object_info.size
187
+ self.stream = object_info << data
188
+ set_filter(:FlateDecode)
189
+
190
+ obj_to_stm
191
+ end
192
+
193
+ private
194
+
195
+ # Parses the object numbers and their offsets from the start of the stream data.
196
+ def parse_oids_and_offsets(data)
197
+ oids = []
198
+ offsets = []
199
+ first = value[:First].to_i
200
+
201
+ stream_tokenizer = Tokenizer.new(StringIO.new(data))
202
+ data.size > 0 && value[:N].to_i.times do
203
+ oids << stream_tokenizer.next_object
204
+ offsets << first + stream_tokenizer.next_object
205
+ end
206
+
207
+ [oids, offsets]
208
+ end
209
+
210
+ # Returns the container with the to-be-stored objects.
211
+ def objects
212
+ @objects ||= {}
213
+ end
214
+
215
+ # Validates that the generation number of the object stream is zero.
216
+ def perform_validation
217
+ super
218
+ yield("Object stream has invalid generation number > 0", false) if gen != 0
219
+ end
220
+
221
+ end
222
+
223
+ end
224
+ end
@@ -0,0 +1,355 @@
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 'hexapdf/dictionary'
35
+ require 'hexapdf/stream'
36
+ require 'hexapdf/type/page_tree_node'
37
+ require 'hexapdf/content'
38
+
39
+ module HexaPDF
40
+ module Type
41
+
42
+ # Represents a page of a PDF document.
43
+ #
44
+ # A page object contains the meta information for a page. Most of the fields are independent
45
+ # from the page's content like the /Dur field. However, some of them (like /Resources or
46
+ # /UserUnit) influence how or if the page's content can be rendered correctly.
47
+ #
48
+ # A number of field values can also be inherited: /Resources, /MediaBox, /CropBox, /Rotate.
49
+ # Field inheritance means that if a field is not set on the page object itself, the value is
50
+ # taken from the nearest page tree ancestor that has this value set.
51
+ #
52
+ # See: PDF1.7 s7.7.3.3, s7.7.3.4, Pages
53
+ class Page < Dictionary
54
+
55
+ # The predefined paper sizes in points (1/72 inch):
56
+ #
57
+ # * ISO sizes: A0x4, A0x2, A0-A10, B0-B10, C0-C10
58
+ # * Letter, Legal, Ledger, Tabloid, Executive
59
+ PAPER_SIZE = {
60
+ A0x4: [0, 0, 4768, 6741].freeze,
61
+ A0x2: [0, 0, 3370, 4768].freeze,
62
+ A0: [0, 0, 2384, 3370].freeze,
63
+ A1: [0, 0, 1684, 2384].freeze,
64
+ A2: [0, 0, 1191, 1684].freeze,
65
+ A3: [0, 0, 842, 1191].freeze,
66
+ A4: [0, 0, 595, 842].freeze,
67
+ A5: [0, 0, 420, 595].freeze,
68
+ A6: [0, 0, 298, 420].freeze,
69
+ A7: [0, 0, 210, 298].freeze,
70
+ A8: [0, 0, 147, 210].freeze,
71
+ A9: [0, 0, 105, 147].freeze,
72
+ A10: [0, 0, 74, 105].freeze,
73
+ B0: [0, 0, 2835, 4008].freeze,
74
+ B1: [0, 0, 2004, 2835].freeze,
75
+ B2: [0, 0, 1417, 2004].freeze,
76
+ B3: [0, 0, 1001, 1417].freeze,
77
+ B4: [0, 0, 709, 1001].freeze,
78
+ B5: [0, 0, 499, 709].freeze,
79
+ B6: [0, 0, 354, 499].freeze,
80
+ B7: [0, 0, 249, 354].freeze,
81
+ B8: [0, 0, 176, 249].freeze,
82
+ B9: [0, 0, 125, 176].freeze,
83
+ B10: [0, 0, 88, 125].freeze,
84
+ C0: [0, 0, 2599, 3677].freeze,
85
+ C1: [0, 0, 1837, 2599].freeze,
86
+ C2: [0, 0, 1298, 1837].freeze,
87
+ C3: [0, 0, 918, 1298].freeze,
88
+ C4: [0, 0, 649, 918].freeze,
89
+ C5: [0, 0, 459, 649].freeze,
90
+ C6: [0, 0, 323, 459].freeze,
91
+ C7: [0, 0, 230, 323].freeze,
92
+ C8: [0, 0, 162, 230].freeze,
93
+ C9: [0, 0, 113, 162].freeze,
94
+ C10: [0, 0, 79, 113].freeze,
95
+ Letter: [0, 0, 612, 792].freeze,
96
+ Legal: [0, 0, 612, 1008].freeze,
97
+ Ledger: [0, 0, 792, 1224].freeze,
98
+ Tabloid: [0, 0, 1224, 792].freeze,
99
+ Executive: [0, 0, 522, 756].freeze,
100
+ }
101
+
102
+ # The inheritable fields.
103
+ INHERITABLE_FIELDS = [:Resources, :MediaBox, :CropBox, :Rotate]
104
+
105
+ # The required inheritable fields.
106
+ REQUIRED_INHERITABLE_FIELDS = [:Resources, :MediaBox]
107
+
108
+
109
+ define_field :Type, type: Symbol, required: true, default: :Page
110
+ define_field :Parent, type: :Pages, required: true, indirect: true
111
+ define_field :LastModified, type: PDFDate, version: '1.3'
112
+ define_field :Resources, type: :XXResources
113
+ define_field :MediaBox, type: Rectangle
114
+ define_field :CropBox, type: Rectangle
115
+ define_field :BleedBox, type: Rectangle, version: '1.3'
116
+ define_field :TrimBox, type: Rectangle, version: '1.3'
117
+ define_field :ArtBox, type: Rectangle, version: '1.3'
118
+ define_field :BoxColorInfo, type: Dictionary, version: '1.4'
119
+ define_field :Contents, type: [Array, Stream]
120
+ define_field :Rotate, type: Integer, default: 0
121
+ define_field :Group, type: Dictionary, version: '1.4'
122
+ define_field :Thumb, type: Stream
123
+ define_field :B, type: Array, version: '1.1'
124
+ define_field :Dur, type: Numeric, version: '1.1'
125
+ define_field :Trans, type: Dictionary, version: '1.1'
126
+ define_field :Annots, type: Array
127
+ define_field :AA, type: Dictionary, version: '1.2'
128
+ define_field :Metadata, type: Stream, version: '1.4'
129
+ define_field :PieceInfo, type: Dictionary, version: '1.3'
130
+ define_field :StructParents, type: Integer, version: '1.3'
131
+ define_field :ID, type: PDFByteString, version: '1.3'
132
+ define_field :PZ, type: Numeric, version: '1.3'
133
+ define_field :SeparationInfo, type: Dictionary, version: '1.3'
134
+ define_field :Tabs, type: Symbol, version: '1.5'
135
+ define_field :TemplateInstantiated, type: Symbol, version: '1.5'
136
+ define_field :PresSteps, type: Dictionary, version: '1.5'
137
+ define_field :UserUnit, type: Numeric, version: '1.6'
138
+ define_field :VP, type: Dictionary, version: '1.6'
139
+
140
+ # Returns +true+ since page objects must always be indirect.
141
+ def must_be_indirect?
142
+ true
143
+ end
144
+
145
+ # Returns the value for the entry +name+.
146
+ #
147
+ # If +name+ is an inheritable value and the value has not been set on the page object, its
148
+ # value is retrieved from the ancestor page tree nodes.
149
+ #
150
+ # See: Dictionary#[]
151
+ def [](name)
152
+ if value[name].nil? && INHERITABLE_FIELDS.include?(name)
153
+ node = self[:Parent] || (raise InvalidPDFObjectError, "Page has no parent node")
154
+ node = node[:Parent] while node.value[name].nil? && node.key?(:Parent)
155
+ node[name] || super
156
+ else
157
+ super
158
+ end
159
+ end
160
+
161
+ # Copies the page's inherited values from the ancestor page tree nodes into a hash and returns
162
+ # the hash.
163
+ #
164
+ # The hash can then be used to update the page itself (e.g. when moving a page from one
165
+ # position to another) or another page (e.g. when importing a page from another document).
166
+ def copy_inherited_values
167
+ INHERITABLE_FIELDS.each_with_object({}) do |name, hash|
168
+ hash[name] = HexaPDF::Object.deep_copy(self[name]) if value[name].nil?
169
+ end
170
+ end
171
+
172
+ # Returns the rectangle defining a certain kind of box for the page.
173
+ #
174
+ # This method should be used instead of directly accessing any of /MediaBox, /CropBox,
175
+ # /BleedBox, /ArtBox or /TrimBox because it also takes the fallback values into account!
176
+ #
177
+ # The following types are allowed:
178
+ #
179
+ # :media::
180
+ # The media box defines the boundaries of the medium the page is to be printed on.
181
+ #
182
+ # :crop::
183
+ # The crop box defines the region to which the contents of the page should be clipped
184
+ # when it is displayed or printed. The default is the media box.
185
+ #
186
+ # :bleed::
187
+ # The bleed box defines the region to which the contents of the page should be clipped
188
+ # when output in a production environment. The default is the crop box.
189
+ #
190
+ # :trim::
191
+ # The trim box defines the intended dimensions of the page after trimming. The default
192
+ # value is the crop box.
193
+ #
194
+ # :art::
195
+ # The art box defines the region of the page's meaningful content as intended by the
196
+ # author. The default is the crop box.
197
+ #
198
+ # See: PDF1.7 s14.11.2
199
+ def box(type = :media)
200
+ case type
201
+ when :media then self[:MediaBox]
202
+ when :crop then self[:CropBox] || self[:MediaBox]
203
+ when :bleed then self[:BleedBox] || self[:CropBox] || self[:MediaBox]
204
+ when :trim then self[:TrimBox] || self[:CropBox] || self[:MediaBox]
205
+ when :art then self[:ArtBox] || self[:CropBox] || self[:MediaBox]
206
+ else
207
+ raise ArgumentError, "Unsupported page box type provided: #{type}"
208
+ end
209
+ end
210
+
211
+ # Returns the concatenated stream data from the content streams as binary string.
212
+ #
213
+ # Note: Any modifications done to the returned value *won't* be reflected in any of the
214
+ # streams' data!
215
+ def contents
216
+ Array(self[:Contents]).each_with_object("".b) do |content_stream, content|
217
+ content << " ".freeze unless content.empty?
218
+ content << document.deref(content_stream).stream
219
+ end
220
+ end
221
+
222
+ # Replaces the contents of the page with the given string.
223
+ #
224
+ # This is done by deleting all but the first content stream and reusing this content stream;
225
+ # or by creating a new one if no content stream exists.
226
+ def contents=(data)
227
+ first, *rest = self[:Contents]
228
+ rest.each {|stream| document.delete(stream)}
229
+ if first
230
+ self[:Contents] = first
231
+ document.deref(first).stream = data
232
+ else
233
+ self[:Contents] = document.add({Filter: :FlateDecode}, stream: data)
234
+ end
235
+ end
236
+
237
+ # Returns the resource dictionary which is automatically created if it doesn't exist.
238
+ def resources
239
+ self[:Resources] ||= document.wrap({}, type: :XXResources)
240
+ end
241
+
242
+ # Processes the content streams associated with the page with the given processor object.
243
+ #
244
+ # See: HexaPDF::Content::Processor
245
+ def process_contents(processor)
246
+ self[:Resources] = {} if self[:Resources].nil?
247
+ processor.resources = self[:Resources]
248
+ Content::Parser.parse(contents, processor)
249
+ end
250
+
251
+ # Returns the requested type of canvas for the page.
252
+ #
253
+ # The canvas object is cached once it is created so that its graphics state is correctly
254
+ # retained without the need for parsing its contents.
255
+ #
256
+ # type::
257
+ # Can either be
258
+ # * :page for getting the canvas for the page itself (only valid for initially empty pages)
259
+ # * :overlay for getting the canvas for drawing over the page contents
260
+ # * :underlay for getting the canvas for drawing unter the page contents
261
+ def canvas(type: :page)
262
+ unless [:page, :overlay, :underlay].include?(type)
263
+ raise ArgumentError, "Invalid value for 'type', expected: :page, :underlay or :overlay"
264
+ end
265
+ @canvas_cache ||= {}
266
+ return @canvas_cache[type] if @canvas_cache.key?(type)
267
+
268
+ if type == :page && key?(:Contents)
269
+ raise HexaPDF::Error, "Cannot get the canvas for a page with contents"
270
+ end
271
+
272
+ contents = self[:Contents]
273
+ if contents.nil?
274
+ @canvas_cache[:page] = Content::Canvas.new(self)
275
+ self[:Contents] = document.add({Filter: :FlateDecode},
276
+ stream: @canvas_cache[:page].stream_data)
277
+ end
278
+
279
+ if type == :overlay || type == :underlay
280
+ @canvas_cache[:overlay] = Content::Canvas.new(self)
281
+ @canvas_cache[:underlay] = Content::Canvas.new(self)
282
+
283
+ stream = HexaPDF::StreamData.new do
284
+ Fiber.yield(" q ")
285
+ fiber = @canvas_cache[:underlay].stream_data.fiber
286
+ while fiber.alive? && (data = fiber.resume)
287
+ Fiber.yield(data)
288
+ end
289
+ " Q q "
290
+ end
291
+ underlay = document.add({Filter: :FlateDecode}, stream: stream)
292
+
293
+ stream = HexaPDF::StreamData.new do
294
+ Fiber.yield(" Q ")
295
+ fiber = @canvas_cache[:overlay].stream_data.fiber
296
+ while fiber.alive? && (data = fiber.resume)
297
+ Fiber.yield(data)
298
+ end
299
+ end
300
+ overlay = document.add({Filter: :FlateDecode}, stream: stream)
301
+
302
+ self[:Contents] = [underlay, *self[:Contents], overlay]
303
+ end
304
+
305
+ @canvas_cache[type]
306
+ end
307
+
308
+ # Creates a Form XObject from the page's dictionary and contents for the given PDF document.
309
+ #
310
+ # If +reference+ is true, the page's contents is referenced when possible to avoid unnecessary
311
+ # decoding/encoding.
312
+ #
313
+ # Note 1: The created Form XObject is *not* added to the document automatically!
314
+ #
315
+ # Note 2: If +reference+ is false and if a canvas is used on this page (see #canvas), this
316
+ # method should only be called once the contents of the page has been fully defined. The
317
+ # reason is that during the copying of the content stream data the contents may be modified to
318
+ # make it a fully valid content stream.
319
+ def to_form_xobject(reference: true)
320
+ first, *rest = self[:Contents]
321
+ stream = if !first
322
+ nil
323
+ elsif !reference || !rest.empty? || first.raw_stream.kind_of?(String)
324
+ contents
325
+ else
326
+ first.raw_stream
327
+ end
328
+ dict = {
329
+ Type: :XObject,
330
+ Subtype: :Form,
331
+ BBox: HexaPDF::Object.deep_copy(box(:crop)),
332
+ Resources: HexaPDF::Object.deep_copy(self[:Resources]),
333
+ Filter: :FlateDecode,
334
+ }
335
+ document.wrap(dict, stream: stream)
336
+ end
337
+
338
+ private
339
+
340
+ # Ensures that the required inheritable fields are set.
341
+ def perform_validation(&block)
342
+ super
343
+ REQUIRED_INHERITABLE_FIELDS.each do |name|
344
+ if self[name].nil?
345
+ yield("Inheritable page field #{name} not set", name == :Resources)
346
+ self[:Resources] = {}
347
+ self[:Resources].validate(&block)
348
+ end
349
+ end
350
+ end
351
+
352
+ end
353
+
354
+ end
355
+ end