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,1113 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ require 'test_helper'
4
+ require_relative 'common'
5
+ require 'hexapdf/content/canvas'
6
+ require 'hexapdf/document'
7
+ require 'hexapdf/content/processor'
8
+ require 'hexapdf/content/parser'
9
+
10
+ describe HexaPDF::Content::Canvas do
11
+ before do
12
+ @processor = TestHelper::OperatorRecorder.new
13
+ @parser = HexaPDF::Content::Parser.new
14
+
15
+ @doc = HexaPDF::Document.new
16
+ @doc.config['graphic_object.arc.max_curves'] = 4
17
+ @page = @doc.pages.add_page
18
+ @canvas = @page.canvas
19
+ end
20
+
21
+ # Asserts that the content string contains the operators.
22
+ def assert_operators(content, operators)
23
+ @processor.recorded_ops.clear
24
+ @parser.parse(content, @processor)
25
+ assert_equal(operators, @processor.recorded_ops)
26
+ end
27
+
28
+ # Asserts that a specific operator is invoked when the block is executed.
29
+ def assert_operator_invoked(op, *args)
30
+ mock = Minitest::Mock.new
31
+ if args.empty?
32
+ mock.expect(:invoke, nil) { true }
33
+ mock.expect(:serialize, '') { true }
34
+ else
35
+ mock.expect(:invoke, nil, [@canvas] + args)
36
+ mock.expect(:serialize, '', [@canvas.instance_variable_get(:@serializer)] + args)
37
+ end
38
+ op_before = @canvas.instance_variable_get(:@operators)[op]
39
+ @canvas.instance_variable_get(:@operators)[op] = mock
40
+ yield
41
+ assert(mock.verify)
42
+ ensure
43
+ @canvas.instance_variable_get(:@operators)[op] = op_before
44
+ end
45
+
46
+ # Asserts that the block raises an error when in one of the given graphics objects.
47
+ def assert_raises_in_graphics_object(*objects, &block)
48
+ objects.each do |graphics_object|
49
+ @canvas.graphics_object = graphics_object
50
+ assert_raises(HexaPDF::Error, &block)
51
+ end
52
+ end
53
+
54
+ describe "contents" do
55
+ it "returns the serialized contents of the canvas operations" do
56
+ @canvas.save_graphics_state {}
57
+ assert_equal("q\nQ\n", @canvas.contents)
58
+ end
59
+ end
60
+
61
+ describe "stream_data" do
62
+ it "it closes an open path object" do
63
+ @canvas.move_to(5, 5)
64
+ assert_equal("5 5 m\nn\n", @canvas.stream_data.fiber.resume)
65
+ end
66
+
67
+ it "it closes an open text object" do
68
+ @canvas.begin_text
69
+ assert_equal("BT\nET\n", @canvas.stream_data.fiber.resume)
70
+ end
71
+
72
+ it "rewinds the graphics state stack" do
73
+ @canvas.save_graphics_state
74
+ @canvas.begin_text
75
+ assert_equal("q\nBT\nET\nQ\n", @canvas.stream_data.fiber.resume)
76
+ end
77
+ end
78
+
79
+ describe "resources" do
80
+ it "returns the resources of the context object" do
81
+ assert_equal(@page.resources, @canvas.resources)
82
+ end
83
+ end
84
+
85
+ describe "save_graphics_state" do
86
+ it "invokes the operator implementation" do
87
+ assert_operator_invoked(:q) { @canvas.save_graphics_state }
88
+ end
89
+
90
+ it "is serialized correctly when no block is used" do
91
+ @canvas.save_graphics_state
92
+ assert_operators(@canvas.contents, [[:save_graphics_state]])
93
+ end
94
+
95
+ it "is serialized correctly when a block is used" do
96
+ @canvas.save_graphics_state { }
97
+ assert_operators(@canvas.contents, [[:save_graphics_state], [:restore_graphics_state]])
98
+ end
99
+
100
+ it "fails if invoked while in an unsupported graphics objects" do
101
+ assert_raises_in_graphics_object(:path, :clipping_path) { @canvas.save_graphics_state }
102
+ end
103
+ end
104
+
105
+ describe "restore_graphics_state" do
106
+ it "invokes the operator implementation" do
107
+ assert_operator_invoked(:Q) { @canvas.restore_graphics_state }
108
+ end
109
+
110
+ it "is serialized correctly" do
111
+ @canvas.graphics_state.save
112
+ @canvas.restore_graphics_state
113
+ assert_operators(@page.contents, [[:restore_graphics_state]])
114
+ end
115
+
116
+ it "fails if invoked while in an unsupported graphics objects" do
117
+ assert_raises_in_graphics_object(:path, :clipping_path) { @canvas.restore_graphics_state }
118
+ end
119
+ end
120
+
121
+ describe "transform" do
122
+ it "invokes the operator implementation" do
123
+ assert_operator_invoked(:cm, 1, 2, 3, 4, 5, 6) { @canvas.transform(1, 2, 3, 4, 5, 6) }
124
+ end
125
+
126
+ it "is serialized correctly when no block is used" do
127
+ @canvas.transform(1, 2, 3, 4, 5, 6)
128
+ assert_operators(@page.contents, [[:concatenate_matrix, [1, 2, 3, 4, 5, 6]]])
129
+ end
130
+
131
+ it "is serialized correctly when a block is used" do
132
+ @canvas.transform(1, 2, 3, 4, 5, 6) {}
133
+ assert_operators(@page.contents, [[:save_graphics_state],
134
+ [:concatenate_matrix, [1, 2, 3, 4, 5, 6]],
135
+ [:restore_graphics_state]])
136
+ end
137
+
138
+ it "fails if invoked while in an unsupported graphics objects" do
139
+ assert_raises_in_graphics_object(:path, :clipping_path) { @canvas.transform(1, 2, 3, 4, 5, 6) }
140
+ end
141
+ end
142
+
143
+ describe "rotate" do
144
+ it "can rotate around the origin" do
145
+ @canvas.rotate(90)
146
+ assert_operators(@page.contents, [[:concatenate_matrix, [0, 1, -1, 0, 0, 0]]])
147
+ end
148
+
149
+ it "can rotate about an arbitrary point" do
150
+ @canvas.rotate(90, origin: [100, 200])
151
+ assert_operators(@page.contents, [[:concatenate_matrix, [0.0, 1.0, -1.0, 0.0, 300.0, 100.0]]])
152
+ end
153
+ end
154
+
155
+ describe "scale" do
156
+ it "can scale from the origin" do
157
+ @canvas.scale(5, 10)
158
+ assert_operators(@page.contents, [[:concatenate_matrix, [5, 0, 0, 10, 0, 0]]])
159
+ end
160
+
161
+ it "can scale from an arbitrary point" do
162
+ @canvas.scale(5, 10, origin: [100, 200])
163
+ assert_operators(@page.contents, [[:concatenate_matrix, [5, 0, 0, 10, -400, -1800]]])
164
+ end
165
+
166
+ it "works with a single scale factor" do
167
+ @canvas.scale(5)
168
+ assert_operators(@page.contents, [[:concatenate_matrix, [5, 0, 0, 5, 0, 0]]])
169
+ end
170
+ end
171
+
172
+ describe "translate" do
173
+ it "translates the origin" do
174
+ @canvas.translate(100, 200)
175
+ assert_operators(@page.contents, [[:concatenate_matrix, [1, 0, 0, 1, 100, 200]]])
176
+ end
177
+ end
178
+
179
+ describe "skew" do
180
+ it "can skew from the origin" do
181
+ @canvas.skew(45, 0)
182
+ assert_operators(@page.contents, [[:concatenate_matrix, [1, 1, 0, 1, 0, 0]]])
183
+ end
184
+
185
+ it "can skew from an arbitrary point" do
186
+ @canvas.skew(45, 0, origin: [100, 200])
187
+ assert_operators(@page.contents, [[:concatenate_matrix, [1, 1, 0, 1, 0, -100]]])
188
+ end
189
+ end
190
+
191
+ describe "private gs_getter_setter" do
192
+ it "returns the current value when used with a nil argument" do
193
+ @canvas.graphics_state.line_width = 5
194
+ assert_equal(5, @canvas.send(:gs_getter_setter, :line_width, :w, nil))
195
+ end
196
+
197
+ it "returns the canvas object when used with a non-nil argument or a block" do
198
+ assert_equal(@canvas, @canvas.send(:gs_getter_setter, :line_width, :w, 15))
199
+ assert_equal(@canvas, @canvas.send(:gs_getter_setter, :line_width, :w, 15) {})
200
+ end
201
+
202
+ it "invokes the operator implementation when a non-nil argument is used" do
203
+ assert_operator_invoked(:w, 5) { @canvas.send(:gs_getter_setter, :line_width, :w, 5) }
204
+ assert_operator_invoked(:w, 15) { @canvas.send(:gs_getter_setter, :line_width, :w, 15) {} }
205
+ end
206
+
207
+ it "doesn't add an operator if the value is equal to the current one" do
208
+ @canvas.send(:gs_getter_setter, :line_width, :w,
209
+ @canvas.send(:gs_getter_setter, :line_width, :w, nil))
210
+ assert_operators(@page.contents, [])
211
+ end
212
+
213
+ it "always saves and restores the graphics state if a block is used" do
214
+ @canvas.send(:gs_getter_setter, :line_width, :w,
215
+ @canvas.send(:gs_getter_setter, :line_width, :w, nil)) {}
216
+ assert_operators(@page.contents, [[:save_graphics_state], [:restore_graphics_state]])
217
+ end
218
+
219
+ it "is serialized correctly when no block is used" do
220
+ @canvas.send(:gs_getter_setter, :line_width, :w, 5)
221
+ assert_operators(@page.contents, [[:set_line_width, [5]]])
222
+ end
223
+
224
+ it "is serialized correctly when a block is used" do
225
+ @canvas.send(:gs_getter_setter, :line_width, :w, 5) do
226
+ @canvas.send(:gs_getter_setter, :line_width, :w, 15)
227
+ end
228
+ assert_operators(@page.contents, [[:save_graphics_state],
229
+ [:set_line_width, [5]],
230
+ [:set_line_width, [15]],
231
+ [:restore_graphics_state]])
232
+ end
233
+
234
+ it "fails if a block is given without an argument" do
235
+ assert_raises(ArgumentError) { @canvas.send(:gs_getter_setter, :line_width, :w, nil) {} }
236
+ end
237
+
238
+ it "fails if invoked while in an unsupported graphics objects" do
239
+ assert_raises_in_graphics_object(:path, :clipping_path) do
240
+ @canvas.send(:gs_getter_setter, :line_width, :w, 5)
241
+ end
242
+ end
243
+ end
244
+
245
+ # Asserts that the method +name+ invoked with +values+ invokes the #gs_getter_setter helper method
246
+ # with the +name+, +operator+ and +expected_value+ as arguments.
247
+ def assert_gs_getter_setter(name, operator, expected_value, *values)
248
+ args = [name, operator, expected_value]
249
+ assert_method_invoked(@canvas, :gs_getter_setter, args, check_block: true) do
250
+ @canvas.send(name, *values) {}
251
+ end
252
+ unless values.compact.empty?
253
+ @canvas.send(name, *values)
254
+ assert_equal(expected_value, @canvas.graphics_state.send(name))
255
+ end
256
+ assert_respond_to(@canvas, name)
257
+ end
258
+
259
+ describe "line_width" do
260
+ it "uses the gs_getter_setter implementation" do
261
+ assert_gs_getter_setter(:line_width, :w, 5, 5)
262
+ assert_gs_getter_setter(:line_width, :w, nil, nil)
263
+ end
264
+ end
265
+
266
+ describe "line_cap_style" do
267
+ it "uses the gs_getter_setter implementation" do
268
+ assert_gs_getter_setter(:line_cap_style, :J, 1, :round)
269
+ assert_gs_getter_setter(:line_cap_style, :J, nil, nil)
270
+ end
271
+ end
272
+
273
+ describe "line_join_style" do
274
+ it "uses the gs_getter_setter implementation" do
275
+ assert_gs_getter_setter(:line_join_style, :j, 1, :round)
276
+ assert_gs_getter_setter(:line_join_style, :j, nil, nil)
277
+ end
278
+ end
279
+
280
+ describe "miter_limit" do
281
+ it "uses the gs_getter_setter implementation" do
282
+ assert_gs_getter_setter(:miter_limit, :M, 15, 15)
283
+ assert_gs_getter_setter(:miter_limit, :M, nil, nil)
284
+ end
285
+ end
286
+
287
+ describe "line_dash_pattern" do
288
+ it "uses the gs_getter_setter implementation" do
289
+ assert_gs_getter_setter(:line_dash_pattern, :d, nil, nil)
290
+ assert_gs_getter_setter(:line_dash_pattern, :d,
291
+ HexaPDF::Content::LineDashPattern.new, 0)
292
+ assert_gs_getter_setter(:line_dash_pattern, :d,
293
+ HexaPDF::Content::LineDashPattern.new([5]), 5)
294
+ assert_gs_getter_setter(:line_dash_pattern, :d,
295
+ HexaPDF::Content::LineDashPattern.new([5], 2), 5, 2)
296
+ assert_gs_getter_setter(:line_dash_pattern, :d,
297
+ HexaPDF::Content::LineDashPattern.new([5, 3], 2), [5, 3], 2)
298
+ assert_gs_getter_setter(:line_dash_pattern, :d,
299
+ HexaPDF::Content::LineDashPattern.new([5, 3], 2),
300
+ HexaPDF::Content::LineDashPattern.new([5, 3], 2))
301
+ end
302
+ end
303
+
304
+ describe "rendering_intent" do
305
+ it "uses the gs_getter_setter implementation" do
306
+ assert_gs_getter_setter(:rendering_intent, :ri, :Perceptual, :Perceptual)
307
+ assert_gs_getter_setter(:rendering_intent, :ri, nil, nil)
308
+ end
309
+ end
310
+
311
+ describe "opacity" do
312
+ it "returns the current values when no argument/nil arguments are provided" do
313
+ assert_equal({fill_alpha: 1.0, stroke_alpha: 1.0}, @canvas.opacity)
314
+ end
315
+
316
+ it "returns the canvas object when at least one non-nil argument is provided" do
317
+ assert_equal(@canvas, @canvas.opacity(fill_alpha: 0.5))
318
+ end
319
+
320
+ it "invokes the operator implementation when at least one non-nil argument is used" do
321
+ assert_operator_invoked(:gs, :GS1) do
322
+ @canvas.opacity(fill_alpha: 1.0, stroke_alpha: 0.5)
323
+ end
324
+ end
325
+
326
+ it "doesn't add an operator if the values are not really changed" do
327
+ @canvas.opacity(fill_alpha: 1.0, stroke_alpha: 1.0)
328
+ assert_operators(@page.contents, [])
329
+ end
330
+
331
+ it "always saves and restores the graphics state if a block is used" do
332
+ @canvas.opacity(fill_alpha: 1.0, stroke_alpha: 1.0) {}
333
+ assert_operators(@page.contents, [[:save_graphics_state], [:restore_graphics_state]])
334
+ end
335
+
336
+ it "adds the needed entry to the /ExtGState resources dictionary" do
337
+ @canvas.graphics_state.alpha_source = true
338
+ @canvas.opacity(fill_alpha: 0.5, stroke_alpha: 0.7)
339
+ assert_equal({Type: :ExtGState, CA: 0.7, ca: 0.5, AIS: false},
340
+ @canvas.resources.ext_gstate(:GS1))
341
+ end
342
+
343
+ it "is serialized correctly when no block is used" do
344
+ @canvas.opacity(fill_alpha: 0.5, stroke_alpha: 0.7)
345
+ assert_operators(@page.contents, [[:set_graphics_state_parameters, [:GS1]]])
346
+ end
347
+
348
+ it "is serialized correctly when a block is used" do
349
+ @canvas.opacity(fill_alpha: 0.5) do
350
+ @canvas.opacity(stroke_alpha: 0.7)
351
+ end
352
+ assert_operators(@page.contents, [[:save_graphics_state],
353
+ [:set_graphics_state_parameters, [:GS1]],
354
+ [:set_graphics_state_parameters, [:GS2]],
355
+ [:restore_graphics_state]])
356
+ end
357
+
358
+ it "fails if a block is given without an argument" do
359
+ assert_raises(ArgumentError) { @canvas.opacity {} }
360
+ end
361
+
362
+ it "fails if invoked while in an unsupported graphics objects" do
363
+ assert_raises_in_graphics_object(:path, :clipping_path) { @canvas.opacity(fill_alpha: 1.0) }
364
+ end
365
+ end
366
+
367
+ describe "private color_getter_setter" do
368
+ def invoke(*params, &block)
369
+ @canvas.send(:color_getter_setter, :stroke_color, params, :RG, :G, :K, :CS, :SCN, &block)
370
+ end
371
+
372
+ it "returns the current value when used with no argument" do
373
+ color = @canvas.graphics_state.stroke_color
374
+ assert_equal(color, invoke)
375
+ end
376
+
377
+ it "returns the canvas when used with a non-nil argument and no block" do
378
+ assert_equal(@canvas, invoke(255))
379
+ assert_equal(@canvas, invoke(255) {})
380
+ end
381
+
382
+ it "doesn't add an operator if the value is equal to the current one" do
383
+ invoke(0.0)
384
+ assert_operators(@page.contents, [])
385
+ end
386
+
387
+ it "always saves and restores the graphics state if a block is used" do
388
+ invoke(0.0) {}
389
+ assert_operators(@page.contents, [[:save_graphics_state], [:restore_graphics_state]])
390
+ end
391
+
392
+ it "adds an unknown color space to the resource dictionary" do
393
+ invoke(HexaPDF::Content::ColorSpace::Universal.new([:Pattern, :DeviceRGB]).color(:Name))
394
+ assert_equal([:Pattern, :DeviceRGB], @page.resources.color_space(:CS1).definition)
395
+ end
396
+
397
+ it "is serialized correctly when no block is used" do
398
+ invoke(102)
399
+ invoke([102])
400
+ invoke("6600FF")
401
+ invoke(102, 0, 255)
402
+ invoke(0, 20, 40, 80)
403
+ invoke(HexaPDF::Content::ColorSpace::Universal.new([:Pattern]).color(:Name))
404
+ assert_operators(@page.contents, [[:set_device_gray_stroking_color, [0.4]],
405
+ [:set_device_rgb_stroking_color, [0.4, 0, 1]],
406
+ [:set_device_cmyk_stroking_color, [0, 0.2, 0.4, 0.8]],
407
+ [:set_stroking_color_space, [:CS1]],
408
+ [:set_stroking_color, [:Name]]])
409
+ end
410
+
411
+ it "is serialized correctly when a block is used" do
412
+ invoke(102) { invoke(255) }
413
+ assert_operators(@page.contents, [[:save_graphics_state],
414
+ [:set_device_gray_stroking_color, [0.4]],
415
+ [:set_device_gray_stroking_color, [1.0]],
416
+ [:restore_graphics_state]])
417
+ end
418
+
419
+ it "fails if a block is given without an argument" do
420
+ assert_raises(ArgumentError) { invoke {} }
421
+ end
422
+
423
+ it "fails if an unsupported number of component values is provided" do
424
+ assert_raises(ArgumentError) { invoke(5, 5) }
425
+ end
426
+
427
+ it "fails if invoked while in an unsupported graphics objects" do
428
+ assert_raises_in_graphics_object(:path, :clipping_path) { invoke(0.5) }
429
+ end
430
+ end
431
+
432
+ # Asserts that the method +name+ invoked with +values+ invokes the #color_getter_setter helper
433
+ # method with the +expected_values+ as arguments.
434
+ def assert_color_getter_setter(name, expected_values, *values)
435
+ assert_method_invoked(@canvas, :color_getter_setter, expected_values, check_block: true) do
436
+ @canvas.send(name, *values) {}
437
+ end
438
+ end
439
+
440
+ describe "stroke_color" do
441
+ it "uses the color_getter_setter implementation" do
442
+ assert_color_getter_setter(:stroke_color, [:stroke_color, [255], :RG, :G, :K, :CS, :SCN], 255)
443
+ assert_color_getter_setter(:stroke_color, [:stroke_color, [], :RG, :G, :K, :CS, :SCN])
444
+ end
445
+ end
446
+
447
+ describe "fill_color" do
448
+ it "uses the color_getter_setter implementation" do
449
+ assert_color_getter_setter(:fill_color, [:fill_color, [255], :rg, :g, :k, :cs, :scn], 255)
450
+ assert_color_getter_setter(:fill_color, [:fill_color, [], :rg, :g, :k, :cs, :scn])
451
+ end
452
+ end
453
+
454
+ describe "move_to" do
455
+ it "invokes the operator implementation" do
456
+ assert_operator_invoked(:m, 5, 6) { @canvas.move_to(5, 6) }
457
+ end
458
+
459
+ it "returns the canvas object" do
460
+ assert_equal(@canvas, @canvas.move_to(5, 6))
461
+ end
462
+
463
+ it "sets the current point correctly" do
464
+ @canvas.move_to(5, 6)
465
+ assert_equal([5, 6], @canvas.current_point)
466
+ end
467
+
468
+ it "fails if invoked while in an unsupported graphics objects" do
469
+ assert_raises_in_graphics_object(:clipping_path) { @canvas.move_to(5, 6) }
470
+ end
471
+ end
472
+
473
+ describe "line_to" do
474
+ before do
475
+ @canvas.graphics_object = :path
476
+ end
477
+
478
+ it "invokes the operator implementation" do
479
+ assert_operator_invoked(:l, 5, 6) { @canvas.line_to(5, 6) }
480
+ end
481
+
482
+ it "returns the canvas object" do
483
+ assert_equal(@canvas, @canvas.line_to(5, 6))
484
+ end
485
+
486
+ it "sets the current point correctly" do
487
+ @canvas.line_to(5, 6)
488
+ assert_equal([5, 6], @canvas.current_point)
489
+ end
490
+
491
+ it "fails if invoked while in an unsupported graphics objects" do
492
+ assert_raises_in_graphics_object(:none, :text, :clipping_path) { @canvas.line_to(5, 6) }
493
+ end
494
+ end
495
+
496
+ describe "curve_to" do
497
+ before do
498
+ @canvas.graphics_object = :path
499
+ end
500
+
501
+ it "invokes the operator implementation" do
502
+ assert_operator_invoked(:c, 5, 6, 7, 8, 9, 10) { @canvas.curve_to(9, 10, p1: [5, 6], p2: [7, 8]) }
503
+ assert_operator_invoked(:v, 7, 8, 9, 10) { @canvas.curve_to(9, 10, p2: [7, 8]) }
504
+ assert_operator_invoked(:y, 5, 6, 9, 10) { @canvas.curve_to(9, 10, p1: [5, 6]) }
505
+ end
506
+
507
+ it "returns the canvas object" do
508
+ assert_equal(@canvas, @canvas.curve_to(5, 6, p1: [7, 8]))
509
+ end
510
+
511
+ it "sets the current point correctly" do
512
+ @canvas.curve_to(5, 6, p1: [9, 10])
513
+ assert_equal([5, 6], @canvas.current_point)
514
+ end
515
+
516
+ it "raises an error if both control points are omitted" do
517
+ assert_raises(ArgumentError) { @canvas.curve_to(9, 10) }
518
+ end
519
+
520
+ it "fails if invoked while in an unsupported graphics objects" do
521
+ assert_raises_in_graphics_object(:none, :text, :clipping_path) do
522
+ @canvas.curve_to(5, 6, p1: [7, 8])
523
+ end
524
+ end
525
+ end
526
+
527
+ describe "rectangle" do
528
+ it "invokes the operator implementation when radius == 0" do
529
+ assert_operator_invoked(:re, 5, 10, 15, 20) { @canvas.rectangle(5, 10, 15, 20) }
530
+ end
531
+
532
+ it "invokes the polygon method when radius != 0" do
533
+ args = [0, 0, 10, 0, 10, 10, 0, 10, radius: 5]
534
+ assert_method_invoked(@canvas, :polygon, args) do
535
+ @canvas.rectangle(0, 0, 10, 10, radius: 5)
536
+ end
537
+ end
538
+
539
+ it "returns the canvas object" do
540
+ assert_equal(@canvas, @canvas.rectangle(5, 6, 7, 8))
541
+ end
542
+
543
+ it "sets the current point correctly" do
544
+ @canvas.rectangle(5, 6, 7, 8)
545
+ assert_equal([5, 6], @canvas.current_point)
546
+ end
547
+
548
+ it "fails if invoked while in an unsupported graphics objects" do
549
+ assert_raises_in_graphics_object(:clipping_path) { @canvas.rectangle(5, 6, 7, 8) }
550
+ end
551
+ end
552
+
553
+ describe "close_subpath" do
554
+ before do
555
+ @canvas.graphics_object = :path
556
+ end
557
+
558
+ it "invokes the operator implementation" do
559
+ assert_operator_invoked(:h) { @canvas.close_subpath }
560
+ end
561
+
562
+ it "returns the canvas object" do
563
+ assert_equal(@canvas, @canvas.close_subpath)
564
+ end
565
+
566
+ it "sets the current point correctly" do
567
+ @canvas.move_to(1, 1)
568
+ @canvas.line_to(10, 10)
569
+ @canvas.close_subpath
570
+ assert_equal([1, 1], @canvas.current_point)
571
+ end
572
+
573
+ it "fails if invoked while in an unsupported graphics objects" do
574
+ assert_raises_in_graphics_object(:none, :text) { @canvas.close_subpath }
575
+ end
576
+ end
577
+
578
+ describe "line" do
579
+ it "serializes correctly" do
580
+ @canvas.line(1, 2, 3, 4)
581
+ assert_operators(@canvas.contents, [[:move_to, [1, 2]], [:line_to, [3, 4]]])
582
+ end
583
+
584
+ it "returns the canvas object" do
585
+ assert_equal(@canvas, @canvas.line(1, 2, 3, 4))
586
+ end
587
+ end
588
+
589
+ describe "polyline" do
590
+ it "serializes correctly" do
591
+ @canvas.polyline(1, 2, 3, 4, 5, 6)
592
+ assert_operators(@canvas.contents, [[:move_to, [1, 2]], [:line_to, [3, 4]], [:line_to, [5, 6]]])
593
+ end
594
+
595
+ it "returns the canvas object" do
596
+ assert_equal(@canvas, @canvas.polyline(1, 2, 3, 4))
597
+ end
598
+
599
+ it "fails if not enought points are supplied" do
600
+ assert_raises(ArgumentError) { @canvas.polyline(5, 6) }
601
+ end
602
+
603
+ it "fails if a y-coordinate is missing" do
604
+ assert_raises(ArgumentError) { @canvas.polyline(5, 6, 7, 8, 9) }
605
+ end
606
+ end
607
+
608
+ describe "polygon" do
609
+ it "serializes correctly with no radius" do
610
+ @canvas.polygon(1, 2, 3, 4, 5, 6)
611
+ assert_operators(@canvas.contents, [[:move_to, [1, 2]], [:line_to, [3, 4]],
612
+ [:line_to, [5, 6]], [:close_subpath]])
613
+ end
614
+
615
+ it "serializes correctly with a radius" do
616
+ @canvas.polygon(-1, -1, -1, 1, 1, 1, 1, -1, radius: 1)
617
+ k = @canvas.class::KAPPA.round(6)
618
+ assert_operators(@canvas.contents, [[:move_to, [-1, 0]],
619
+ [:line_to, [-1, 0]], [:curve_to, [-1, k, -k, 1, 0, 1]],
620
+ [:line_to, [0, 1]], [:curve_to, [k, 1, 1, k, 1, 0]],
621
+ [:line_to, [1, 0]], [:curve_to, [1, -k, k, -1, 0, -1]],
622
+ [:line_to, [0, -1]], [:curve_to, [-k, -1, -1, -k, -1, 0]],
623
+ [:close_subpath]])
624
+ end
625
+
626
+ it "returns the canvas object" do
627
+ assert_equal(@canvas, @canvas.polyline(1, 2, 3, 4, 5, 6))
628
+ end
629
+ end
630
+
631
+ describe "circle" do
632
+ it "uses arc for the hard work" do
633
+ assert_method_invoked(@canvas, :arc, [5, 6, a: 7]) do
634
+ @canvas.graphics_object = :path
635
+ @canvas.circle(5, 6, 7)
636
+ end
637
+ end
638
+
639
+ it "serializes correctly" do
640
+ @canvas.circle(0, 0, 1)
641
+ @processor.recorded_ops.clear
642
+ @parser.parse(@canvas.contents, @processor)
643
+ assert_equal([:move_to, :curve_to, :curve_to, :curve_to, :curve_to, :close_subpath],
644
+ @processor.recorded_ops.map(&:first))
645
+ end
646
+
647
+ it "returns the canvas object" do
648
+ assert_equal(@canvas, @canvas.circle(1, 2, 3))
649
+ end
650
+ end
651
+
652
+ describe "ellipse" do
653
+ it "uses arc for the hard work" do
654
+ assert_method_invoked(@canvas, :ellipse, [5, 6, a: 7, b: 5, inclination: 10]) do
655
+ @canvas.ellipse(5, 6, a: 7, b: 5, inclination: 10)
656
+ end
657
+ end
658
+
659
+ it "serializes correctly" do
660
+ @canvas.ellipse(0, 0, a: 10, b: 5, inclination: 10)
661
+ @processor.recorded_ops.clear
662
+ @parser.parse(@canvas.contents, @processor)
663
+ assert_equal([:move_to, :curve_to, :curve_to, :curve_to, :curve_to, :close_subpath],
664
+ @processor.recorded_ops.map(&:first))
665
+ end
666
+
667
+ it "returns the canvas object" do
668
+ assert_equal(@canvas, @canvas.circle(1, 2, 3))
669
+ end
670
+ end
671
+
672
+ describe "arc" do
673
+ it "serializes correctly" do
674
+ @canvas.arc(0, 0, a: 1, b: 1, start_angle: 0, end_angle: 360, inclination: 0)
675
+ @canvas.arc(0, 0, a: 1, b: 1, start_angle: 0, end_angle: 360, clockwise: true, inclination: 0)
676
+ assert_operators(@canvas.contents, [[:move_to, [1, 0]],
677
+ [:curve_to, [1, 0.548584, 0.548584, 1, 0, 1]],
678
+ [:curve_to, [-0.548584, 1, -1, 0.548584, -1, 0]],
679
+ [:curve_to, [-1, -0.548584, -0.548584, -1, 0, -1]],
680
+ [:curve_to, [0.548584, -1, 1, -0.548584, 1, 0]],
681
+ [:move_to, [1, 0]],
682
+ [:curve_to, [1, -0.548584, 0.548584, -1, 0, -1]],
683
+ [:curve_to, [-0.548584, -1, -1, -0.548584, -1, 0]],
684
+ [:curve_to, [-1, 0.548584, -0.548584, 1, 0, 1]],
685
+ [:curve_to, [0.548584, 1, 1, 0.548584, 1, 0]]])
686
+ end
687
+
688
+ it "returns the canvas object" do
689
+ assert_equal(@canvas, @canvas.arc(1, 2, a: 3))
690
+ end
691
+ end
692
+
693
+ describe "graphic_object" do
694
+ it "returns a new graphic object given a name" do
695
+ arc = @canvas.graphic_object(:arc)
696
+ assert_respond_to(arc, :draw)
697
+ arc1 = @canvas.graphic_object(:arc)
698
+ refute_same(arc, arc1)
699
+ end
700
+
701
+ it "returns a configured graphic object given a name" do
702
+ arc = @canvas.graphic_object(:arc, cx: 10)
703
+ assert_equal(10, arc.cx)
704
+ end
705
+
706
+ it "reconfigures the given graphic object" do
707
+ arc = @canvas.graphic_object(:arc)
708
+ arc1 = @canvas.graphic_object(arc, cx: 10)
709
+ assert_same(arc, arc1)
710
+ assert_equal(10, arc.cx)
711
+ end
712
+ end
713
+
714
+ describe "draw" do
715
+ it "draws the, optionally configured, graphic object onto the canvas" do
716
+ obj = Object.new
717
+ obj.define_singleton_method(:options) { @options }
718
+ obj.define_singleton_method(:configure) {|**kwargs| @options = kwargs; self}
719
+ obj.define_singleton_method(:draw) {|canvas| canvas.move_to(@options[:x], @options[:y])}
720
+ @canvas.draw(obj, x: 5, y: 6)
721
+ assert_operators(@canvas.contents, [[:move_to, [5, 6]]])
722
+ end
723
+
724
+ it "returns the canvas object" do
725
+ assert_equal(@canvas, @canvas.draw(:arc))
726
+ end
727
+ end
728
+
729
+ describe "path painting methods" do
730
+ before do
731
+ @canvas.graphics_object = :path
732
+ end
733
+
734
+ it "invokes the respective operator implementation" do
735
+ assert_operator_invoked(:S) { @canvas.stroke }
736
+ assert_operator_invoked(:s) { @canvas.close_stroke }
737
+ assert_operator_invoked(:f) { @canvas.fill(:nonzero) }
738
+ assert_operator_invoked(:'f*') { @canvas.fill(:even_odd) }
739
+ assert_operator_invoked(:B) { @canvas.fill_stroke(:nonzero) }
740
+ assert_operator_invoked(:'B*') { @canvas.fill_stroke(:even_odd) }
741
+ assert_operator_invoked(:b) { @canvas.close_fill_stroke(:nonzero) }
742
+ assert_operator_invoked(:'b*') { @canvas.close_fill_stroke(:even_odd) }
743
+ assert_operator_invoked(:n) { @canvas.end_path }
744
+ end
745
+
746
+ it "returns the canvas object" do
747
+ [:stroke, :close_stroke, :fill, :fill_stroke, :close_fill_stroke, :end_path].each do |m|
748
+ @canvas.graphics_object = :path
749
+ assert_equal(@canvas, @canvas.send(m))
750
+ end
751
+ end
752
+
753
+ it "fails if invoked while in an unsupported graphics objects" do
754
+ [:stroke, :close_stroke, :fill, :fill_stroke, :close_fill_stroke, :end_path].each do |m|
755
+ assert_raises_in_graphics_object(:none, :text) { @canvas.send(m) }
756
+ end
757
+ end
758
+ end
759
+
760
+ describe "clip_path" do
761
+ before do
762
+ @canvas.graphics_object = :path
763
+ end
764
+
765
+ it "invokes the respective operator implementation" do
766
+ assert_operator_invoked(:W) { @canvas.clip_path(:nonzero) }
767
+ assert_operator_invoked(:'W*') { @canvas.clip_path(:even_odd) }
768
+ end
769
+
770
+ it "returns the canvas object" do
771
+ assert_equal(@canvas, @canvas.clip_path)
772
+ end
773
+
774
+ it "fails if invoked while in an unsupported graphics objects" do
775
+ assert_raises_in_graphics_object(:none, :text, :clipping_path) { @canvas.clip_path }
776
+ end
777
+ end
778
+
779
+ describe "xobject" do
780
+ before do
781
+ @image = @doc.add(Subtype: :Image, Width: 10, Height: 5)
782
+ @image.source_path = File.join(TEST_DATA_DIR, 'images', 'gray.jpg')
783
+ @form = @doc.add(Subtype: :Form, BBox: [100, 50, 200, 100])
784
+ end
785
+
786
+ it "can use any xobject specified via a filename" do
787
+ xobject = @canvas.xobject(@image.source_path, at: [0, 0])
788
+ assert_equal(xobject, @page.resources.xobject(:XO1))
789
+ end
790
+
791
+ it "can use any xobject specified via an IO object" do
792
+ File.open(@image.source_path, 'rb') do |file|
793
+ xobject = @canvas.xobject(file, at: [0, 0])
794
+ assert_equal(xobject, @page.resources.xobject(:XO1))
795
+ end
796
+ end
797
+
798
+ it "can use an already existing xobject" do
799
+ xobject = @canvas.xobject(@image, at: [0, 0])
800
+ assert_equal(xobject, @page.resources.xobject(:XO1))
801
+ end
802
+
803
+ it "correctly serializes the image with no options" do
804
+ @canvas.xobject(@image, at: [1, 2])
805
+ assert_operators(@page.contents, [[:save_graphics_state],
806
+ [:concatenate_matrix, [10, 0, 0, 5, 1, 2]],
807
+ [:paint_xobject, [:XO1]],
808
+ [:restore_graphics_state]])
809
+ end
810
+
811
+ it "correctly serializes the image with just the width given" do
812
+ @canvas.image(@image, at: [1, 2], width: 20)
813
+ assert_operators(@page.contents, [[:save_graphics_state],
814
+ [:concatenate_matrix, [20, 0, 0, 10, 1, 2]],
815
+ [:paint_xobject, [:XO1]],
816
+ [:restore_graphics_state]])
817
+ end
818
+
819
+ it "correctly serializes the image with just the height given" do
820
+ @canvas.image(@image, at: [1, 2], height: 10)
821
+ assert_operators(@page.contents, [[:save_graphics_state],
822
+ [:concatenate_matrix, [20, 0, 0, 10, 1, 2]],
823
+ [:paint_xobject, [:XO1]],
824
+ [:restore_graphics_state]])
825
+ end
826
+
827
+ it "correctly serializes the image with both width and height given" do
828
+ @canvas.image(@image, at: [1, 2], width: 10, height: 20)
829
+ assert_operators(@page.contents, [[:save_graphics_state],
830
+ [:concatenate_matrix, [10, 0, 0, 20, 1, 2]],
831
+ [:paint_xobject, [:XO1]],
832
+ [:restore_graphics_state]])
833
+ end
834
+
835
+ it "correctly serializes the form with no options" do
836
+ @canvas.xobject(@form, at: [1, 2])
837
+ assert_operators(@page.contents, [[:save_graphics_state],
838
+ [:concatenate_matrix, [1, 0, 0, 1, -99, -48]],
839
+ [:paint_xobject, [:XO1]],
840
+ [:restore_graphics_state]])
841
+ end
842
+
843
+ it "correctly serializes the form with just the width given" do
844
+ @canvas.image(@form, at: [1, 2], width: 50)
845
+ assert_operators(@page.contents, [[:save_graphics_state],
846
+ [:concatenate_matrix, [0.5, 0, 0, 0.5, -99, -48]],
847
+ [:paint_xobject, [:XO1]],
848
+ [:restore_graphics_state]])
849
+ end
850
+
851
+ it "correctly serializes the form with just the height given" do
852
+ @canvas.image(@form, at: [1, 2], height: 10)
853
+ assert_operators(@page.contents, [[:save_graphics_state],
854
+ [:concatenate_matrix, [0.2, 0, 0, 0.2, -99, -48]],
855
+ [:paint_xobject, [:XO1]],
856
+ [:restore_graphics_state]])
857
+ end
858
+
859
+ it "correctly serializes the form with both width and height given" do
860
+ @canvas.image(@form, at: [1, 2], width: 50, height: 10)
861
+ assert_operators(@page.contents, [[:save_graphics_state],
862
+ [:concatenate_matrix, [0.5, 0, 0, 0.2, -99, -48]],
863
+ [:paint_xobject, [:XO1]],
864
+ [:restore_graphics_state]])
865
+ end
866
+ end
867
+
868
+ describe "character_spacing" do
869
+ it "uses the gs_getter_setter implementation" do
870
+ assert_gs_getter_setter(:character_spacing, :Tc, 0.25, 0.25)
871
+ assert_gs_getter_setter(:character_spacing, :Tc, nil, nil)
872
+ end
873
+ end
874
+
875
+ describe "word_spacing" do
876
+ it "uses the gs_getter_setter implementation" do
877
+ assert_gs_getter_setter(:word_spacing, :Tw, 0.25, 0.25)
878
+ assert_gs_getter_setter(:word_spacing, :Tw, nil, nil)
879
+ end
880
+ end
881
+
882
+ describe "horizontal_scaling" do
883
+ it "uses the gs_getter_setter implementation" do
884
+ assert_gs_getter_setter(:horizontal_scaling, :Tz, 50, 50)
885
+ assert_gs_getter_setter(:horizontal_scaling, :Tz, nil, nil)
886
+ end
887
+ end
888
+
889
+ describe "leading" do
890
+ it "uses the gs_getter_setter implementation" do
891
+ assert_gs_getter_setter(:leading, :TL, 15, 15)
892
+ assert_gs_getter_setter(:leading, :TL, nil, nil)
893
+ end
894
+ end
895
+
896
+ describe "text_rendering_mode" do
897
+ it "uses the gs_getter_setter implementation" do
898
+ assert_gs_getter_setter(:text_rendering_mode, :Tr, 0, :fill)
899
+ assert_gs_getter_setter(:text_rendering_mode, :Tr, nil, nil)
900
+ end
901
+ end
902
+
903
+ describe "text_rise" do
904
+ it "uses the gs_getter_setter implementation" do
905
+ assert_gs_getter_setter(:text_rise, :Ts, 15, 15)
906
+ assert_gs_getter_setter(:text_rise, :Ts, nil, nil)
907
+ end
908
+ end
909
+
910
+ describe "begin_text" do
911
+ it "invokes the operator implementation" do
912
+ assert_operator_invoked(:BT) { @canvas.begin_text }
913
+ end
914
+
915
+ it "serializes correctly" do
916
+ @canvas.begin_text
917
+ @canvas.begin_text
918
+ @canvas.begin_text(force_new: true)
919
+ @parser.parse(@canvas.contents, @processor)
920
+ assert_equal([:begin_text, :end_text, :begin_text], @processor.recorded_ops.map(&:first))
921
+ end
922
+
923
+ it "returns the canvas object" do
924
+ assert_equal(@canvas, @canvas.begin_text)
925
+ end
926
+
927
+ it "fails if the current graphics object doesn't allow a new text object" do
928
+ assert_raises(HexaPDF::Error) do
929
+ @canvas.graphics_object = :path
930
+ @canvas.begin_text
931
+ end
932
+ end
933
+
934
+ it "fails if invoked while in an unsupported graphics objects" do
935
+ assert_raises_in_graphics_object(:path, :clipping_path) { @canvas.begin_text }
936
+ end
937
+ end
938
+
939
+ describe "end_text" do
940
+ it "invokes the operator implementation" do
941
+ @canvas.graphics_object = :text
942
+ assert_operator_invoked(:ET) { @canvas.end_text }
943
+ end
944
+
945
+ it "serializes correctly" do
946
+ @canvas.end_text
947
+ @canvas.begin_text
948
+ @canvas.end_text
949
+ @canvas.end_text
950
+ @parser.parse(@page.contents, @processor)
951
+ assert_equal([:begin_text, :end_text], @processor.recorded_ops.map(&:first))
952
+ end
953
+
954
+ it "returns the canvas object" do
955
+ assert_equal(@canvas, @canvas.begin_text)
956
+ end
957
+
958
+ it "fails if invoked while in an unsupported graphics objects" do
959
+ assert_raises_in_graphics_object(:path, :clipping_path) { @canvas.end_text }
960
+ end
961
+ end
962
+
963
+ describe "text_matrix" do
964
+ it "invokes the operator implementation" do
965
+ @canvas.text_matrix(1, 2, 3, 4, 5, 6)
966
+ assert_operators(@canvas.contents, [[:begin_text],
967
+ [:set_text_matrix, [1, 2, 3, 4, 5,6]]])
968
+ end
969
+
970
+ it "returns the canvas object" do
971
+ assert_equal(@canvas, @canvas.text_matrix(1, 1, 1, 1, 1, 1))
972
+ end
973
+ end
974
+
975
+ describe "move_text_cursor" do
976
+ describe "invokes the operator implementation" do
977
+ it "moves to the next line" do
978
+ @canvas.move_text_cursor
979
+ assert_operators(@canvas.contents, [[:begin_text],
980
+ [:move_text_next_line]])
981
+ end
982
+
983
+ it "moves to the next line with an offset" do
984
+ @canvas.move_text_cursor(offset: [5, 10], absolute: false)
985
+ assert_operators(@canvas.contents, [[:begin_text],
986
+ [:move_text, [5, 10]]])
987
+ end
988
+
989
+ it "moves to an absolute position" do
990
+ @canvas.move_text_cursor(offset: [5, 10], absolute: true)
991
+ assert_operators(@canvas.contents, [[:begin_text],
992
+ [:set_text_matrix, [1, 0, 0, 1, 5, 10]]])
993
+ end
994
+ end
995
+
996
+ it "returns the canvas object" do
997
+ assert_equal(@canvas, @canvas.move_text_cursor)
998
+ end
999
+ end
1000
+
1001
+ describe "text_cursor" do
1002
+ it "returns the text cursor position" do
1003
+ @canvas.move_text_cursor(offset: [5, 10])
1004
+ assert_equal([5, 10], @canvas.text_cursor)
1005
+ end
1006
+
1007
+ it "fails if invoked outside a text object" do
1008
+ assert_raises_in_graphics_object(:none, :path, :clipping_path) { @canvas.text_cursor }
1009
+ end
1010
+ end
1011
+
1012
+ describe "font" do
1013
+ it "returns the set font" do
1014
+ assert_nil(@canvas.font)
1015
+ @canvas.font("Times", size: 10)
1016
+ assert_same(@doc.fonts.load("Times"), @canvas.font)
1017
+ @canvas.font("Helvetica", size: 10)
1018
+ assert_operators(@canvas.contents, [[:set_font_and_size, [:F1, 10]],
1019
+ [:set_leading, [12.0]],
1020
+ [:set_font_and_size, [:F2, 10]]])
1021
+ end
1022
+
1023
+ it "sets the font and optionally the font size" do
1024
+ @canvas.font("Times", size: 12, variant: :italic)
1025
+ assert_same(@doc.fonts.load("Times", variant: :italic), @canvas.font)
1026
+ assert_equal(12, @canvas.font_size)
1027
+ @canvas.font("Helvetica")
1028
+ assert_equal(12, @canvas.font_size)
1029
+ end
1030
+ end
1031
+
1032
+ describe "font_size" do
1033
+ it "returns the set font size" do
1034
+ assert_equal(0, @canvas.font_size)
1035
+ @canvas.font("Times", size: 10) # calls #font_size
1036
+ assert_equal(10, @canvas.font_size)
1037
+ end
1038
+
1039
+ it "sets the font size and, optionally, the leading" do
1040
+ @canvas.font("Times", size: 10)
1041
+ assert_equal(10, @canvas.font_size)
1042
+ assert_equal(12, @canvas.leading)
1043
+ @canvas.font_size(10, leading: 20)
1044
+ assert_equal(10, @canvas.font_size)
1045
+ assert_equal(20, @canvas.leading)
1046
+ @canvas.font_size(10, leading: nil)
1047
+ assert_equal(10, @canvas.font_size)
1048
+ assert_equal(20, @canvas.leading)
1049
+ end
1050
+
1051
+ it "fails if no valid font is already set" do
1052
+ assert_raises(HexaPDF::Error) { @canvas.font_size(10) }
1053
+ end
1054
+ end
1055
+
1056
+ describe "show_glyphs" do
1057
+ it "serializes correctly" do
1058
+ @canvas.font("Times", size: 20)
1059
+ @canvas.horizontal_scaling(200)
1060
+ @canvas.character_spacing(1)
1061
+ @canvas.word_spacing(2)
1062
+
1063
+ font = @canvas.font
1064
+ @canvas.show_glyphs(font.decode_utf8("Hal lo").insert(2, -35).insert(0, -10))
1065
+ assert_in_delta(116.68, @canvas.text_cursor[0])
1066
+ assert_equal(0, @canvas.text_cursor[1])
1067
+ @canvas.font_size(10)
1068
+ @canvas.show_glyphs(font.decode_utf8("Hal"))
1069
+ assert_operators(@canvas.contents, [[:set_font_and_size, [:F1, 20]],
1070
+ [:set_leading, [24]],
1071
+ [:set_horizontal_scaling, [200]],
1072
+ [:set_character_spacing, [1]],
1073
+ [:set_word_spacing, [2]],
1074
+ [:begin_text],
1075
+ [:show_text_with_positioning, [['', -10, "Ha", -35, "l lo"]]],
1076
+ [:set_font_and_size, [:F1, 10]],
1077
+ [:set_leading, [12]],
1078
+ [:show_text_with_positioning, [["Hal"]]],
1079
+ ])
1080
+ end
1081
+ end
1082
+
1083
+ describe "text" do
1084
+ it "sets the text cursor position if instructed" do
1085
+ @canvas.font("Times", size: 10)
1086
+ @canvas.text("Hallo", at: [100, 100])
1087
+ assert_operators(@canvas.contents, [[:set_font_and_size, [:F1, 10]],
1088
+ [:set_leading, [12]],
1089
+ [:begin_text],
1090
+ [:set_text_matrix, [1, 0, 0, 1, 100, 100]],
1091
+ [:show_text_with_positioning, [["Hallo"]]],
1092
+ ])
1093
+ end
1094
+
1095
+ it "shows text, possibly split over multiple lines" do
1096
+ @canvas.font("Times", size: 10)
1097
+ @canvas.text("H\u{D A}H\u{A}H\u{B}H\u{c}H\u{D}H\u{85}H\u{2028}H\u{2029}H")
1098
+ assert_operators(@canvas.contents, [[:set_font_and_size, [:F1, 10]],
1099
+ [:set_leading, [12]],
1100
+ [:begin_text],
1101
+ [:show_text_with_positioning, [["H"]]], [:move_text_next_line],
1102
+ [:show_text_with_positioning, [["H"]]], [:move_text_next_line],
1103
+ [:show_text_with_positioning, [["H"]]], [:move_text_next_line],
1104
+ [:show_text_with_positioning, [["H"]]], [:move_text_next_line],
1105
+ [:show_text_with_positioning, [["H"]]], [:move_text_next_line],
1106
+ [:show_text_with_positioning, [["H"]]], [:move_text_next_line],
1107
+ [:show_text_with_positioning, [["H"]]], [:move_text_next_line],
1108
+ [:show_text_with_positioning, [["H"]]], [:move_text_next_line],
1109
+ [:show_text_with_positioning, [["H"]]],
1110
+ ])
1111
+ end
1112
+ end
1113
+ end