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,179 @@
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/font/true_type/table'
35
+
36
+ module HexaPDF
37
+ module Font
38
+ module TrueType
39
+
40
+ # Represents a font in the TrueType font file format.
41
+ class Font
42
+
43
+ # The default configuration:
44
+ #
45
+ # font.ttf.table_mapping::
46
+ # The default mapping from table tag as symbol to table class name.
47
+ #
48
+ # font.ttf.cmap.unknown_format::
49
+ # Action to take when encountering unknown 'cmap' subtables. Can either be :ignore
50
+ # which ignores them or :raise which raises an error.
51
+ DEFAULT_CONFIG = {
52
+ 'font.true_type.table_mapping' => {
53
+ head: 'HexaPDF::Font::TrueType::Table::Head',
54
+ cmap: 'HexaPDF::Font::TrueType::Table::Cmap',
55
+ hhea: 'HexaPDF::Font::TrueType::Table::Hhea',
56
+ hmtx: 'HexaPDF::Font::TrueType::Table::Hmtx',
57
+ loca: 'HexaPDF::Font::TrueType::Table::Loca',
58
+ maxp: 'HexaPDF::Font::TrueType::Table::Maxp',
59
+ name: 'HexaPDF::Font::TrueType::Table::Name',
60
+ post: 'HexaPDF::Font::TrueType::Table::Post',
61
+ glyf: 'HexaPDF::Font::TrueType::Table::Glyf',
62
+ 'OS/2': 'HexaPDF::Font::TrueType::Table::OS2',
63
+ },
64
+ 'font.true_type.cmap.unknown_format' => :ignore,
65
+ }
66
+
67
+
68
+ # The IO stream associated with this file. If this is +nil+ then the TrueType font wasn't
69
+ # originally read from an IO stream.
70
+ attr_reader :io
71
+
72
+ # The configuration for the TrueType font.
73
+ attr_reader :config
74
+
75
+ # Creates a new TrueType font file object. If an IO object is given, the TrueType font data
76
+ # is read from it.
77
+ #
78
+ # The +config+ hash can contain configuration options.
79
+ def initialize(io: nil, config: {})
80
+ @io = io
81
+ @config = DEFAULT_CONFIG.merge(config)
82
+ @tables = {}
83
+ end
84
+
85
+ # Returns the table instance for the given tag (a symbol), or +nil+ if no such table exists.
86
+ def [](tag)
87
+ return @tables[tag] if @tables.key?(tag)
88
+
89
+ entry = directory.entry(tag.to_s.b)
90
+ entry ? @tables[tag] = table_class(tag).new(self, entry) : nil
91
+ end
92
+
93
+ # Adds a new table instance for the given tag (a symbol) to the font if such a table
94
+ # instance doesn't already exist. Returns the table instance for the tag.
95
+ def add_table(tag)
96
+ @tables[tag] ||= table_class(tag).new(self)
97
+ end
98
+
99
+ # Returns the font directory.
100
+ def directory
101
+ @directory ||= Table::Directory.new(self, io ? Table::Directory::SELF_ENTRY : nil)
102
+ end
103
+
104
+ # Returns the PostScript font name.
105
+ def font_name
106
+ self[:name][:postscript_name].preferred_record
107
+ end
108
+
109
+ # Returns the full name of the font.
110
+ def full_name
111
+ self[:name][:font_name].preferred_record
112
+ end
113
+
114
+ # Returns the family name of the font.
115
+ def family_name
116
+ self[:name][:font_family].preferred_record
117
+ end
118
+
119
+ # Returns the weight of the font.
120
+ def weight
121
+ self[:"OS/2"]&.weight_class || 0
122
+ end
123
+
124
+ # Returns the bounding of the font.
125
+ def bounding_box
126
+ self[:head].bbox
127
+ end
128
+
129
+ # Returns the cap height of the font.
130
+ def cap_height
131
+ self[:"OS/2"]&.cap_height
132
+ end
133
+
134
+ # Returns the x-height of the font.
135
+ def x_height
136
+ self[:"OS/2"]&.x_height
137
+ end
138
+
139
+ # Returns the ascender of the font.
140
+ def ascender
141
+ self[:"OS/2"]&.typo_ascender || self[:hhea].ascent
142
+ end
143
+
144
+ # Returns the descender of the font.
145
+ def descender
146
+ self[:"OS/2"]&.typo_descender || self[:hhea].descent
147
+ end
148
+
149
+ # Returns the italic angle of the font, in degrees counter-clockwise from the vertical.
150
+ def italic_angle
151
+ self[:post].italic_angle.to_f
152
+ end
153
+
154
+ # Returns the dominant width of vertical stems.
155
+ #
156
+ # Note: This attribute does not actually exist in TrueType fonts, so it is estimated based
157
+ # on the #weight.
158
+ def dominant_vertical_stem_width
159
+ weight / 5
160
+ end
161
+
162
+ # Returns th glyph ID of the missing glyph, i.e. 0.
163
+ def missing_glyph_id
164
+ 0
165
+ end
166
+
167
+ private
168
+
169
+ # Returns the class that is used for handling tables of the given tag.
170
+ def table_class(tag)
171
+ k = config['font.true_type.table_mapping'].fetch(tag, 'HexaPDF::Font::TrueType::Table')
172
+ ::Object.const_get(k)
173
+ end
174
+
175
+ end
176
+
177
+ end
178
+ end
179
+ end
@@ -0,0 +1,103 @@
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/font/true_type/table'
35
+ require 'hexapdf/font/true_type/table/cmap_subtable'
36
+
37
+ module HexaPDF
38
+ module Font
39
+ module TrueType
40
+ class Table
41
+
42
+ # The 'cmap' table contains subtables for mapping character codes to glyph indices.
43
+ #
44
+ # See:
45
+ # * CmapSubtable
46
+ # * https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6cmap.html
47
+ class Cmap < Table
48
+
49
+ # The version of the cmap table.
50
+ attr_accessor :version
51
+
52
+ # The available cmap subtables.
53
+ attr_accessor :tables
54
+
55
+ # Returns the preferred of the available cmap subtables.
56
+ #
57
+ # A preferred table is always a table mapping Unicode characters.
58
+ def preferred_table
59
+ tables.select(&:unicode?).sort {|a, b| a.format <=> b.format}.last
60
+ end
61
+
62
+ private
63
+
64
+ def parse_table #:nodoc:
65
+ @version, num_tables = read_formatted(4, 'n2')
66
+ @tables = []
67
+ handle_unknown = font.config['font.true_type.cmap.unknown_format']
68
+
69
+ num_tables.times { @tables << read_formatted(8, 'n2N') }
70
+ offset_map = {}
71
+ @tables.map! do |platform_id, encoding_id, offset|
72
+ offset += directory_entry.offset
73
+ if offset_map.key?(offset)
74
+ subtable = offset_map[offset].dup
75
+ subtable.platform_id = platform_id
76
+ subtable.encoding_id = encoding_id
77
+ next subtable
78
+ end
79
+
80
+ subtable = CmapSubtable.new(platform_id, encoding_id)
81
+ supported = subtable.parse(io, offset)
82
+ if supported
83
+ offset_map[offset] = subtable
84
+ subtable
85
+ elsif handle_unknown == :raise
86
+ raise HexaPDF::Error, "Unknown cmap subtable format #{subtable.format}"
87
+ else
88
+ nil
89
+ end
90
+ end.compact!
91
+ end
92
+
93
+ def load_default #:nodoc:
94
+ @version = 0
95
+ @tables = []
96
+ end
97
+
98
+ end
99
+
100
+ end
101
+ end
102
+ end
103
+ end
@@ -0,0 +1,384 @@
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/font/true_type/table'
35
+
36
+ module HexaPDF
37
+ module Font
38
+ module TrueType
39
+ class Table
40
+
41
+ # Generic base class for all cmap subtables.
42
+ #
43
+ # cmap format 8.0 is currently not implemented because use of the format is discouraged in
44
+ # the specification and no font with a format 8.0 cmap subtable was available for testing.
45
+ #
46
+ # The preferred cmap format is 12.0 because it supports all of Unicode and allows for fast
47
+ # and memory efficient code-to-gid as well as gid-to-code mappings.
48
+ #
49
+ # See:
50
+ # * Cmap
51
+ # * https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6cmap.html
52
+ class CmapSubtable
53
+
54
+ # The platform identifier for Unicode.
55
+ PLATFORM_UNICODE = 0
56
+
57
+ # The platform identifier for Microsoft.
58
+ PLATFORM_MICROSOFT = 3
59
+
60
+ # The platform identifier.
61
+ attr_accessor :platform_id
62
+
63
+ # The platform-specific encoding identifier.
64
+ attr_accessor :encoding_id
65
+
66
+ # The cmap format or +nil+ if the subtable wasn't read from a file.
67
+ attr_reader :format
68
+
69
+ # The language code.
70
+ attr_accessor :language
71
+
72
+ # The complete code map.
73
+ attr_accessor :code_map
74
+
75
+ # The complete gid map.
76
+ attr_accessor :gid_map
77
+
78
+ # Creates a new subtable.
79
+ def initialize(platform_id, encoding_id)
80
+ @platform_id = platform_id
81
+ @encoding_id = encoding_id
82
+ @supported = true
83
+ @code_map = {}
84
+ @gid_map = {}
85
+ @format = nil
86
+ @language = 0
87
+ end
88
+
89
+ # Returns +true+ if this subtable contains a Unicode cmap.
90
+ def unicode?
91
+ (platform_id == PLATFORM_MICROSOFT && (encoding_id == 1 || encoding_id == 10)) ||
92
+ platform_id == PLATFORM_UNICODE
93
+ end
94
+
95
+ # Returns the glyph index for the given character code or +nil+ if the character code is
96
+ # not mapped.
97
+ def [](code)
98
+ @code_map[code]
99
+ end
100
+
101
+ # Returns a character code for the given glyph index or +nil+ if the given glyph index
102
+ # does not exist or is not mapped to a character code.
103
+ #
104
+ # Note that some fonts map multiple character codes to the same glyph (e.g. hyphen and
105
+ # minus), i.e. the code-to-glyph mapping is surjective but not injective! In such a case
106
+ # one of the available character codes is returned.
107
+ def gid_to_code(gid)
108
+ @gid_map[gid]
109
+ end
110
+
111
+ # :call-seq:
112
+ # subtable.parse!(io, offset) => true or false
113
+ #
114
+ # Parses the cmap subtable from the IO at the given offset.
115
+ #
116
+ # If the subtable format is supported, the information is used to populate this object and
117
+ # +true+ is returned. Otherwise nothing is done and +false+ is returned.
118
+ def parse(io, offset)
119
+ io.pos = offset
120
+ @format = io.read(2).unpack('n').first
121
+ if [8, 10, 12].include?(@format)
122
+ io.pos += 2
123
+ length, @language = io.read(8).unpack('N2')
124
+ elsif [0, 2, 4, 6].include?(@format)
125
+ length, @language = io.read(4).unpack('n2')
126
+ end
127
+ supported = true
128
+ @code_map, @gid_map = case @format
129
+ when 0 then Format0.parse(io, length)
130
+ when 2 then Format2.parse(io, length)
131
+ when 4 then Format4.parse(io, length)
132
+ when 6 then Format6.parse(io, length)
133
+ when 10 then Format10.parse(io, length)
134
+ when 12 then Format12.parse(io, length)
135
+ else
136
+ supported = false
137
+ [{}, {}]
138
+ end
139
+ supported
140
+ end
141
+
142
+ def inspect #:nodoc:
143
+ "#<#{self.class.name} (#{platform_id}, #{encoding_id}, #{language}, " \
144
+ "#{format.inspect})>"
145
+ end
146
+
147
+
148
+ # Cmap format 0
149
+ module Format0
150
+
151
+ # :call-seq:
152
+ # Format0.parse(io, length) -> code_map
153
+ #
154
+ # Parses the format 0 cmap subtable from the given IO at the current position and
155
+ # returns the contained code map.
156
+ #
157
+ # It is assumed that the first six bytes of the subtable have already been consumed.
158
+ def self.parse(io, length)
159
+ raise HexaPDF::Error, "Invalid length #{length} for cmap format 0" if length != 262
160
+ code_map = io.read(256).unpack('C*')
161
+ gid_map = {}
162
+ code_map.each_with_index {|glyph, index| gid_map[glyph] = index}
163
+ [code_map, gid_map]
164
+ end
165
+
166
+ end
167
+
168
+
169
+ # Cmap format 2
170
+ module Format2
171
+
172
+ SubHeader = Struct.new(:first_code, :entry_count, :id_delta, :first_glyph_index)
173
+
174
+ # :call-seq:
175
+ # Format2.parse(io, length) -> code_map
176
+ #
177
+ # Parses the format 2 cmap subtable from the given IO at the current position and
178
+ # returns the contained code map.
179
+ #
180
+ # It is assumed that the first six bytes of the subtable have already been consumed.
181
+ def self.parse(io, length)
182
+ sub_header_keys = io.read(512).unpack('n*')
183
+ nr_sub_headers = 0
184
+ sub_header_keys.map! do |key|
185
+ nr_sub_headers = key if key > nr_sub_headers
186
+ key / 8
187
+ end
188
+ nr_sub_headers = 1 + nr_sub_headers / 8
189
+
190
+ sub_headers = []
191
+ nr_sub_headers.times do |i|
192
+ h = SubHeader.new(*io.read(8).unpack('n2s>n'))
193
+ # Map the currently stored id_range_offset to the corresponding glyph index by first
194
+ # changing the offset to begin from the position of the first glyph index and then
195
+ # halfing the value since each glyph is a UInt16.
196
+ h.first_glyph_index = (h.first_glyph_index - 2 - 8 * (nr_sub_headers - i - 1)) / 2
197
+ sub_headers << h
198
+ end
199
+ glyph_indexes = io.read(length - 6 - 512 - 8 * nr_sub_headers).unpack('n*')
200
+
201
+ gid_map = {}
202
+ sub_headers.each_with_index do |sub_header, i|
203
+ sub_header.entry_count.times do |j|
204
+ glyph_id = glyph_indexes[sub_header.first_glyph_index + j]
205
+ glyph_id = (glyph_id + sub_header.id_delta) % 65536 if glyph_id != 0
206
+ gid_map[glyph_id] = (sub_header_keys.index(i) << 8) + j + sub_header.first_code
207
+ end
208
+ end
209
+
210
+ [mapper(sub_header_keys, sub_headers, glyph_indexes), gid_map]
211
+ end
212
+
213
+ def self.mapper(sub_header_keys, sub_headers, glyph_indexes) #:nodoc:
214
+ Hash.new do |h, code|
215
+ i = code
216
+ i, j = i.divmod(256) if code > 255
217
+ k = sub_header_keys[i]
218
+ if !k
219
+ glyph_id = 0
220
+ elsif k > 0
221
+ sub_header = sub_headers[k]
222
+ raise HexaPDF::Error, "Second byte of character code missing" if j.nil?
223
+ j -= sub_header.first_code
224
+ if 0 <= j && j < sub_header.entry_count
225
+ glyph_id = glyph_indexes[sub_header.first_glyph_index + j]
226
+ glyph_id = (glyph_id + sub_header.id_delta) % 65536 if glyph_id != 0
227
+ else
228
+ glyph_id = 0
229
+ end
230
+ else
231
+ glyph_id = glyph_indexes[i]
232
+ end
233
+ h[code] = glyph_id unless glyph_id == 0
234
+ end
235
+ end
236
+
237
+
238
+ end
239
+
240
+
241
+ # Cmap format 4
242
+ module Format4
243
+
244
+ # :call-seq:
245
+ # Format4.parse(io, length) -> code_map
246
+ #
247
+ # Parses the format 4 cmap subtable from the given IO at the current position and
248
+ # returns the contained code map.
249
+ #
250
+ # It is assumed that the first six bytes of the subtable have already been consumed.
251
+ def self.parse(io, length)
252
+ seg_count_x2 = io.read(8).unpack('n').first
253
+ end_codes = io.read(seg_count_x2).unpack('n*')
254
+ io.pos += 2
255
+ start_codes = io.read(seg_count_x2).unpack('n*')
256
+ id_deltas = io.read(seg_count_x2).unpack('n*')
257
+ id_range_offsets = io.read(seg_count_x2).unpack('n*').map!.with_index do |offset, idx|
258
+ # Change offsets to indexes, starting from the id_range_offsets array
259
+ offset == 0 ? offset : offset / 2 + idx
260
+ end
261
+ glyph_indexes = io.read(length - 16 - seg_count_x2 * 4).unpack('n*')
262
+ mapper(end_codes, start_codes, id_deltas, id_range_offsets, glyph_indexes)
263
+ end
264
+
265
+ def self.mapper(end_codes, start_codes, id_deltas, id_range_offsets, glyph_indexes) #:nodoc:
266
+ compute_glyph_id = lambda do |index, code|
267
+ offset = id_range_offsets[index]
268
+ if offset != 0
269
+ glyph_id = glyph_indexes[offset - end_codes.length + (code - start_codes[index])]
270
+ glyph_id = (glyph_id + id_deltas[index]) % 65536 if glyph_id != 0
271
+ else
272
+ glyph_id = (code + id_deltas[index]) % 65536
273
+ end
274
+ glyph_id
275
+ end
276
+
277
+ code_map = Hash.new do |h, code|
278
+ i = end_codes.bsearch_index {|c| c >= code}
279
+ if i && start_codes[i] <= code
280
+ glyph_id = compute_glyph_id.call(i, code)
281
+ else
282
+ glyph_id = 0
283
+ end
284
+ h[code] = glyph_id unless glyph_id == 0
285
+ end
286
+
287
+ gid_map = {}
288
+ end_codes.length.times do |i|
289
+ start_codes[i].upto(end_codes[i]) do |code|
290
+ gid_map[compute_glyph_id.call(i, code)] = code
291
+ end
292
+ end
293
+ [code_map, gid_map]
294
+ end
295
+
296
+ end
297
+
298
+
299
+ # Cmap format 6
300
+ module Format6
301
+
302
+ # :call-seq:
303
+ # Format6.parse(io, length) -> code_map
304
+ #
305
+ # Parses the format 6 cmap subtable from the given IO at the current position and
306
+ # returns the contained code map.
307
+ #
308
+ # It is assumed that the first six bytes of the subtable have already been consumed.
309
+ def self.parse(io, _length)
310
+ first_code, entry_count = io.read(4).unpack('n2')
311
+ code_map = io.read(2 * entry_count).unpack('n*')
312
+ gid_map = {}
313
+ code_map = code_map.each_with_index.with_object({}) do |(g, i), hash|
314
+ hash[first_code + i] = g
315
+ gid_map[g] = first_code + i
316
+ end
317
+ [code_map, gid_map]
318
+ end
319
+
320
+ end
321
+
322
+
323
+ # Cmap format 10
324
+ module Format10
325
+
326
+ # :call-seq:
327
+ # Format10.parse(io, length) -> code_map
328
+ #
329
+ # Parses the format 10 cmap subtable from the given IO at the current position and
330
+ # returns the contained code map.
331
+ #
332
+ # It is assumed that the first twelve bytes of the subtable have already been consumed.
333
+ def self.parse(io, _length)
334
+ first_code, entry_count = io.read(8).unpack('N2')
335
+ code_map = io.read(2 * entry_count).unpack('n*')
336
+ gid_map = {}
337
+ code_map = code_map.each_with_index.with_object({}) do |(g, i), hash|
338
+ hash[first_code + i] = g
339
+ gid_map[g] = first_code + i
340
+ end
341
+ [code_map, gid_map]
342
+ end
343
+
344
+ end
345
+
346
+
347
+ # Cmap format 12
348
+ module Format12
349
+
350
+ # :call-seq:
351
+ # Format12.parse(io, length) -> code_map
352
+ #
353
+ # Parses the format 12 cmap subtable from the given IO at the current position and
354
+ # returns the contained code map.
355
+ #
356
+ # It is assumed that the first twelve bytes of the subtable have already been consumed.
357
+ def self.parse(io, _length)
358
+ mapper(io.read(4).unpack('N').first.times.map { io.read(12).unpack('N3') })
359
+ end
360
+
361
+ # The parameter +groups+ is an array containing [start_code, end_code, start_glyph_id]
362
+ # arrays.
363
+ def self.mapper(groups) #:nodoc:
364
+ code_map = Hash.new do |h, code|
365
+ group = groups.bsearch {|g| g[1] >= code}
366
+ h[code] = group[2] + (code - group[0]) if group && group[0] <= code
367
+ end
368
+ groups_by_gid = groups.sort_by {|g| g[2]}
369
+ gid_map = Hash.new do |h, gid|
370
+ group = groups_by_gid.bsearch {|g| g[2] + g[1] - g[0] >= gid}
371
+ h[gid] = group[0] + (gid - group[2]) if group && group[2] <= gid
372
+ end
373
+ [code_map, gid_map]
374
+ end
375
+
376
+ end
377
+
378
+
379
+ end
380
+
381
+ end
382
+ end
383
+ end
384
+ end