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,1974 @@
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/content/graphics_state'
35
+ require 'hexapdf/content/operator'
36
+ require 'hexapdf/serializer'
37
+ require 'hexapdf/utils/math_helpers'
38
+ require 'hexapdf/content/graphic_object'
39
+ require 'hexapdf/stream'
40
+
41
+ module HexaPDF
42
+ module Content
43
+
44
+ # This class provides the basic drawing operations supported by PDF.
45
+ #
46
+ # == General Information
47
+ #
48
+ # A canvas object is used for modifying content streams on a level higher than text. It would
49
+ # be possible to write a content stream by hand since PDF uses a simplified reversed polish
50
+ # notation for specifying operators: First come the operands, then comes the operator and no
51
+ # operator returns any result. However, it is easy to make mistakes this way and one has to
52
+ # know all operators and their operands.
53
+ #
54
+ # This is rather tedious and therefore this class exists. It allows one to modify a content
55
+ # stream by invoking methods that should be familiar to anyone that has ever used a graphic
56
+ # API. There are methods for moving the current point, drawing lines and curves, setting the
57
+ # color, line width and so on.
58
+ #
59
+ # The PDF operators themselves are implemented as classes, see Operator. The canvas class uses
60
+ # the Operator::BaseOperator#invoke and Operator::BaseOperator#serialize methods for applying
61
+ # changes and serialization, with one exception: color setters don't invoke the corresponding
62
+ # operator implementation but directly work on the graphics state.
63
+ #
64
+ #
65
+ # == PDF Graphics
66
+ #
67
+ # === Graphics Operators and Objects
68
+ #
69
+ # There are about 60 PDF content stream operators. Some are used for changing the graphics
70
+ # state, some for drawing paths and others for showing text. This is all abstracted through
71
+ # the Canvas class.
72
+ #
73
+ # PDF knows about five different graphics objects: path objects, text objects, external
74
+ # objects, inline image objects and shading objects. If none of the five graphics objects is
75
+ # current, the content stream is at the so called page description level (in between graphics
76
+ # objects).
77
+ #
78
+ # Additionally the PDF operators are divided into several groups, like path painting or text
79
+ # showing operators, and such groups of operators are allowed to be used only in certain
80
+ # graphics objects or the page description level.
81
+ #
82
+ # Have a look at the PDF specification (PDF1.7 s8.2) for more details.
83
+ #
84
+ # HexaPDF tries to ensure the proper use of the operators and graphics objects and if it
85
+ # cannot do it, an error is raised. So if you don't modify a content stream directly but via
86
+ # the Canvas methods, you generally don't have to worry about the low-level inner workings.
87
+ #
88
+ # === Graphics State
89
+ #
90
+ # Some operators modify the so called graphics state (see Content::GraphicsState). The graphics
91
+ # state is a collection of settings that is used during processing or creating a content stream.
92
+ # For example, the path painting operators don't have operands to specify the line width or the
93
+ # stroke color but take this information from the graphics state.
94
+ #
95
+ # One important thing about the graphics state is that it is only possible to restore a prior
96
+ # state using the save and restore methods. It is not possible to reset the graphics state
97
+ # while creating the content stream!
98
+ #
99
+ # === Paths
100
+ #
101
+ # A PDF path object consists of one or more subpaths. Each subpath can be a rectangle or can
102
+ # consist of lines and cubic bezier curves. No other types of subpaths are known to PDF.
103
+ # However, the Canvas class contains additional methods that use the basic path construction
104
+ # methods for drawing other paths like circles.
105
+ #
106
+ # When a subpath is started, the current graphics object is changed to :path. After all path
107
+ # constructions are finished, a path painting method needs to be invoked to change back to the
108
+ # page description level. Optionally, the path painting method may be preceeded by a clipping
109
+ # path method to change the current clipping path (see #clip_path).
110
+ #
111
+ # There are four kinds of path painting methods:
112
+ #
113
+ # * Those that stroke the path,
114
+ # * those that fill the path,
115
+ # * those that stroke and fill the path and
116
+ # * one to neither stroke or fill the path (used, for example, to just set the clipping path).
117
+ #
118
+ # In addition filling may be done using either the nonzero winding number rule or the even-odd
119
+ # rule.
120
+ #
121
+ #
122
+ # == Special Graphics State Methods
123
+ #
124
+ # These methods are only allowed when the current graphics object is :none, i.e. operations are
125
+ # done on the page description level.
126
+ #
127
+ # * #save_graphics_state
128
+ # * #restore_graphics_state
129
+ # * #transform, #rotate, #scale, #translate, #skew
130
+ #
131
+ # See: PDF1.7 s8, s9
132
+ class Canvas
133
+
134
+ include HexaPDF::Utils::MathHelpers
135
+
136
+ # The context for which the canvas was created (a HexaPDF::Type::Page or HexaPDF::Type::Form
137
+ # object).
138
+ attr_reader :context
139
+
140
+ # The serialized contents produced by the various canvas operations up to this point.
141
+ #
142
+ # Note that the returned string may not be a completely valid PDF content stream since a
143
+ # graphic object may be open or the graphics state not completely restored.
144
+ #
145
+ # See: #stream_data
146
+ attr_reader :contents
147
+
148
+ # A StreamData object representing the serialized contents produced by the various canvas
149
+ # operations.
150
+ #
151
+ # In contrast to #contents, it is ensured that an open graphics object is closed and all saved
152
+ # graphics states are restored when the contents of the stream data object is read. *Note*
153
+ # that this means that reading the stream data object may change the state of the canvas.
154
+ attr_reader :stream_data
155
+
156
+ # The Content::GraphicsState object containing the current graphics state.
157
+ #
158
+ # The graphics state must not be changed directly, only by using the provided methods. If it
159
+ # is changed directly, the output will not be correct.
160
+ attr_reader :graphics_state
161
+
162
+ # The current graphics object.
163
+ #
164
+ # The graphics object should not be changed directly. It is automatically updated according
165
+ # to the invoked methods.
166
+ #
167
+ # This attribute can have the following values:
168
+ #
169
+ # :none:: No current graphics object, i.e. the page description level.
170
+ # :path:: The current graphics object is a path.
171
+ # :clipping_path:: The current graphics object is a clipping path.
172
+ # :text:: The current graphics object is a text object.
173
+ #
174
+ # See: PDF1.7 s8.2
175
+ attr_accessor :graphics_object
176
+
177
+ # The current point [x, y] of the path.
178
+ #
179
+ # This attribute holds the current point which is only valid if the current graphics objects
180
+ # is :path.
181
+ #
182
+ # When the current point changes, the array is modified in place instead of creating a new
183
+ # array!
184
+ attr_reader :current_point
185
+
186
+ # The operator name/implementation map used when invoking or serializing an operator.
187
+ attr_reader :operators
188
+
189
+ # Creates a new Canvas object for the given context object (either a Page or a Form).
190
+ def initialize(context)
191
+ @context = context
192
+ @operators = Operator::DEFAULT_OPERATORS.dup
193
+ @graphics_state = GraphicsState.new
194
+ @graphics_object = :none
195
+ @font = nil
196
+ @serializer = HexaPDF::Serializer.new
197
+ @current_point = [0, 0]
198
+ @start_point = [0, 0]
199
+ @contents = ''.b
200
+ @stream_data = HexaPDF::StreamData.new do
201
+ case graphics_object
202
+ when :path, :clipping_path then end_path
203
+ when :text then end_text
204
+ end
205
+ restore_graphics_state while graphics_state.saved_states?
206
+ @contents
207
+ end
208
+ end
209
+
210
+ # Returns the resource dictionary of the context object.
211
+ def resources
212
+ @context.resources
213
+ end
214
+
215
+ # :call-seq:
216
+ # canvas.save_graphics_state => canvas
217
+ # canvas.save_graphics_state { block } => canvas
218
+ #
219
+ # Saves the current graphics state and returns self.
220
+ #
221
+ # If invoked without a block a corresponding call to #restore_graphics_state must be done.
222
+ # Otherwise the graphics state is automatically restored when the block is finished.
223
+ #
224
+ # Examples:
225
+ #
226
+ # # With a block
227
+ # canvas.save_graphics_state do
228
+ # canvas.line_width(10)
229
+ # canvas.line(100, 100, 200, 200)
230
+ # end
231
+ #
232
+ # # Same without a block
233
+ # canvas.save_graphics_state
234
+ # canvas.line_width(10)
235
+ # canvas.line(100, 100, 200, 200)
236
+ # canvas.restore_graphics_state
237
+ #
238
+ # See: PDF1.7 s8.4.2, #restore_graphics_state
239
+ def save_graphics_state
240
+ raise_unless_at_page_description_level
241
+ invoke0(:q)
242
+ if block_given?
243
+ yield
244
+ restore_graphics_state
245
+ end
246
+ self
247
+ end
248
+
249
+ # :call-seq:
250
+ # canvas.restore_graphics_state => canvas
251
+ #
252
+ # Restores the current graphics state and returns self.
253
+ #
254
+ # Must not be invoked more times than #save_graphics_state.
255
+ #
256
+ # See: PDF1.7 s8.4.2, #save_graphics_state
257
+ def restore_graphics_state
258
+ raise_unless_at_page_description_level
259
+ invoke0(:Q)
260
+ self
261
+ end
262
+
263
+ # :call-seq:
264
+ # canvas.transform(a, b, c, d, e, f) => canvas
265
+ # canvas.transform(a, b, c, d, e, f) { block } => canvas
266
+ #
267
+ # Transforms the user space by applying the given matrix to the current transformation
268
+ # matrix and returns self.
269
+ #
270
+ # If invoked with a block, the transformation is only active during the block by saving and
271
+ # restoring the graphics state.
272
+ #
273
+ # The given values are interpreted as a matrix in the following way:
274
+ #
275
+ # a b 0
276
+ # c d 0
277
+ # e f 1
278
+ #
279
+ # Examples:
280
+ #
281
+ # canvas.transform(1, 0, 0, 1, 100, 100) do # Translate origin to (100, 100)
282
+ # canvas.line(0, 0, 100, 100) # Actually from (100, 100) to (200, 200)
283
+ # end
284
+ # canvas.line(0, 0, 100, 100) # Again from (0, 0) to (100, 100)
285
+ #
286
+ # See: PDF1.7 s8.3, s8.4.4
287
+ def transform(a, b, c, d, e, f)
288
+ raise_unless_at_page_description_level
289
+ save_graphics_state if block_given?
290
+ invoke(:cm, a, b, c, d, e, f)
291
+ if block_given?
292
+ yield
293
+ restore_graphics_state
294
+ end
295
+ self
296
+ end
297
+
298
+ # :call-seq:
299
+ # canvas.rotate(angle, origin: nil) => canvas
300
+ # canvas.rotate(angle, origin: nil) { block } => canvas
301
+ #
302
+ # Rotates the user space +angle+ degrees around the coordinate system origin or around the
303
+ # given point and returns self.
304
+ #
305
+ # If invoked with a block, the rotation of the user space is only active during the block by
306
+ # saving and restoring the graphics state.
307
+ #
308
+ # Note that the origin of the coordinate system itself doesn't change!
309
+ #
310
+ # origin::
311
+ # The point around which the user space should be rotated.
312
+ #
313
+ # Examples:
314
+ #
315
+ # canvas.rotate(90) do # Positive x-axis is now pointing upwards
316
+ # canvas.line(0, 0, 100, 0) # Actually from (0, 0) to (0, 100)
317
+ # end
318
+ # canvas.line(0, 0, 100, 0) # Again from (0, 0) to (100, 0)
319
+ #
320
+ # canvas.rotate(90, origin: [100, 100]) do
321
+ # canvas.line(100, 100, 200, 0) # Actually from (100, 100) to (100, 200)
322
+ # end
323
+ #
324
+ # See: #transform
325
+ def rotate(angle, origin: nil, &block)
326
+ cos = Math.cos(deg_to_rad(angle))
327
+ sin = Math.sin(deg_to_rad(angle))
328
+
329
+ # Rotation is performed around the coordinate system origin but points are translated so
330
+ # that the rotated rotation origin coincides with the unrotated one.
331
+ tx = (origin ? origin[0] - (origin[0] * cos - origin[1] * sin) : 0)
332
+ ty = (origin ? origin[1] - (origin[0] * sin + origin[1] * cos) : 0)
333
+ transform(cos, sin, -sin, cos, tx, ty, &block)
334
+ end
335
+
336
+ # :call-seq:
337
+ # canvas.scale(sx, sy = sx, origin: nil) => canvas
338
+ # canvas.scale(sx, sy = sx, origin: nil) { block } => canvas
339
+ #
340
+ # Scales the user space +sx+ units in the horizontal and +sy+ units in the vertical
341
+ # direction and returns self. If the optional +origin+ is specified, scaling is done from
342
+ # that point.
343
+ #
344
+ # If invoked with a block, the scaling is only active during the block by saving and
345
+ # restoring the graphics state.
346
+ #
347
+ # Note that the origin of the coordinate system itself doesn't change!
348
+ #
349
+ # origin::
350
+ # The point from which the user space should be scaled.
351
+ #
352
+ # Examples:
353
+ #
354
+ # canvas.scale(2, 3) do # Point (1, 1) is now actually (2, 3)
355
+ # canvas.line(50, 50, 100, 100) # Actually from (100, 150) to (200, 300)
356
+ # end
357
+ # canvas.line(0, 0, 100, 0) # Again from (0, 0) to (100, 0)
358
+ #
359
+ # canvas.scale(2, 3, origin: [50, 50]) do
360
+ # canvas.line(50, 50, 100, 100) # Actually from (50, 50) to (200, 300)
361
+ # end
362
+ #
363
+ # See: #transform
364
+ def scale(sx, sy = sx, origin: nil, &block)
365
+ # As with rotation, scaling is performed around the coordinate system origin but points
366
+ # are translated so that the scaled scaling origin coincides with the unscaled one.
367
+ tx = (origin ? origin[0] - origin[0] * sx : 0)
368
+ ty = (origin ? origin[1] - origin[1] * sy : 0)
369
+ transform(sx, 0, 0, sy, tx, ty, &block)
370
+ end
371
+
372
+ # :call-seq:
373
+ # canvas.translate(x, y) => canvas
374
+ # canvas.translate(x, y) { block } => canvas
375
+ #
376
+ # Translates the user space coordinate system origin to the given +x+ and +y+ coordinates
377
+ # and returns self.
378
+ #
379
+ # If invoked with a block, the translation of the user space is only active during the block
380
+ # by saving and restoring the graphics state.
381
+ #
382
+ # Examples:
383
+ #
384
+ # canvas.translate(100, 100) do # Origin is now at (100, 100)
385
+ # canvas.line(0, 0, 100, 0) # Actually from (100, 100) to (200, 100)
386
+ # end
387
+ # canvas.line(0, 0, 100, 0) # Again from (0, 0) to (100, 0)
388
+ #
389
+ # See: #transform
390
+ def translate(x, y, &block)
391
+ transform(1, 0, 0, 1, x, y, &block)
392
+ end
393
+
394
+ # :call-seq:
395
+ # canvas.skew(a, b, origin: nil) => canvas
396
+ # canvas.skew(a, b, origin: nil) { block } => canvas
397
+ #
398
+ # Skews the the x-axis by +a+ degrees and the y-axis by +b+ degress and returns self. If the
399
+ # optional +origin+ is specified, skewing is done from that point.
400
+ #
401
+ # If invoked with a block, the skewing is only active during the block by saving and
402
+ # restoring the graphics state.
403
+ #
404
+ # Note that the origin of the coordinate system itself doesn't change!
405
+ #
406
+ # origin::
407
+ # The point from which the axes are skewed.
408
+ #
409
+ # Examples:
410
+ #
411
+ # canvas.skew(0, 45) do # Point (1, 1) is now actually (2, 1)
412
+ # canvas.line(50, 50, 100, 100) # Actually from (100, 50) to (200, 100)
413
+ # end
414
+ # canvas.line(0, 0, 100, 0) # Again from (0, 0) to (100, 0)
415
+ #
416
+ # canvas.skew(0, origin: [50, 50]) do
417
+ # canvas.line(50, 50, 100, 100) # Actually from (50, 50) to (200, 300)
418
+ # end
419
+ #
420
+ # See: #transform
421
+ def skew(a, b, origin: nil, &block)
422
+ tan_a = Math.tan(deg_to_rad(a))
423
+ tan_b = Math.sin(deg_to_rad(b))
424
+
425
+ # As with rotation, skewing is performed around the coordinate system origin but points
426
+ # are translated so that the skewed skewing origin coincides with the unskewed one.
427
+ tx = (origin ? -origin[1] * tan_b : 0)
428
+ ty = (origin ? -origin[0] * tan_a : 0)
429
+ transform(1, tan_a, tan_b, 1, tx, ty, &block)
430
+ end
431
+
432
+ # :call-seq:
433
+ # canvas.line_width => current_line_width
434
+ # canvas.line_width(width) => canvas
435
+ # canvas.line_width(width) { block } => canvas
436
+ #
437
+ # The line width determines the thickness of a stroked path.
438
+ #
439
+ # Returns the current line width (see Content::GraphicsState#line_width) when no argument is
440
+ # given. Otherwise sets the line width to the given +width+ and returns self. The setter
441
+ # version can also be called in the line_width= form.
442
+ #
443
+ # If the +width+ and a block are provided, the changed line width is only active during the
444
+ # block by saving and restoring the graphics state.
445
+ #
446
+ # Examples:
447
+ #
448
+ # canvas.line_width(10)
449
+ # canvas.line_width # => 10
450
+ # canvas.line_width = 5 # => 5
451
+ #
452
+ # canvas.line_width(10) do
453
+ # canvas.line_width # => 10
454
+ # end
455
+ # canvas.line_width # => 5
456
+ #
457
+ # See: PDF1.7 s8.4.3.2
458
+ def line_width(width = nil, &block)
459
+ gs_getter_setter(:line_width, :w, width, &block)
460
+ end
461
+ alias :line_width= :line_width
462
+
463
+ # :call-seq:
464
+ # canvas.line_cap_style => current_line_cap_style
465
+ # canvas.line_cap_style(style) => canvas
466
+ # canvas.line_cap_style(style) { block } => canvas
467
+ #
468
+ # The line cap style specifies how the ends of stroked open paths should look like. The
469
+ # +style+ parameter can either be a valid integer or one of the symbols +:butt+, +:round+ or
470
+ # +:projecting_square+ (see Content::LineCapStyle.normalize for details). Note that the return
471
+ # value is always a normalized line cap style.
472
+ #
473
+ # Returns the current line cap style (see Content::GraphicsState#line_cap_style) when no
474
+ # argument is given. Otherwise sets the line cap style to the given +style+ and returns self.
475
+ # The setter version can also be called in the line_cap_style= form.
476
+ #
477
+ # If the +style+ and a block are provided, the changed line cap style is only active during
478
+ # the block by saving and restoring the graphics state.
479
+ #
480
+ # Examples:
481
+ #
482
+ # canvas.line_cap_style(:butt)
483
+ # canvas.line_cap_style # => #<NamedValue @name=:butt, @value=0>
484
+ # canvas.line_cap_style = :round # => #<NamedValue @name=:round, @value=1>
485
+ #
486
+ # canvas.line_cap_style(:butt) do
487
+ # canvas.line_cap_style # => #<NamedValue @name=:butt, @value=0>
488
+ # end
489
+ # canvas.line_cap_style # => #<NamedValue @name=:round, @value=1>
490
+ #
491
+ # See: PDF1.7 s8.4.3.3
492
+ def line_cap_style(style = nil, &block)
493
+ gs_getter_setter(:line_cap_style, :J, style && LineCapStyle.normalize(style), &block)
494
+ end
495
+ alias :line_cap_style= :line_cap_style
496
+
497
+ # :call-seq:
498
+ # canvas.line_join_style => current_line_join_style
499
+ # canvas.line_join_style(style) => canvas
500
+ # canvas.line_join_style(style) { block } => canvas
501
+ #
502
+ # The line join style specifies the shape that is used at the corners of stroked paths. The
503
+ # +style+ parameter can either be a valid integer or one of the symbols +:miter+, +:round+ or
504
+ # +:bevel+ (see Content::LineJoinStyle.normalize for details). Note that the return value is
505
+ # always a normalized line join style.
506
+ #
507
+ # Returns the current line join style (see Content::GraphicsState#line_join_style) when no
508
+ # argument is given. Otherwise sets the line join style to the given +style+ and returns self.
509
+ # The setter version can also be called in the line_join_style= form.
510
+ #
511
+ # If the +style+ and a block are provided, the changed line join style is only active during
512
+ # the block by saving and restoring the graphics state.
513
+ #
514
+ # Examples:
515
+ #
516
+ # canvas.line_join_style(:miter)
517
+ # canvas.line_join_style # => #<NamedValue @name=:miter, @value=0>
518
+ # canvas.line_join_style = :round # => #<NamedValue @name=:round, @value=1>
519
+ #
520
+ # canvas.line_join_style(:bevel) do
521
+ # canvas.line_join_style # => #<NamedValue @name=:bevel, @value=2>
522
+ # end
523
+ # canvas.line_join_style # => #<NamedValue @name=:round, @value=1>
524
+ #
525
+ # See: PDF1.7 s8.4.3.4
526
+ def line_join_style(style = nil, &block)
527
+ gs_getter_setter(:line_join_style, :j, style && LineJoinStyle.normalize(style), &block)
528
+ end
529
+ alias :line_join_style= :line_join_style
530
+
531
+ # :call-seq:
532
+ # canvas.miter_limit => current_miter_limit
533
+ # canvas.miter_limit(limit) => canvas
534
+ # canvas.miter_limit(limit) { block } => canvas
535
+ #
536
+ # The miter limit specifies the maximum ratio of the miter length to the line width for
537
+ # mitered line joins (see #line_join_style). When the limit is exceeded, a bevel join is
538
+ # used instead of a miter join.
539
+ #
540
+ # Returns the current miter limit (see Content::GraphicsState#miter_limit) when no argument is
541
+ # given. Otherwise sets the miter limit to the given +limit+ and returns self. The setter
542
+ # version can also be called in the miter_limit= form.
543
+ #
544
+ # If the +limit+ and a block are provided, the changed miter limit is only active during the
545
+ # block by saving and restoring the graphics state.
546
+ #
547
+ # Examples:
548
+ #
549
+ # canvas.miter_limit(10)
550
+ # canvas.miter_limit # => 10
551
+ # canvas.miter_limit = 5 # => 5
552
+ #
553
+ # canvas.miter_limit(10) do
554
+ # canvas.miter_limit # => 10
555
+ # end
556
+ # canvas.miter_limit # => 5
557
+ #
558
+ # See: PDF1.7 s8.4.3.5
559
+ def miter_limit(limit = nil, &block)
560
+ gs_getter_setter(:miter_limit, :M, limit, &block)
561
+ end
562
+ alias :miter_limit= :miter_limit
563
+
564
+ # :call-seq:
565
+ # canvas.line_dash_pattern => current_line_dash_pattern
566
+ # canvas.line_dash_pattern(line_dash_pattern) => canvas
567
+ # canvas.line_dash_pattern(length, phase = 0) => canvas
568
+ # canvas.line_dash_pattern(array, phase = 0) => canvas
569
+ # canvas.line_dash_pattern(value, phase = 0) { block } => canvas
570
+ #
571
+ # The line dash pattern defines the appearance of a stroked path (line _or_ curve), ie. if
572
+ # it is solid or if it contains dashes and gaps.
573
+ #
574
+ # There are multiple ways to set the line dash pattern:
575
+ #
576
+ # * By providing a Content::LineDashPattern object
577
+ # * By providing a single Integer/Float that is used for both dashes and gaps
578
+ # * By providing an array of Integers/Floats that specify the alternating dashes and gaps
579
+ #
580
+ # The phase (i.e. the distance into the dashes/gaps at which to start) can additionally be
581
+ # set in the last two cases.
582
+ #
583
+ # A solid line can be achieved by using 0 for the length or by using an empty array.
584
+ #
585
+ # Returns the current line dash pattern (see Content::GraphicsState#line_dash_pattern) when no
586
+ # argument is given. Otherwise sets the line dash pattern using the given arguments and
587
+ # returns self. The setter version can also be called in the line_dash_pattern= form (but only
588
+ # without the second argument!).
589
+ #
590
+ # If arguments and a block are provided, the changed line dash pattern is only active during
591
+ # the block by saving and restoring the graphics state.
592
+ #
593
+ # Examples:
594
+ #
595
+ # canvas.line_dash_pattern(10)
596
+ # canvas.line_dash_pattern # => LineDashPattern.new([10], 0)
597
+ # canvas.line_dash_pattern(10, 2)
598
+ # canvas.line_dash_pattern([5, 3, 1], 2)
599
+ # canvas.line_dash_pattern = LineDashPattern.new([5, 3, 1], 1)
600
+ #
601
+ # canvas.line_dash_pattern(10) do
602
+ # canvas.line_dash_pattern # => LineDashPattern.new([10], 0)
603
+ # end
604
+ # canvas.line_dash_pattern # => LineDashPattern.new([5, 3, 1], 1)
605
+ #
606
+ # See: PDF1.7 s8.4.3.5, LineDashPattern
607
+ def line_dash_pattern(value = nil, phase = 0, &block)
608
+ case value
609
+ when nil, LineDashPattern
610
+ when Array
611
+ value = LineDashPattern.new(value, phase)
612
+ when 0
613
+ value = LineDashPattern.new([], 0)
614
+ else
615
+ value = LineDashPattern.new([value], phase)
616
+ end
617
+ gs_getter_setter(:line_dash_pattern, :d, value, &block)
618
+ end
619
+ alias :line_dash_pattern= :line_dash_pattern
620
+
621
+ # :call-seq:
622
+ # canvas.rendering_intent => current_rendering_intent
623
+ # canvas.rendering_intent(intent) => canvas
624
+ # canvas.rendering_intent(intent) { block } => canvas
625
+ #
626
+ # The rendering intent is used to specify the intent on how colors should be rendered since
627
+ # sometimes compromises have to be made when the capabilities of an output device are not
628
+ # sufficient. The +intent+ parameter can be one of the following symbols:
629
+ #
630
+ # * +:AbsoluteColorimetric+
631
+ # * +:RelativeColorimetric+
632
+ # * +:Saturation+
633
+ # * +:Perceptual+
634
+ #
635
+ # Returns the current rendering intent (see Content::GraphicsState#rendering_intent) when no
636
+ # argument is given. Otherwise sets the rendering intent using the +intent+ argument and
637
+ # returns self. The setter version can also be called in the rendering_intent= form.
638
+ #
639
+ # If the +intent+ and a block are provided, the changed rendering intent is only active
640
+ # during the block by saving and restoring the graphics state.
641
+ #
642
+ # Examples:
643
+ #
644
+ # canvas.rendering_intent(:Perceptual)
645
+ # canvas.rendering_intent # => :Perceptual
646
+ # canvas.rendering_intent = :Saturation # => :Saturation
647
+ #
648
+ # canvas.rendering_intent(:Perceptual) do
649
+ # canvas.rendering_intent # => :Perceptual
650
+ # end
651
+ # canvas.rendering_intent # => :Saturation
652
+ #
653
+ # See: PDF1.7 s8.6.5.8, RenderingIntent
654
+ def rendering_intent(intent = nil, &bk)
655
+ gs_getter_setter(:rendering_intent, :ri, intent && RenderingIntent.normalize(intent), &bk)
656
+ end
657
+ alias :rendering_intent= :rendering_intent
658
+
659
+ # :call-seq:
660
+ # canvas.stroke_color => current_stroke_color
661
+ # canvas.stroke_color(gray) => canvas
662
+ # canvas.stroke_color(r, g, b) => canvas
663
+ # canvas.stroke_color(c, m, y, k) => canvas
664
+ # canvas.stroke_color(string) => canvas
665
+ # canvas.stroke_color(color_object) => canvas
666
+ # canvas.stroke_color(array) => canvas
667
+ # canvas.stroke_color(color_spec) { block } => canvas
668
+ #
669
+ # The stroke color defines the color used for stroking operations, i.e. for painting paths.
670
+ #
671
+ # There are several ways to define the color that should be used:
672
+ #
673
+ # * A single numeric argument specifies a gray color (see
674
+ # Content::ColorSpace::DeviceGray::Color).
675
+ # * Three numeric arguments specify an RGB color (see Content::ColorSpace::DeviceRGB::Color).
676
+ # * A string in the format "RRGGBB" where "RR" is the hexadecimal number for the red, "GG"
677
+ # for the green and "BB" for the blue color value also specifies an RGB color.
678
+ # * Four numeric arguments specify a CMYK color (see Content::ColorSpace::DeviceCMYK::Color).
679
+ # * A color object is used directly (normally used for color spaces other than DeviceRGB,
680
+ # DeviceCMYK and DeviceGray).
681
+ # * An array is treated as if its items were specified separately as arguments.
682
+ #
683
+ # Returns the current stroke color (see Content::GraphicsState#stroke_color) when no argument
684
+ # is given. Otherwise sets the stroke color using the given arguments and returns self. The
685
+ # setter version can also be called in the stroke_color= form.
686
+ #
687
+ # If the arguments and a block are provided, the changed stroke color is only active during
688
+ # the block by saving and restoring the graphics state.
689
+ #
690
+ # Examples:
691
+ #
692
+ # # With no arguments just returns the current color
693
+ # canvas.stroke_color # => DeviceGray.color(0.0)
694
+ #
695
+ # # Same gray color because integer values are normalized to the range of 0.0 to 1.0
696
+ # canvas.stroke_color(102)
697
+ # canvas.stroke_color(0.4)
698
+ #
699
+ # # Specifying RGB colors
700
+ # canvas.stroke_color(255, 255, 0)
701
+ # canvas.stroke_color("FFFF00")
702
+ #
703
+ # # Specifying CMYK colors
704
+ # canvas.stroke_color(255, 255, 0, 128)
705
+ #
706
+ # # Can use a color object directly
707
+ # color = HexaPDF::Content::ColorSpace::DeviceRGB.color(255, 255, 0)
708
+ # canvas.stroke_color(color)
709
+ #
710
+ # # An array argument is destructured - these calls are all equal
711
+ # cnavas.stroke_color(255, 255, 0)
712
+ # canvas.stroke_color([255, 255, 0])
713
+ # canvas.stroke_color = [255, 255, 0]
714
+ #
715
+ # # As usual, can be invoked with a block to limit the effects
716
+ # canvas.stroke_color(102) do
717
+ # canvas.stroke_color # => ColorSpace::DeviceGray.color(0.4)
718
+ # end
719
+ #
720
+ # See: PDF1.7 s8.6, ColorSpace
721
+ def stroke_color(*color, &block)
722
+ color_getter_setter(:stroke_color, color, :RG, :G, :K, :CS, :SCN, &block)
723
+ end
724
+ alias :stroke_color= :stroke_color
725
+
726
+ # The fill color defines the color used for non-stroking operations, i.e. for filling paths.
727
+ #
728
+ # Works exactly the same #stroke_color but for the fill color. See #stroke_color for
729
+ # details on invocation and use.
730
+ def fill_color(*color, &block)
731
+ color_getter_setter(:fill_color, color, :rg, :g, :k, :cs, :scn, &block)
732
+ end
733
+ alias :fill_color= :fill_color
734
+
735
+ # :call-seq:
736
+ # canvas.opacity => current_values
737
+ # canvas.opacity(fill_alpha:) => canvas
738
+ # canvas.opacity(stroke_alpha:) => canvas
739
+ # canvas.opacity(fill_alpha:, stroke_alpha:) => canvas
740
+ # canvas.opacity(fill_alpha:, stroke_alpha:) { block } => canvas
741
+ #
742
+ # The fill and stroke alpha values determine how opaque drawn elements will be. Note that
743
+ # the fill alpha value applies not just to fill values but to all non-stroking operations
744
+ # (e.g. images, ...).
745
+ #
746
+ # Returns the current fill alpha (see Content::GraphicsState#fill_alpha) and stroke alpha (see
747
+ # Content::GraphicsState#stroke_alpha) values using a hash with the keys +:fill_alpha+ and
748
+ # +:stroke_alpha+ when no argument is given. Otherwise sets the fill and stroke alpha values
749
+ # and returns self. The setter version can also be called in the #opacity= form.
750
+ #
751
+ # If the values are set and a block is provided, the changed alpha values are only active
752
+ # during the block by saving and restoring the graphics state.
753
+ #
754
+ # Examples:
755
+ #
756
+ # canvas.opacity(fill_alpha: 0.5)
757
+ # canvas.opacity # => {fill_alpha: 0.5, stroke_alpha: 1.0}
758
+ # canvas.opacity(fill_alpha: 0.4, stroke_alpha: 0.9)
759
+ # canvas.opacity # => {fill_alpha: 0.4, stroke_alpha: 0.9}
760
+ #
761
+ # canvas.opacity(stroke_alpha: 0.7) do
762
+ # canvas.opacity # => {fill_alpha: 0.4, stroke_alpha: 0.7}
763
+ # end
764
+ # canvas.opacity # => {fill_alpha: 0.4, stroke_alpha: 0.9}
765
+ #
766
+ # See: PDF1.7 s11.6.4.4
767
+ def opacity(fill_alpha: nil, stroke_alpha: nil)
768
+ if !fill_alpha.nil? || !stroke_alpha.nil?
769
+ raise_unless_at_page_description_level_or_in_text
770
+ save_graphics_state if block_given?
771
+ if (!fill_alpha.nil? && graphics_state.fill_alpha != fill_alpha) ||
772
+ (!stroke_alpha.nil? && graphics_state.stroke_alpha != stroke_alpha)
773
+ dict = {Type: :ExtGState}
774
+ dict[:CA] = stroke_alpha unless stroke_alpha.nil?
775
+ dict[:ca] = fill_alpha unless fill_alpha.nil?
776
+ dict[:AIS] = false if graphics_state.alpha_source
777
+ invoke1(:gs, resources.add_ext_gstate(dict))
778
+ end
779
+ if block_given?
780
+ yield
781
+ restore_graphics_state
782
+ end
783
+ self
784
+ elsif block_given?
785
+ raise ArgumentError, "Block only allowed with an argument"
786
+ else
787
+ {fill_alpha: graphics_state.fill_alpha, stroke_alpha: graphics_state.stroke_alpha}
788
+ end
789
+ end
790
+
791
+ # :call-seq:
792
+ # canvas.move_to(x, y) => canvas
793
+ #
794
+ # Begins a new subpath (and possibly a new path) by moving the current point to the given
795
+ # point.
796
+ #
797
+ # Examples:
798
+ #
799
+ # canvas.move_to(100, 50)
800
+ def move_to(x, y)
801
+ raise_unless_at_page_description_level_or_in_path
802
+ invoke2(:m, x, y)
803
+ @current_point[0] = @start_point[0] = x
804
+ @current_point[1] = @start_point[1] = y
805
+ self
806
+ end
807
+
808
+ # :call-seq:
809
+ # canvas.line_to(x, y) => canvas
810
+ #
811
+ # Appends a straight line segment from the current point to the given point (which becomes the
812
+ # new current point) to the current subpath.
813
+ #
814
+ # Examples:
815
+ #
816
+ # canvas.line_to(100, 100)
817
+ def line_to(x, y)
818
+ raise_unless_in_path
819
+ invoke2(:l, x, y)
820
+ @current_point[0] = x
821
+ @current_point[1] = y
822
+ self
823
+ end
824
+
825
+ # :call-seq:
826
+ # canvas.curve_to(x, y, p1:, p2:) => canvas
827
+ # canvas.curve_to(x, y, p1:) => canvas
828
+ # canvas.curve_to(x, y, p2:) => canvas
829
+ #
830
+ # Appends a cubic Bezier curve to the current subpath starting from the current point. The end
831
+ # point becomes the new current point.
832
+ #
833
+ # A Bezier curve consists of the start point, the end point and the two control points +p1+
834
+ # and +p2+. The start point is always the current point and the end point is specified as
835
+ # +x+ and +y+ arguments.
836
+ #
837
+ # Additionally, either the first control point +p1+ or the second control +p2+ or both
838
+ # control points have to be specified (as arrays containing two numbers). If the first
839
+ # control point is not specified, the current point is used as first control point. If the
840
+ # second control point is not specified, the end point is used as the second control point.
841
+ #
842
+ # Examples:
843
+ #
844
+ # canvas.curve_to(100, 100, p1: [100, 50], p2: [50, 100])
845
+ # canvas.curve_to(100, 100, p1: [100, 50])
846
+ # canvas.curve_to(100, 100, p2: [50, 100])
847
+ def curve_to(x, y, p1: nil, p2: nil)
848
+ raise_unless_in_path
849
+ if p1 && p2
850
+ invoke(:c, *p1, *p2, x, y)
851
+ elsif p1
852
+ invoke(:y, *p1, x, y)
853
+ elsif p2
854
+ invoke(:v, *p2, x, y)
855
+ else
856
+ raise ArgumentError, "At least one control point must be specified for Bézier curves"
857
+ end
858
+ @current_point[0] = x
859
+ @current_point[1] = y
860
+ self
861
+ end
862
+
863
+ # :call-seq:
864
+ # canvas.rectangle(x, y, width, height, radius: 0) => canvas
865
+ #
866
+ # Appends a rectangle to the current path as a complete subpath (drawn in counterclockwise
867
+ # direction), with the lower-left corner specified by +x+ and +y+ and the given +width+ and
868
+ # +height+.
869
+ #
870
+ # If +radius+ is greater than 0, the corners are rounded with the given radius.
871
+ #
872
+ # If there is no current path when the method is invoked, a new path is automatically begun.
873
+ #
874
+ # The current point is set to the lower-left corner if +radius+ is zero, otherwise it is set
875
+ # to (x, y + radius).
876
+ #
877
+ # Examples:
878
+ #
879
+ # canvas.rectangle(100, 100, 100, 50)
880
+ # canvas.rectangle(100, 100, 100, 50, radius: 10)
881
+ def rectangle(x, y, width, height, radius: 0)
882
+ raise_unless_at_page_description_level_or_in_path
883
+ if radius == 0
884
+ invoke(:re, x, y, width, height)
885
+ @current_point[0] = @start_point[0] = x
886
+ @current_point[1] = @start_point[1] = y
887
+ self
888
+ else
889
+ polygon(x, y, x + width, y, x + width, y + height, x, y + height, radius: radius)
890
+ end
891
+ end
892
+
893
+ # :call-seq:
894
+ # canvas.close_subpath => canvas
895
+ #
896
+ # Closes the current subpath by appending a straight line from the current point to the
897
+ # start point of the subpath which also becomes the new current point.
898
+ def close_subpath
899
+ raise_unless_in_path
900
+ invoke0(:h)
901
+ @current_point = @start_point
902
+ self
903
+ end
904
+
905
+ # :call-seq:
906
+ # canvas.line(x0, y0, x1, y1) => canvas
907
+ #
908
+ # Moves the current point to (x0, y0) and appends a line to (x1, y1) to the current path.
909
+ #
910
+ # This method is equal to "canvas.move_to(x0, y0).line_to(x1, y1)".
911
+ #
912
+ # Examples:
913
+ #
914
+ # canvas.line(10, 10, 100, 100)
915
+ def line(x0, y0, x1, y1)
916
+ move_to(x0, y0)
917
+ line_to(x1, y1)
918
+ end
919
+
920
+ # :call-seq:
921
+ # canvas.polyline(x0, y0, x1, y1, x2, y2, ...) => canvas
922
+ #
923
+ # Moves the current point to (x0, y0) and appends line segments between all given
924
+ # consecutive points, i.e. between (x0, y0) and (x1, y1), between (x1, y1) and (x2, y2) and
925
+ # so on. The last point becomes the new current point.
926
+ #
927
+ # Examples:
928
+ #
929
+ # canvas.polyline(0, 0, 100, 0, 100, 100, 0, 100, 0, 0)
930
+ def polyline(*points)
931
+ check_poly_points(points)
932
+ move_to(points[0], points[1])
933
+ i = 2
934
+ while i < points.length
935
+ line_to(points[i], points[i + 1])
936
+ i += 2
937
+ end
938
+ self
939
+ end
940
+
941
+ # :call-seq:
942
+ # canvas.polygon(x0, y0, x1, y1, x2, y2, ..., radius: 0) => canvas
943
+ #
944
+ # Appends a polygon consisting of the given points to the path as a complete subpath. The
945
+ # point (x0, y0 + radius) becomes the new current point.
946
+ #
947
+ # If +radius+ is greater than 0, the corners are rounded with the given radius.
948
+ #
949
+ # If there is no current path when the method is invoked, a new path is automatically begun.
950
+ #
951
+ # Examples:
952
+ #
953
+ # canvas.polygon(0, 0, 100, 0, 100, 100, 0, 100)
954
+ # canvas.polygon(0, 0, 100, 0, 100, 100, 0, 100, radius: 10)
955
+ def polygon(*points, radius: 0)
956
+ if radius == 0
957
+ polyline(*points)
958
+ else
959
+ check_poly_points(points)
960
+ move_to(*point_on_line(points[0], points[1], points[2], points[3], distance: radius))
961
+ points.concat(points[0, 4])
962
+ 0.step(points.length - 6, 2) {|i| line_with_rounded_corner(*points[i, 6], radius)}
963
+ end
964
+ close_subpath
965
+ end
966
+
967
+ # :call-seq:
968
+ # canvas.circle(cx, cy, radius) => canvas
969
+ #
970
+ # Appends a circle with center (cx, cy) and the given radius (in degrees) to the path as a
971
+ # complete subpath (drawn in counterclockwise direction). The point (center_x + radius,
972
+ # center_y) becomes the new current point.
973
+ #
974
+ # If there is no current path when the method is invoked, a new path is automatically begun.
975
+ #
976
+ # Examples:
977
+ #
978
+ # canvas.circle(100, 100, 10)
979
+ #
980
+ # See: #arc (for approximation accuracy)
981
+ def circle(cx, cy, radius)
982
+ arc(cx, cy, a: radius)
983
+ close_subpath
984
+ end
985
+
986
+ # :call-seq:
987
+ # canvas.ellipse(cx, cy, a:, b:, inclination: 0) => canvas
988
+ #
989
+ # Appends an ellipse with center (cx, cy), semi-major axis +a+, semi-minor axis +b+ and an
990
+ # inclination from the x-axis of +inclination+ degrees to the path as a complete subpath. The
991
+ # outer-most point on the semi-major axis becomes the new current point.
992
+ #
993
+ # If there is no current path when the method is invoked, a new path is automatically begun.
994
+ #
995
+ # Examples:
996
+ #
997
+ # # Ellipse aligned to x-axis and y-axis
998
+ # canvas.ellipse(100, 100, a: 10, b: 5)
999
+ #
1000
+ # # Inclined ellipse
1001
+ # canvas.ellipse(100, 100, a: 10, b: 5, inclination: 45)
1002
+ #
1003
+ # See: #arc (for approximation accuracy)
1004
+ def ellipse(cx, cy, a:, b:, inclination: 0)
1005
+ arc(cx, cy, a: a, b: b, inclination: inclination)
1006
+ close_subpath
1007
+ end
1008
+
1009
+ # :call-seq:
1010
+ # canvas.arc(cx, cy, a:, b: a, start_angle: 0, end_angle: 360, clockwise: false, inclination: 0) => canvas
1011
+ #
1012
+ # Appends an elliptical arc to the path. The endpoint of the arc becomes the new current
1013
+ # point.
1014
+ #
1015
+ # +cx+::
1016
+ # x-coordinate of the center point of the arc
1017
+ #
1018
+ # +cy+::
1019
+ # y-coordinate of the center point of the arc
1020
+ #
1021
+ # +a+::
1022
+ # Length of semi-major axis
1023
+ #
1024
+ # +b+::
1025
+ # Length of semi-minor axis (default: +a+)
1026
+ #
1027
+ # +start_angle+::
1028
+ # Angle in degrees at which to start the arc (default: 0)
1029
+ #
1030
+ # +end_angle+::
1031
+ # Angle in degrees at which to end the arc (default: 360)
1032
+ #
1033
+ # +clockwise+::
1034
+ # If +true+ the arc is drawn in clockwise direction, otherwise in counterclockwise
1035
+ # direction.
1036
+ #
1037
+ # +inclination+::
1038
+ # Angle in degrees between the x-axis and the semi-major axis (default: 0)
1039
+ #
1040
+ # If +a+ and +b+ are equal, a circular arc is drawn. If the difference of the start angle
1041
+ # and end angle is equal to 360, a full ellipse (or circle) is drawn.
1042
+ #
1043
+ # If there is no current path when the method is invoked, a new path is automatically begun.
1044
+ #
1045
+ # Since PDF doesn't have operators for drawing elliptical or circular arcs, they have to be
1046
+ # approximated using Bezier curves (see #curve_to). The accuracy of the approximation can be
1047
+ # controlled using the configuration option 'graphic_object.arc.max_curves'.
1048
+ #
1049
+ # Examples:
1050
+ #
1051
+ # canvas.arc(0, 0, a: 10) # Circle at (0, 0) with radius 10
1052
+ # canvas.arc(0, 0, a: 10, b: 5) # Ellipse at (0, 0) with radii 10 and 5
1053
+ # canvas.arc(0, 0, a: 10, b: 5, inclination: 45) # The above ellipse inclined 45 degrees
1054
+ #
1055
+ # # Circular and elliptical arcs from 45 degrees to 135 degrees
1056
+ # canvas.arc(0, 0, a: 10, start_angle: 45, end_angle: 135)
1057
+ # canvas.arc(0, 0, a: 10, b: 5, start_angle: 45, end_angle: 135)
1058
+ #
1059
+ # # Arcs from 135 degrees to 15 degrees, the first in counterclockwise direction (i.e. the
1060
+ # # big arc), the other in clockwise direction (i.e. the small arc)
1061
+ # canvas.arc(0, 0, a: 10, start_angle: 135, end_angle: 15)
1062
+ # canvas.arc(0, 0, a: 10, start_angle: 135, end_angle: 15, clockwise: true)
1063
+ #
1064
+ # See: Content::GraphicObject::Arc
1065
+ def arc(cx, cy, a:, b: a, start_angle: 0, end_angle: 360, clockwise: false, inclination: 0)
1066
+ arc = GraphicObject::Arc.configure(cx: cx, cy: cy, a: a, b: b,
1067
+ start_angle: start_angle, end_angle: end_angle,
1068
+ clockwise: clockwise, inclination: inclination)
1069
+ arc.draw(self)
1070
+ self
1071
+ end
1072
+
1073
+ # :call-seq:
1074
+ # canvas.graphic_object(obj, **options) => obj
1075
+ # canvas.graphic_object(name, **options) => graphic_object
1076
+ #
1077
+ # Returns the named graphic object, configured with the given options.
1078
+ #
1079
+ # If an object responding to :configure is given, it is used. Otherwise the graphic object
1080
+ # is looked up via the given name in the configuration option 'graphic_object.map'. Then the
1081
+ # graphic object is configured with the given options if at least one is given.
1082
+ #
1083
+ # Examples:
1084
+ #
1085
+ # obj = canvas.graphic_object(:arc, cx: 10, cy: 10)
1086
+ # canvas.draw(obj)
1087
+ def graphic_object(obj, **options)
1088
+ unless obj.respond_to?(:configure)
1089
+ obj = context.document.config.constantize('graphic_object.map', obj)
1090
+ end
1091
+ obj = obj.configure(options) if options.size > 0 || !obj.respond_to?(:draw)
1092
+ obj
1093
+ end
1094
+
1095
+ # :call-seq:
1096
+ # canvas.draw(obj, **options) => canvas
1097
+ # canvas.draw(name, **options) => canvas
1098
+ #
1099
+ # Draws the given graphic object on the canvas.
1100
+ #
1101
+ # See #graphic_object for information on the arguments.
1102
+ #
1103
+ # Examples:
1104
+ #
1105
+ # canvas.draw(:arc, cx: 10, cy: 10)
1106
+ def draw(name, **options)
1107
+ graphic_object(name, **options).draw(self)
1108
+ self
1109
+ end
1110
+
1111
+ # :call-seq:
1112
+ # canvas.stroke => canvas
1113
+ #
1114
+ # Strokes the path.
1115
+ #
1116
+ # See: PDF1.7 s8.5.3.1, s8.5.3.2
1117
+ def stroke
1118
+ raise_unless_in_path_or_clipping_path
1119
+ invoke0(:S)
1120
+ self
1121
+ end
1122
+
1123
+ # :call-seq:
1124
+ # canvas.close_stroke => canvas
1125
+ #
1126
+ # Closes the last subpath and then strokes the path.
1127
+ #
1128
+ # See: PDF1.7 s8.5.3.1, s8.5.3.2
1129
+ def close_stroke
1130
+ raise_unless_in_path_or_clipping_path
1131
+ invoke0(:s)
1132
+ self
1133
+ end
1134
+
1135
+ # :call-seq:
1136
+ # canvas.fill(rule = :nonzero) => canvas
1137
+ #
1138
+ # Fills the path using the given rule.
1139
+ #
1140
+ # The argument +rule+ may either be +:nonzero+ to use the nonzero winding number rule or
1141
+ # +:even_odd+ to use the even-odd rule for determining which regions to fill in.
1142
+ #
1143
+ # Any open subpaths are implicitly closed before being filled.
1144
+ #
1145
+ # See: PDF1.7 s8.5.3.1, s8.5.3.3
1146
+ def fill(rule = :nonzero)
1147
+ raise_unless_in_path_or_clipping_path
1148
+ invoke0(rule == :nonzero ? :f : :'f*')
1149
+ self
1150
+ end
1151
+
1152
+ # :call-seq:
1153
+ # canvas.fill_stroke(rule = :nonzero) => canvas
1154
+ #
1155
+ # Fills and then strokes the path using the given rule.
1156
+ #
1157
+ # The argument +rule+ may either be +:nonzero+ to use the nonzero winding number rule or
1158
+ # +:even_odd+ to use the even-odd rule for determining which regions to fill in.
1159
+ #
1160
+ # See: PDF1.7 s8.5.3
1161
+ def fill_stroke(rule = :nonzero)
1162
+ raise_unless_in_path_or_clipping_path
1163
+ invoke0(rule == :nonzero ? :B : :'B*')
1164
+ self
1165
+ end
1166
+
1167
+ # :call-seq:
1168
+ # canvas.close_fill_stroke(rule = :nonzero) => canvas
1169
+ #
1170
+ # Closes the last subpath and then fills and strokes the path using the given rule.
1171
+ #
1172
+ # The argument +rule+ may either be +:nonzero+ to use the nonzero winding number rule or
1173
+ # +:even_odd+ to use the even-odd rule for determining which regions to fill in.
1174
+ #
1175
+ # See: PDF1.7 s8.5.3
1176
+ def close_fill_stroke(rule = :nonzero)
1177
+ raise_unless_in_path_or_clipping_path
1178
+ invoke0(rule == :nonzero ? :b : :'b*')
1179
+ self
1180
+ end
1181
+
1182
+ # :call-seq:
1183
+ # canvas.end_path => canvas
1184
+ #
1185
+ # Ends the path without stroking or filling it.
1186
+ #
1187
+ # This method is normally used in conjunction with the clipping path methods to define the
1188
+ # clipping.
1189
+ #
1190
+ # See: PDF1.7 s8.5.3.1 #clip
1191
+ def end_path
1192
+ raise_unless_in_path_or_clipping_path
1193
+ invoke0(:n)
1194
+ self
1195
+ end
1196
+
1197
+ # :call-seq:
1198
+ # canvas.clip_path(rule = :nonzero) => canvas
1199
+ #
1200
+ # Modifies the clipping path by intersecting it with the current path.
1201
+ #
1202
+ # The argument +rule+ may either be +:nonzero+ to use the nonzero winding number rule or
1203
+ # +:even_odd+ to use the even-odd rule for determining which regions lie inside the clipping
1204
+ # path.
1205
+ #
1206
+ # Note that the current path cannot be modified after invoking this method! This means that
1207
+ # one of the path painting methods or #end_path must be called immediately afterwards.
1208
+ #
1209
+ # See: PDF1.7 s8.5.4
1210
+ def clip_path(rule = :nonzero)
1211
+ raise_unless_in_path
1212
+ invoke0(rule == :nonzero ? :W : :'W*')
1213
+ self
1214
+ end
1215
+
1216
+ # :call-seq:
1217
+ # canvas.xobject(filename, at:, width: nil, height: nil) => xobject
1218
+ # canvas.xobject(io, at:, width: nil, height: nil) => xobject
1219
+ # canvas.xobject(image_object, at:, width: nil, height: nil) => image_object
1220
+ # canvas.xobject(form_object, at:, width: nil, height: nil) => form_object
1221
+ #
1222
+ # Draws the given XObject (either an image XObject or a form XObject) at the specified
1223
+ # position and returns the XObject.
1224
+ #
1225
+ # Any image format for which a HexaPDF::ImageLoader object is available and registered with
1226
+ # the configuration option 'image_loader' can be used. PNG and JPEG images are supported out
1227
+ # of the box.
1228
+ #
1229
+ # If the filename or the IO specifies a PDF file, the first page of this file is used to
1230
+ # create a form XObject which is then drawn.
1231
+ #
1232
+ # The +at+ argument has to be an array containing two numbers specifying the lower-left
1233
+ # corner at which to draw the XObject.
1234
+ #
1235
+ # If +width+ and +height+ are specified, the drawn XObject will have exactly these
1236
+ # dimensions. If only one of them is specified, the other dimension is automatically
1237
+ # calculated so that the aspect ratio is retained. If neither is specified, the width and
1238
+ # height of the XObject are used (for images, 1 pixel being represented by 1 PDF point, i.e.
1239
+ # 72 DPI).
1240
+ #
1241
+ # *Note*: If a form XObject is drawn, all currently set graphics state parameters influence
1242
+ # the rendering of the form XObject. This means, for example, that when the line width is
1243
+ # set to 20, all lines of the form XObject are drawn with that line width unless the line
1244
+ # width is changed in the form XObject itself.
1245
+ #
1246
+ # Examples:
1247
+ #
1248
+ # canvas.xobject('test.png', at: [100, 100])
1249
+ # canvas.xobject('test.pdf', at: [100, 100])
1250
+ #
1251
+ # File.new('test.jpg', 'rb') do |io|
1252
+ # canvas.xobject(io, at: [100, 200], width: 300)
1253
+ # end
1254
+ #
1255
+ # image = document.object(5) # Object with oid=5 is an image XObject in this example
1256
+ # canvas.xobject(image, at: [100, 200], width: 200, heigth: 300)
1257
+ #
1258
+ # See: PDF1.7 s8.8, s.8.10.1
1259
+ def xobject(obj, at:, width: nil, height: nil)
1260
+ unless obj.kind_of?(HexaPDF::Stream)
1261
+ obj = context.document.utils.add_image(obj)
1262
+ end
1263
+
1264
+ if obj[:Subtype] == :Image
1265
+ width, height = calculate_dimensions(obj[:Width], obj[:Height],
1266
+ rwidth: width, rheight: height)
1267
+ else
1268
+ width, height = calculate_dimensions(obj.box.width, obj.box.height,
1269
+ rwidth: width, rheight: height)
1270
+ width /= obj.box.width.to_f
1271
+ height /= obj.box.height.to_f
1272
+ at[0] -= obj.box.left
1273
+ at[1] -= obj.box.bottom
1274
+ end
1275
+
1276
+ transform(width, 0, 0, height, at[0], at[1]) do
1277
+ invoke1(:Do, resources.add_xobject(obj))
1278
+ end
1279
+
1280
+ obj
1281
+ end
1282
+ alias :image :xobject
1283
+
1284
+ # :call-seq:
1285
+ # canvas.character_spacing => current_character_spacing
1286
+ # canvas.character_spacing(amount) => canvas
1287
+ # canvas.character_spacing(amount) { block } => canvas
1288
+ #
1289
+ # The character spacing determines how much additional space is added between two
1290
+ # consecutive characters. For horizontal writing positive values increase the distance
1291
+ # between two characters, whereas for vertical writing negative values increase the
1292
+ # distance.
1293
+ #
1294
+ # Returns the current character spacing value (see Content::GraphicsState#character_spacing)
1295
+ # when no argument is given. Otherwise sets the character spacing using the +amount+ argument
1296
+ # and returns self. The setter version can also be called in the character_spacing= form.
1297
+ #
1298
+ # If the +amount+ and a block are provided, the changed character spacing is only active
1299
+ # during the block by saving and restoring the graphics state.
1300
+ #
1301
+ # Examples:
1302
+ #
1303
+ # canvas.character_spacing(0.25)
1304
+ # canvas.character_spacing # => 0.25
1305
+ # canvas.character_spacing = 0.5 # => 0.5
1306
+ #
1307
+ # canvas.character_spacing(0.10) do
1308
+ # canvas.character_spacing # => 0.10
1309
+ # end
1310
+ # canvas.character_spacing # => 0.5
1311
+ #
1312
+ # See: PDF1.7 s9.3.2
1313
+ def character_spacing(amount = nil, &bk)
1314
+ gs_getter_setter(:character_spacing, :Tc, amount, &bk)
1315
+ end
1316
+ alias :character_spacing= :character_spacing
1317
+
1318
+ # :call-seq:
1319
+ # canvas.word_spacing => current_word_spacing
1320
+ # canvas.word_spacing(amount) => canvas
1321
+ # canvas.word_spacing(amount) { block } => canvas
1322
+ #
1323
+ # The word spacing determines how much additional space is added when the ASCII space
1324
+ # character is encountered in a text. For horizontal writing positive values increase the
1325
+ # distance between two words, whereas for vertical writing negative values increase the
1326
+ # distance.
1327
+ #
1328
+ # Returns the current word spacing value (see Content::GraphicsState#word_spacing) when no
1329
+ # argument is given. Otherwise sets the word spacing using the +amount+ argument and returns
1330
+ # self. The setter version can also be called in the word_spacing= form.
1331
+ #
1332
+ # If the +amount+ and a block are provided, the changed word spacing is only active during
1333
+ # the block by saving and restoring the graphics state.
1334
+ #
1335
+ # Examples:
1336
+ #
1337
+ # canvas.word_spacing(0.25)
1338
+ # canvas.word_spacing # => 0.25
1339
+ # canvas.word_spacing = 0.5 # => 0.5
1340
+ #
1341
+ # canvas.word_spacing(0.10) do
1342
+ # canvas.word_spacing # => 0.10
1343
+ # end
1344
+ # canvas.word_spacing # => 0.5
1345
+ #
1346
+ # See: PDF1.7 s9.3.3
1347
+ def word_spacing(amount = nil, &bk)
1348
+ gs_getter_setter(:word_spacing, :Tw, amount, &bk)
1349
+ end
1350
+ alias :word_spacing= :word_spacing
1351
+
1352
+ # :call-seq:
1353
+ # canvas.horizontal_scaling => current_horizontal_scaling
1354
+ # canvas.horizontal_scaling(percent) => canvas
1355
+ # canvas.horizontal_scaling(percent) { block } => canvas
1356
+ #
1357
+ # The horizontal scaling adjusts the width of text character glyphs by stretching or
1358
+ # compressing them in the horizontal direction. The value is specified as percent of the
1359
+ # normal width.
1360
+ #
1361
+ # Returns the current horizontal scaling value (see Content::GraphicsState#horizontal_scaling)
1362
+ # when no argument is given. Otherwise sets the horizontal scaling using the +percent+
1363
+ # argument and returns self. The setter version can also be called in the horizontal_scaling=
1364
+ # form.
1365
+ #
1366
+ # If the +percent+ and a block are provided, the changed horizontal scaling is only active
1367
+ # during the block by saving and restoring the graphics state.
1368
+ #
1369
+ # Examples:
1370
+ #
1371
+ # canvas.horizontal_scaling(50) # each glyph has only 50% width
1372
+ # canvas.horizontal_scaling # => 50
1373
+ # canvas.horizontal_scaling = 125 # => 125
1374
+ #
1375
+ # canvas.horizontal_scaling(75) do
1376
+ # canvas.horizontal_scaling # => 75
1377
+ # end
1378
+ # canvas.horizontal_scaling # => 125
1379
+ #
1380
+ # See: PDF1.7 s9.3.4
1381
+ def horizontal_scaling(amount = nil, &bk)
1382
+ gs_getter_setter(:horizontal_scaling, :Tz, amount, &bk)
1383
+ end
1384
+ alias :horizontal_scaling= :horizontal_scaling
1385
+
1386
+ # :call-seq:
1387
+ # canvas.leading => current_leading
1388
+ # canvas.leading(amount) => canvas
1389
+ # canvas.leading(amount) { block } => canvas
1390
+ #
1391
+ # The leading specifies the vertical distance between the baselines of adjacent text lines.
1392
+ #
1393
+ # Returns the current leading value (see Content::GraphicsState#leading) when no argument is
1394
+ # given. Otherwise sets the leading using the +amount+ argument and returns self. The setter
1395
+ # version can also be called in the leading= form.
1396
+ #
1397
+ # If the +amount+ and a block are provided, the changed leading is only active during the
1398
+ # block by saving and restoring the graphics state.
1399
+ #
1400
+ # Examples:
1401
+ #
1402
+ # canvas.leading(14.5)
1403
+ # canvas.leading # => 14.5
1404
+ # canvas.leading = 10 # => 10
1405
+ #
1406
+ # canvas.leading(25) do
1407
+ # canvas.leading # => 25
1408
+ # end
1409
+ # canvas.leading # => 10
1410
+ #
1411
+ # See: PDF1.7 s9.3.5
1412
+ def leading(amount = nil, &bk)
1413
+ gs_getter_setter(:leading, :TL, amount, &bk)
1414
+ end
1415
+ alias :leading= :leading
1416
+
1417
+ # :call-seq:
1418
+ # canvas.text_rendering_mode => current_text_rendering_mode
1419
+ # canvas.text_rendering_mode(mode) => canvas
1420
+ # canvas.text_rendering_mode(mode) { block } => canvas
1421
+ #
1422
+ # The text rendering mode determines if and how glyphs are rendered. The +mode+ parameter
1423
+ # can either be a valid integer or one of the symbols +:fill+, +:stroke+, +:fill_stroke+,
1424
+ # +:invisible+, +:fill_clip+, +:stroke_clip+, +:fill_stroke_clip+ or +:clip+ (see
1425
+ # TextRenderingMode.normalize for details). Note that the return value is always a
1426
+ # normalized text rendering mode value.
1427
+ #
1428
+ # Returns the current text rendering mode value (see
1429
+ # Content::GraphicsState#text_rendering_mode) when no argument is given. Otherwise sets the
1430
+ # text rendering mode using the +mode+ argument and returns self. The setter version can also
1431
+ # be called in the text_rendering_mode= form.
1432
+ #
1433
+ # If the +mode+ and a block are provided, the changed text rendering mode is only active
1434
+ # during the block by saving and restoring the graphics state.
1435
+ #
1436
+ # Examples:
1437
+ #
1438
+ # canvas.text_rendering_mode(:fill)
1439
+ # canvas.text_rendering_mode # => #<NamedValue @name=:fill, @value = 0>
1440
+ # canvas.text_rendering_mode = :stroke # => #<NamedValue @name=:stroke, @value = 1>
1441
+ #
1442
+ # canvas.text_rendering_mode(3) do
1443
+ # canvas.text_rendering_mode # => #<NamedValue @name=:invisible, @value = 3>
1444
+ # end
1445
+ # canvas.text_rendering_mode # => #<NamedValue @name=:stroke, @value = 1>
1446
+ #
1447
+ # See: PDF1.7 s9.3.6
1448
+ def text_rendering_mode(m = nil, &bk)
1449
+ gs_getter_setter(:text_rendering_mode, :Tr, m && TextRenderingMode.normalize(m), &bk)
1450
+ end
1451
+ alias :text_rendering_mode= :text_rendering_mode
1452
+
1453
+ # :call-seq:
1454
+ # canvas.text_rise => current_text_rise
1455
+ # canvas.text_rise(amount) => canvas
1456
+ # canvas.text_rise(amount) { block } => canvas
1457
+ #
1458
+ # The text rise specifies the vertical distance to move the baseline up or down from its
1459
+ # default location. Positive values move the baseline up, negative values down.
1460
+ #
1461
+ # Returns the current text rise value (see Content::GraphicsState#text_rise) when no argument
1462
+ # is given. Otherwise sets the text rise using the +amount+ argument and returns self. The
1463
+ # setter version can also be called in the text_rise= form.
1464
+ #
1465
+ # If the +amount+ and a block are provided, the changed text rise is only active during the
1466
+ # block by saving and restoring the graphics state.
1467
+ #
1468
+ # Examples:
1469
+ #
1470
+ # canvas.text_rise(5)
1471
+ # canvas.text_rise # => 5
1472
+ # canvas.text_rise = 10 # => 10
1473
+ #
1474
+ # canvas.text_rise(15) do
1475
+ # canvas.text_rise # => 15
1476
+ # end
1477
+ # canvas.text_rise # => 10
1478
+ #
1479
+ # See: PDF1.7 s9.3.7
1480
+ def text_rise(amount = nil, &bk)
1481
+ gs_getter_setter(:text_rise, :Ts, amount, &bk)
1482
+ end
1483
+ alias :text_rise= :text_rise
1484
+
1485
+ # :call-seq:
1486
+ # canvas.begin_text(force_new: false) -> canvas
1487
+ #
1488
+ # Begins a new text object.
1489
+ #
1490
+ # If +force+ is +true+ and the current graphics object is already a text object, it is ended
1491
+ # and a new text object is begun.
1492
+ #
1493
+ # See: PDF1.7 s9.4.1
1494
+ def begin_text(force_new: false)
1495
+ raise_unless_at_page_description_level_or_in_text
1496
+ end_text if force_new
1497
+ invoke0(:BT) if graphics_object == :none
1498
+ self
1499
+ end
1500
+
1501
+ # :call-seq:
1502
+ # canvas.end_text -> canvas
1503
+ #
1504
+ # Ends the current text object.
1505
+ #
1506
+ # See: PDF1.7 s9.4.1
1507
+ def end_text
1508
+ raise_unless_at_page_description_level_or_in_text
1509
+ invoke0(:ET) if graphics_object == :text
1510
+ self
1511
+ end
1512
+
1513
+ # :call-seq:
1514
+ # canvas.text_matrix(a, b, c, d, e, f) => canvas
1515
+ #
1516
+ # Sets the text matrix (and the text line matrix) to the given matrix and returns self.
1517
+ #
1518
+ # The given values are interpreted as a matrix in the following way:
1519
+ #
1520
+ # a b 0
1521
+ # c d 0
1522
+ # e f 1
1523
+ #
1524
+ # Examples:
1525
+ #
1526
+ # canvas.begin_text
1527
+ # canvas.text_matrix(1, 0, 0, 1, 100, 100)
1528
+ #
1529
+ # See: PDF1.7 s9.4.2
1530
+ def text_matrix(a, b, c, d, e, f)
1531
+ begin_text
1532
+ invoke(:Tm, a, b, c, d, e, f)
1533
+ self
1534
+ end
1535
+
1536
+ # :call-seq:
1537
+ # canvas.move_text_cursor(offset: nil, absolute: true) -> canvas
1538
+ #
1539
+ # Moves the text cursor by modifying the text and text line matrices.
1540
+ #
1541
+ # If +offset+ is not specified, the text cursor is moved to the start of the next text line
1542
+ # using #leading as vertical offset.
1543
+ #
1544
+ # Otherwise, the arguments +offset+, which has to be an array of the form [x, y], and
1545
+ # +absolute+ work together:
1546
+ #
1547
+ # * If +absolute+ is +true+, then the text and text line matrices are set to [1, 0, 0, 1, x,
1548
+ # y], placing the origin of text space, and therefore the text cursor, at [x, y].
1549
+ #
1550
+ # Note that +absolute+ has to be understood in terms of the text matrix since for the actual
1551
+ # rendering the current transformation matrix is multiplied with the text matrix.
1552
+ #
1553
+ # * If +absolute+ is +false+, then the text cursor is moved to the start of the next line,
1554
+ # offset from the start of the current line (the origin of the text line matrix) by
1555
+ # +offset+.
1556
+ #
1557
+ # See: #show_glyphs
1558
+ def move_text_cursor(offset: nil, absolute: true)
1559
+ begin_text
1560
+ if offset
1561
+ if absolute
1562
+ text_matrix(1, 0, 0, 1, offset[0], offset[1])
1563
+ else
1564
+ invoke2(:Td, offset[0], offset[1])
1565
+ end
1566
+ else
1567
+ invoke0(:"T*")
1568
+ end
1569
+ self
1570
+ end
1571
+
1572
+ # :call-seq:
1573
+ # canvas.text_cursor -> [x, y]
1574
+ #
1575
+ # Returns the position of the text cursor, i.e. the origin of the current text matrix.
1576
+ #
1577
+ # Note that this method can only be called while the current graphic object is a text object
1578
+ # since the text matrix is otherwise undefined.
1579
+ def text_cursor
1580
+ raise_unless_in_text
1581
+ graphics_state.tm.evaluate(0, 0)
1582
+ end
1583
+
1584
+ # :call-seq:
1585
+ # canvas.font => current_font
1586
+ # canvas.font(name, size: nil, **options) => canvas
1587
+ #
1588
+ # Specifies the font that should be used when showing text.
1589
+ #
1590
+ # A valid font size need to be provided on the first invocation, otherwise an error is raised.
1591
+ #
1592
+ # *Note* that this method returns the font object itself, not the PDF dictionary representing
1593
+ # the font!
1594
+ #
1595
+ # If +size+ is specified, the #font_size method is invoked with it as argument. All other
1596
+ # options are passed on to the font loaders (see HexaPDF::FontLoader) that are used for
1597
+ # loading the specified font.
1598
+ #
1599
+ # Returns the current font object when no argument is given.
1600
+ #
1601
+ # Examples:
1602
+ #
1603
+ # canvas.font("Times", variant: :bold, size: 12)
1604
+ # canvas.font # => font object
1605
+ # canvas.font = "Times"
1606
+ #
1607
+ # See: PDF1.7 s9.2.2
1608
+ def font(name = nil, size: nil, **options)
1609
+ if name
1610
+ @font = context.document.fonts.load(name, options)
1611
+ if size
1612
+ font_size(size)
1613
+ else
1614
+ size = font_size
1615
+ raise HexaPDF::Error, "No valid font size set" if size <= 0
1616
+ invoke_font_operator(@font.dict, size)
1617
+ end
1618
+ self
1619
+ else
1620
+ @font
1621
+ end
1622
+ end
1623
+ alias :font= :font
1624
+
1625
+ # :call-seq:
1626
+ # canvas.font_size => font_size
1627
+ # canvas.font_size(size, leading: size * 1.2) => canvas
1628
+ #
1629
+ # Specifies the font size.
1630
+ #
1631
+ # Note that an error is raised if no font has been set before!
1632
+ #
1633
+ # The leading can be additionally set and defaults to the font size times 1.2. If the leading
1634
+ # should not be changed, +nil+ has to be passed for +leading+.
1635
+ #
1636
+ # Returns the current font size when no argument is given.
1637
+ #
1638
+ # Examples:
1639
+ #
1640
+ # canvas.font_size(12)
1641
+ # canvas.font_size # => 12
1642
+ # canvas.font_size(12, leading: 20)
1643
+ # canvas.font_size = 12
1644
+ #
1645
+ # See: PDF1.7 s9.2.2
1646
+ def font_size(size = nil, leading: size && size * 1.2)
1647
+ if size
1648
+ unless @font
1649
+ raise HexaPDF::Error, "A font needs to be set before the font size can be set"
1650
+ end
1651
+ invoke_font_operator(@font.dict, size)
1652
+ self.leading(leading) if leading
1653
+ self
1654
+ else
1655
+ graphics_state.font_size
1656
+ end
1657
+ end
1658
+ alias :font_size= :font_size
1659
+
1660
+ # :call-seq:
1661
+ # canvas.text(text) -> canvas
1662
+ # canvas.text(text, at: [x, y]) -> canvas
1663
+ #
1664
+ # Shows the given text string.
1665
+ #
1666
+ # If no position is provided, the text is positioned at the current position of the text
1667
+ # cursor (the origin in case of a new text object or otherwise after the last shown text).
1668
+ #
1669
+ # The text string may contain any valid Unicode newline separator and if so, multiple lines
1670
+ # are shown, using #leading for offsetting the lines.
1671
+ #
1672
+ # Note that there are no provisions to make sure that all text is visible! So if the text
1673
+ # string is too long, it will just flow off the page and be cut off.
1674
+ #
1675
+ # Examples:
1676
+ #
1677
+ # canvas.font('Times', size: 12)
1678
+ # canvas.text("This is a \n multiline text", at: [100, 100])
1679
+ #
1680
+ # See: http://www.unicode.org/reports/tr18/#Line_Boundaries
1681
+ def text(text, at: nil)
1682
+ move_text_cursor(offset: at) if at
1683
+ lines = text.split(/\u{D A}|(?!\u{D A})[\u{A}-\u{D}\u{85}\u{2028}\u{2029}]/, -1)
1684
+ lines.each_with_index do |str, index|
1685
+ show_glyphs(@font.decode_utf8(str))
1686
+ move_text_cursor unless index == lines.length - 1
1687
+ end
1688
+ self
1689
+ end
1690
+
1691
+ # :call-seq:
1692
+ # canvas.show_glyphs(glyphs) -> canvas
1693
+ #
1694
+ # Low-level method for actually showing text on the canvas.
1695
+ #
1696
+ # The argument +data+ needs to be a an array of glyph objects valid for the current font,
1697
+ # optionally interspersed with numbers for kerning.
1698
+ #
1699
+ # Text is always shown at the current position of the text cursor, i.e. the origin of the text
1700
+ # matrix. To move the text cursor to somewhere else use #move_text_cursor before calling this
1701
+ # method.
1702
+ #
1703
+ # The text matrix is updated to correctly represent the graphics state after the invocation.
1704
+ #
1705
+ # This method is usually not invoked directly but by higher level methods like #show_text.
1706
+ def show_glyphs(data)
1707
+ begin_text
1708
+
1709
+ result = [''.b]
1710
+ offset = 0
1711
+ data.each do |item|
1712
+ if item.kind_of?(Numeric)
1713
+ result << item << ''.b
1714
+ offset -= item * graphics_state.scaled_font_size
1715
+ else
1716
+ encoded = @font.encode(item)
1717
+ result[-1] << encoded
1718
+
1719
+ offset += item.width * graphics_state.scaled_font_size +
1720
+ graphics_state.scaled_character_spacing
1721
+ offset += graphics_state.scaled_word_spacing if encoded.length == 1 && item.space?
1722
+ end
1723
+ end
1724
+
1725
+ invoke1(:TJ, result)
1726
+ graphics_state.tm.translate(offset, 0)
1727
+ self
1728
+ end
1729
+
1730
+ private
1731
+
1732
+ # Invokes the given operator with the operands and serializes it.
1733
+ def invoke(operator, *operands)
1734
+ @operators[operator].invoke(self, *operands)
1735
+ serialize(operator, *operands)
1736
+ end
1737
+
1738
+ # Serializes the operator with the operands to the content stream.
1739
+ def serialize(operator, *operands)
1740
+ @contents << @operators[operator].serialize(@serializer, *operands)
1741
+ end
1742
+
1743
+ # Optimized method for zero operands.
1744
+ def invoke0(operator)
1745
+ @operators[operator].invoke(self)
1746
+ @contents << @operators[operator].serialize(@serializer)
1747
+ end
1748
+
1749
+ # Optimized method for one operand.
1750
+ def invoke1(operator, op1)
1751
+ @operators[operator].invoke(self, op1)
1752
+ @contents << @operators[operator].serialize(@serializer, op1)
1753
+ end
1754
+
1755
+ # Optimized method for two operands.
1756
+ def invoke2(operator, op1, op2)
1757
+ @operators[operator].invoke(self, op1, op2)
1758
+ @contents << @operators[operator].serialize(@serializer, op1, op2)
1759
+ end
1760
+
1761
+ # Invokes the font operator using the given PDF font dictionary.
1762
+ def invoke_font_operator(font, font_size)
1763
+ if graphics_state.font != font || graphics_state.font_size != font_size
1764
+ invoke(:Tf, resources.add_font(font), font_size)
1765
+ end
1766
+ end
1767
+
1768
+ # Raises an error unless the current graphics object is a path.
1769
+ def raise_unless_in_path
1770
+ if graphics_object != :path
1771
+ raise HexaPDF::Error, "Operation only allowed when current graphics object is a path"
1772
+ end
1773
+ end
1774
+
1775
+ # Raises an error unless the current graphics object is a path or a clipping path.
1776
+ def raise_unless_in_path_or_clipping_path
1777
+ if graphics_object != :path && graphics_object != :clipping_path
1778
+ raise HexaPDF::Error, "Operation only allowed when current graphics object is a " \
1779
+ "path or clipping path"
1780
+ end
1781
+ end
1782
+
1783
+ # Raises an error unless the current graphics object is none, i.e. the page description
1784
+ # level.
1785
+ def raise_unless_at_page_description_level
1786
+ end_text if graphics_object == :text
1787
+ if graphics_object != :none
1788
+ raise HexaPDF::Error, "Operation only allowed when there is no current graphics object"
1789
+ end
1790
+ end
1791
+
1792
+ # Raises an error unless the current graphics object is none or a text object.
1793
+ def raise_unless_at_page_description_level_or_in_text
1794
+ if graphics_object != :none && graphics_object != :text
1795
+ raise HexaPDF::Error, "Operation only allowed when current graphics object is a " \
1796
+ "text object or if there is no current object"
1797
+ end
1798
+ end
1799
+
1800
+ # Raises an error unless the current graphics object is none or a path object.
1801
+ def raise_unless_at_page_description_level_or_in_path
1802
+ end_text if graphics_object == :text
1803
+ if graphics_object != :none && graphics_object != :path
1804
+ raise HexaPDF::Error, "Operation only allowed when current graphics object is a " \
1805
+ "path object or if there is no current object"
1806
+ end
1807
+ end
1808
+
1809
+ # Raises an error unless the current graphics object is a text object.
1810
+ def raise_unless_in_text
1811
+ if graphics_object != :text
1812
+ raise HexaPDF::Error, "Operation only allowed when current graphics object is a " \
1813
+ "text object"
1814
+ end
1815
+ end
1816
+
1817
+ # Utility method that abstracts the implementation of the stroke and fill color methods.
1818
+ def color_getter_setter(name, color, rg, g, k, cs, scn)
1819
+ color.flatten!
1820
+ if color.length > 0
1821
+ raise_unless_at_page_description_level_or_in_text
1822
+ color = color_from_specification(color)
1823
+
1824
+ save_graphics_state if block_given?
1825
+ if color != graphics_state.send(name)
1826
+ case color.color_space.family
1827
+ when :DeviceRGB then serialize(rg, *color.components)
1828
+ when :DeviceGray then serialize(g, *color.components)
1829
+ when :DeviceCMYK then serialize(k, *color.components)
1830
+ else
1831
+ if color.color_space != graphics_state.send(name).color_space
1832
+ serialize(cs, resources.add_color_space(color.color_space))
1833
+ end
1834
+ serialize(scn, *color.components)
1835
+ end
1836
+ graphics_state.send(:"#{name}=", color)
1837
+ end
1838
+
1839
+ if block_given?
1840
+ yield
1841
+ restore_graphics_state
1842
+ end
1843
+
1844
+ self
1845
+ elsif block_given?
1846
+ raise ArgumentError, "Block only allowed with arguments"
1847
+ else
1848
+ graphics_state.send(name)
1849
+ end
1850
+ end
1851
+
1852
+ # Creates a color object from the given color specification. See #stroke_color for details
1853
+ # on the possible color specifications.
1854
+ def color_from_specification(spec)
1855
+ if spec.length == 1 && spec[0].kind_of?(String)
1856
+ resources.color_space(:DeviceRGB).color(*spec[0].scan(/../).map!(&:hex))
1857
+ elsif spec.length == 1 && spec[0].respond_to?(:color_space)
1858
+ spec[0]
1859
+ else
1860
+ resources.color_space(color_space_for_components(spec)).color(*spec)
1861
+ end
1862
+ end
1863
+
1864
+ # Returns the name of the device color space that should be used for creating a color object
1865
+ # from the components array.
1866
+ def color_space_for_components(components)
1867
+ case components.length
1868
+ when 1 then :DeviceGray
1869
+ when 3 then :DeviceRGB
1870
+ when 4 then :DeviceCMYK
1871
+ else
1872
+ raise ArgumentError, "Invalid number of color components, 1|3|4 expected, " \
1873
+ "#{components.length} given"
1874
+ end
1875
+ end
1876
+
1877
+ # Utility method that abstracts the implementation of a graphics state parameter
1878
+ # getter/setter method with a call sequence of:
1879
+ #
1880
+ # canvas.method # => cur_value
1881
+ # canvas.method(new_value) # => canvas
1882
+ # canvas.method(new_value) { block } # => canvas
1883
+ #
1884
+ # +name+::
1885
+ # The name (Symbol) of the graphics state parameter for fetching the value from the
1886
+ # GraphicState.
1887
+ #
1888
+ # +op+::
1889
+ # The operator (Symbol) which should be invoked if the value is different from the current
1890
+ # value of the graphics state parameter.
1891
+ #
1892
+ # +value+::
1893
+ # The new value of the graphics state parameter, or +nil+ if the getter functionality is
1894
+ # needed.
1895
+ def gs_getter_setter(name, op, value)
1896
+ if !value.nil?
1897
+ raise_unless_at_page_description_level_or_in_text
1898
+ save_graphics_state if block_given?
1899
+ if graphics_state.send(name) != value
1900
+ value.respond_to?(:to_operands) ? invoke(op, *value.to_operands) : invoke1(op, value)
1901
+ end
1902
+ if block_given?
1903
+ yield
1904
+ restore_graphics_state
1905
+ end
1906
+ self
1907
+ elsif block_given?
1908
+ raise ArgumentError, "Block only allowed with an argument"
1909
+ else
1910
+ graphics_state.send(name)
1911
+ end
1912
+ end
1913
+
1914
+ # Modifies and checks the array +points+ so that polylines and polygons work correctly.
1915
+ def check_poly_points(points)
1916
+ if points.length < 4
1917
+ raise ArgumentError, "At least two points needed to make one line segment"
1918
+ elsif points.length.odd?
1919
+ raise ArgumentError, "Missing y-coordinate for last point"
1920
+ end
1921
+ end
1922
+
1923
+ # Used for calculating the optimal distance of the control points.
1924
+ #
1925
+ # See: http://itc.ktu.lt/itc354/Riskus354.pdf, p373 right column
1926
+ KAPPA = 0.55191496 #:nodoc:
1927
+
1928
+ # Appends a line with a rounded corner from the current point. The corner is specified by
1929
+ # the three points (x0, y0), (x1, y1) and (x2, y2) where (x1, y1) is the corner point.
1930
+ def line_with_rounded_corner(x0, y0, x1, y1, x2, y2, radius)
1931
+ p0 = point_on_line(x1, y1, x0, y0, distance: radius)
1932
+ p3 = point_on_line(x1, y1, x2, y2, distance: radius)
1933
+ p1 = point_on_line(p0[0], p0[1], x1, y1, distance: KAPPA * radius)
1934
+ p2 = point_on_line(p3[0], p3[1], x1, y1, distance: KAPPA * radius)
1935
+ line_to(p0[0], p0[1])
1936
+ curve_to(p3[0], p3[1], p1: p1, p2: p2)
1937
+ end
1938
+
1939
+ # Given two points p0 = (x0, y0) and p1 = (x1, y1), returns the point on the line through
1940
+ # these points that is +distance+ units away from p0.
1941
+ #
1942
+ # v = p1 - p0
1943
+ # result = p0 + distance * v/norm(v)
1944
+ def point_on_line(x0, y0, x1, y1, distance:)
1945
+ norm = Math.sqrt((x1 - x0)**2 + (y1 - y0)**2)
1946
+ [x0 + distance / norm * (x1 - x0), y0 + distance / norm * (y1 - y0)]
1947
+ end
1948
+
1949
+ # Calculates and returns the requested dimensions for the rectangular object with the given
1950
+ # +width+ and +height+ based on the options.
1951
+ #
1952
+ # +rwidth+::
1953
+ # The requested width. If +rheight+ is not specified, it is chosen so that the aspect
1954
+ # ratio is maintained
1955
+ #
1956
+ # +rheight+::
1957
+ # The requested height. If +rwidth+ is not specified, it is chosen so that the aspect
1958
+ # ratio is maintained
1959
+ def calculate_dimensions(width, height, rwidth: nil, rheight: nil)
1960
+ if rwidth && rheight
1961
+ [rwidth, rheight]
1962
+ elsif rwidth
1963
+ [rwidth, height * rwidth / width.to_f]
1964
+ elsif rheight
1965
+ [width * rheight / height.to_f, rheight]
1966
+ else
1967
+ [width, height]
1968
+ end
1969
+ end
1970
+
1971
+ end
1972
+
1973
+ end
1974
+ end