hexapdf 0.17.1 → 0.17.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (255) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +1024 -0
  3. data/LICENSE +29 -0
  4. data/README.md +129 -0
  5. data/Rakefile +109 -0
  6. data/agpl-3.0.txt +661 -0
  7. data/examples/001-hello_world.rb +16 -0
  8. data/examples/002-graphics.rb +275 -0
  9. data/examples/003-arcs.rb +50 -0
  10. data/examples/004-optimizing.rb +23 -0
  11. data/examples/005-merging.rb +27 -0
  12. data/examples/006-standard_pdf_fonts.rb +73 -0
  13. data/examples/007-truetype.rb +42 -0
  14. data/examples/008-show_char_bboxes.rb +55 -0
  15. data/examples/009-text_layouter_alignment.rb +47 -0
  16. data/examples/010-text_layouter_inline_boxes.rb +64 -0
  17. data/examples/011-text_layouter_line_wrapping.rb +57 -0
  18. data/examples/012-text_layouter_styling.rb +122 -0
  19. data/examples/013-text_layouter_shapes.rb +176 -0
  20. data/examples/014-text_in_polygon.rb +60 -0
  21. data/examples/015-boxes.rb +76 -0
  22. data/examples/016-frame_automatic_box_placement.rb +90 -0
  23. data/examples/017-frame_text_flow.rb +60 -0
  24. data/examples/018-composer.rb +44 -0
  25. data/examples/019-acro_form.rb +88 -0
  26. data/examples/emoji-smile.png +0 -0
  27. data/examples/emoji-wink.png +0 -0
  28. data/examples/machupicchu.jpg +0 -0
  29. data/lib/hexapdf/content/graphic_object/endpoint_arc.rb +66 -0
  30. data/lib/hexapdf/content/graphic_object/geom2d.rb +13 -0
  31. data/lib/hexapdf/version.rb +1 -1
  32. data/test/data/aes-test-vectors/CBCGFSbox-128-decrypt.data.gz +0 -0
  33. data/test/data/aes-test-vectors/CBCGFSbox-128-encrypt.data.gz +0 -0
  34. data/test/data/aes-test-vectors/CBCGFSbox-192-decrypt.data.gz +0 -0
  35. data/test/data/aes-test-vectors/CBCGFSbox-192-encrypt.data.gz +0 -0
  36. data/test/data/aes-test-vectors/CBCGFSbox-256-decrypt.data.gz +0 -0
  37. data/test/data/aes-test-vectors/CBCGFSbox-256-encrypt.data.gz +0 -0
  38. data/test/data/aes-test-vectors/CBCKeySbox-128-decrypt.data.gz +0 -0
  39. data/test/data/aes-test-vectors/CBCKeySbox-128-encrypt.data.gz +0 -0
  40. data/test/data/aes-test-vectors/CBCKeySbox-192-decrypt.data.gz +0 -0
  41. data/test/data/aes-test-vectors/CBCKeySbox-192-encrypt.data.gz +0 -0
  42. data/test/data/aes-test-vectors/CBCKeySbox-256-decrypt.data.gz +0 -0
  43. data/test/data/aes-test-vectors/CBCKeySbox-256-encrypt.data.gz +0 -0
  44. data/test/data/aes-test-vectors/CBCVarKey-128-decrypt.data.gz +0 -0
  45. data/test/data/aes-test-vectors/CBCVarKey-128-encrypt.data.gz +0 -0
  46. data/test/data/aes-test-vectors/CBCVarKey-192-decrypt.data.gz +0 -0
  47. data/test/data/aes-test-vectors/CBCVarKey-192-encrypt.data.gz +0 -0
  48. data/test/data/aes-test-vectors/CBCVarKey-256-decrypt.data.gz +0 -0
  49. data/test/data/aes-test-vectors/CBCVarKey-256-encrypt.data.gz +0 -0
  50. data/test/data/aes-test-vectors/CBCVarTxt-128-decrypt.data.gz +0 -0
  51. data/test/data/aes-test-vectors/CBCVarTxt-128-encrypt.data.gz +0 -0
  52. data/test/data/aes-test-vectors/CBCVarTxt-192-decrypt.data.gz +0 -0
  53. data/test/data/aes-test-vectors/CBCVarTxt-192-encrypt.data.gz +0 -0
  54. data/test/data/aes-test-vectors/CBCVarTxt-256-decrypt.data.gz +0 -0
  55. data/test/data/aes-test-vectors/CBCVarTxt-256-encrypt.data.gz +0 -0
  56. data/test/data/fonts/Ubuntu-Title.ttf +0 -0
  57. data/test/data/images/cmyk.jpg +0 -0
  58. data/test/data/images/fillbytes.jpg +0 -0
  59. data/test/data/images/gray.jpg +0 -0
  60. data/test/data/images/greyscale-1bit.png +0 -0
  61. data/test/data/images/greyscale-2bit.png +0 -0
  62. data/test/data/images/greyscale-4bit.png +0 -0
  63. data/test/data/images/greyscale-8bit.png +0 -0
  64. data/test/data/images/greyscale-alpha-8bit.png +0 -0
  65. data/test/data/images/greyscale-trns-8bit.png +0 -0
  66. data/test/data/images/greyscale-with-gamma1.0.png +0 -0
  67. data/test/data/images/greyscale-with-gamma1.5.png +0 -0
  68. data/test/data/images/indexed-1bit.png +0 -0
  69. data/test/data/images/indexed-2bit.png +0 -0
  70. data/test/data/images/indexed-4bit.png +0 -0
  71. data/test/data/images/indexed-8bit.png +0 -0
  72. data/test/data/images/indexed-alpha-4bit.png +0 -0
  73. data/test/data/images/indexed-alpha-8bit.png +0 -0
  74. data/test/data/images/rgb.jpg +0 -0
  75. data/test/data/images/truecolour-8bit.png +0 -0
  76. data/test/data/images/truecolour-alpha-8bit.png +0 -0
  77. data/test/data/images/truecolour-gama-chrm-8bit.png +0 -0
  78. data/test/data/images/truecolour-srgb-8bit.png +0 -0
  79. data/test/data/images/ycck.jpg +0 -0
  80. data/test/data/minimal.pdf +44 -0
  81. data/test/data/standard-security-handler/README +9 -0
  82. data/test/data/standard-security-handler/bothpwd-aes-128bit-V4.pdf +44 -0
  83. data/test/data/standard-security-handler/bothpwd-aes-256bit-V5.pdf +0 -0
  84. data/test/data/standard-security-handler/bothpwd-arc4-128bit-V2.pdf +43 -0
  85. data/test/data/standard-security-handler/bothpwd-arc4-128bit-V4.pdf +43 -0
  86. data/test/data/standard-security-handler/bothpwd-arc4-40bit-V1.pdf +0 -0
  87. data/test/data/standard-security-handler/nopwd-aes-128bit-V4.pdf +43 -0
  88. data/test/data/standard-security-handler/nopwd-aes-256bit-V5.pdf +0 -0
  89. data/test/data/standard-security-handler/nopwd-arc4-128bit-V2.pdf +43 -0
  90. data/test/data/standard-security-handler/nopwd-arc4-128bit-V4.pdf +43 -0
  91. data/test/data/standard-security-handler/nopwd-arc4-40bit-V1.pdf +43 -0
  92. data/test/data/standard-security-handler/ownerpwd-aes-128bit-V4.pdf +0 -0
  93. data/test/data/standard-security-handler/ownerpwd-aes-256bit-V5.pdf +43 -0
  94. data/test/data/standard-security-handler/ownerpwd-arc4-128bit-V2.pdf +43 -0
  95. data/test/data/standard-security-handler/ownerpwd-arc4-128bit-V4.pdf +43 -0
  96. data/test/data/standard-security-handler/ownerpwd-arc4-40bit-V1.pdf +43 -0
  97. data/test/data/standard-security-handler/userpwd-aes-128bit-V4.pdf +43 -0
  98. data/test/data/standard-security-handler/userpwd-aes-256bit-V5.pdf +43 -0
  99. data/test/data/standard-security-handler/userpwd-arc4-128bit-V2.pdf +0 -0
  100. data/test/data/standard-security-handler/userpwd-arc4-128bit-V4.pdf +0 -0
  101. data/test/data/standard-security-handler/userpwd-arc4-40bit-V1.pdf +43 -0
  102. data/test/hexapdf/common_tokenizer_tests.rb +236 -0
  103. data/test/hexapdf/content/common.rb +39 -0
  104. data/test/hexapdf/content/graphic_object/test_arc.rb +102 -0
  105. data/test/hexapdf/content/graphic_object/test_endpoint_arc.rb +90 -0
  106. data/test/hexapdf/content/graphic_object/test_geom2d.rb +79 -0
  107. data/test/hexapdf/content/graphic_object/test_solid_arc.rb +86 -0
  108. data/test/hexapdf/content/test_canvas.rb +1279 -0
  109. data/test/hexapdf/content/test_color_space.rb +176 -0
  110. data/test/hexapdf/content/test_graphics_state.rb +151 -0
  111. data/test/hexapdf/content/test_operator.rb +619 -0
  112. data/test/hexapdf/content/test_parser.rb +99 -0
  113. data/test/hexapdf/content/test_processor.rb +163 -0
  114. data/test/hexapdf/content/test_transformation_matrix.rb +64 -0
  115. data/test/hexapdf/document/test_files.rb +72 -0
  116. data/test/hexapdf/document/test_fonts.rb +60 -0
  117. data/test/hexapdf/document/test_images.rb +72 -0
  118. data/test/hexapdf/document/test_pages.rb +130 -0
  119. data/test/hexapdf/encryption/common.rb +87 -0
  120. data/test/hexapdf/encryption/test_aes.rb +129 -0
  121. data/test/hexapdf/encryption/test_arc4.rb +39 -0
  122. data/test/hexapdf/encryption/test_fast_aes.rb +17 -0
  123. data/test/hexapdf/encryption/test_fast_arc4.rb +12 -0
  124. data/test/hexapdf/encryption/test_identity.rb +21 -0
  125. data/test/hexapdf/encryption/test_ruby_aes.rb +23 -0
  126. data/test/hexapdf/encryption/test_ruby_arc4.rb +20 -0
  127. data/test/hexapdf/encryption/test_security_handler.rb +380 -0
  128. data/test/hexapdf/encryption/test_standard_security_handler.rb +322 -0
  129. data/test/hexapdf/filter/common.rb +53 -0
  130. data/test/hexapdf/filter/test_ascii85_decode.rb +59 -0
  131. data/test/hexapdf/filter/test_ascii_hex_decode.rb +38 -0
  132. data/test/hexapdf/filter/test_crypt.rb +21 -0
  133. data/test/hexapdf/filter/test_encryption.rb +24 -0
  134. data/test/hexapdf/filter/test_flate_decode.rb +44 -0
  135. data/test/hexapdf/filter/test_lzw_decode.rb +52 -0
  136. data/test/hexapdf/filter/test_predictor.rb +219 -0
  137. data/test/hexapdf/filter/test_run_length_decode.rb +32 -0
  138. data/test/hexapdf/font/cmap/test_parser.rb +102 -0
  139. data/test/hexapdf/font/cmap/test_writer.rb +66 -0
  140. data/test/hexapdf/font/encoding/test_base.rb +45 -0
  141. data/test/hexapdf/font/encoding/test_difference_encoding.rb +29 -0
  142. data/test/hexapdf/font/encoding/test_glyph_list.rb +59 -0
  143. data/test/hexapdf/font/encoding/test_zapf_dingbats_encoding.rb +16 -0
  144. data/test/hexapdf/font/test_cmap.rb +104 -0
  145. data/test/hexapdf/font/test_encoding.rb +27 -0
  146. data/test/hexapdf/font/test_invalid_glyph.rb +34 -0
  147. data/test/hexapdf/font/test_true_type_wrapper.rb +186 -0
  148. data/test/hexapdf/font/test_type1_wrapper.rb +107 -0
  149. data/test/hexapdf/font/true_type/common.rb +17 -0
  150. data/test/hexapdf/font/true_type/table/common.rb +27 -0
  151. data/test/hexapdf/font/true_type/table/test_cmap.rb +47 -0
  152. data/test/hexapdf/font/true_type/table/test_cmap_subtable.rb +141 -0
  153. data/test/hexapdf/font/true_type/table/test_directory.rb +30 -0
  154. data/test/hexapdf/font/true_type/table/test_glyf.rb +58 -0
  155. data/test/hexapdf/font/true_type/table/test_head.rb +56 -0
  156. data/test/hexapdf/font/true_type/table/test_hhea.rb +26 -0
  157. data/test/hexapdf/font/true_type/table/test_hmtx.rb +30 -0
  158. data/test/hexapdf/font/true_type/table/test_kern.rb +61 -0
  159. data/test/hexapdf/font/true_type/table/test_loca.rb +33 -0
  160. data/test/hexapdf/font/true_type/table/test_maxp.rb +50 -0
  161. data/test/hexapdf/font/true_type/table/test_name.rb +76 -0
  162. data/test/hexapdf/font/true_type/table/test_os2.rb +55 -0
  163. data/test/hexapdf/font/true_type/table/test_post.rb +78 -0
  164. data/test/hexapdf/font/true_type/test_builder.rb +42 -0
  165. data/test/hexapdf/font/true_type/test_font.rb +116 -0
  166. data/test/hexapdf/font/true_type/test_optimizer.rb +26 -0
  167. data/test/hexapdf/font/true_type/test_subsetter.rb +73 -0
  168. data/test/hexapdf/font/true_type/test_table.rb +48 -0
  169. data/test/hexapdf/font/type1/common.rb +6 -0
  170. data/test/hexapdf/font/type1/test_afm_parser.rb +65 -0
  171. data/test/hexapdf/font/type1/test_font.rb +104 -0
  172. data/test/hexapdf/font/type1/test_font_metrics.rb +22 -0
  173. data/test/hexapdf/font/type1/test_pfb_parser.rb +37 -0
  174. data/test/hexapdf/font_loader/test_from_configuration.rb +43 -0
  175. data/test/hexapdf/font_loader/test_from_file.rb +36 -0
  176. data/test/hexapdf/font_loader/test_standard14.rb +33 -0
  177. data/test/hexapdf/image_loader/test_jpeg.rb +93 -0
  178. data/test/hexapdf/image_loader/test_pdf.rb +47 -0
  179. data/test/hexapdf/image_loader/test_png.rb +259 -0
  180. data/test/hexapdf/layout/test_box.rb +154 -0
  181. data/test/hexapdf/layout/test_frame.rb +350 -0
  182. data/test/hexapdf/layout/test_image_box.rb +73 -0
  183. data/test/hexapdf/layout/test_inline_box.rb +71 -0
  184. data/test/hexapdf/layout/test_line.rb +206 -0
  185. data/test/hexapdf/layout/test_style.rb +790 -0
  186. data/test/hexapdf/layout/test_text_box.rb +140 -0
  187. data/test/hexapdf/layout/test_text_fragment.rb +375 -0
  188. data/test/hexapdf/layout/test_text_layouter.rb +758 -0
  189. data/test/hexapdf/layout/test_text_shaper.rb +62 -0
  190. data/test/hexapdf/layout/test_width_from_polygon.rb +109 -0
  191. data/test/hexapdf/task/test_dereference.rb +51 -0
  192. data/test/hexapdf/task/test_optimize.rb +162 -0
  193. data/test/hexapdf/test_composer.rb +258 -0
  194. data/test/hexapdf/test_configuration.rb +93 -0
  195. data/test/hexapdf/test_data_dir.rb +32 -0
  196. data/test/hexapdf/test_dictionary.rb +340 -0
  197. data/test/hexapdf/test_dictionary_fields.rb +269 -0
  198. data/test/hexapdf/test_document.rb +641 -0
  199. data/test/hexapdf/test_filter.rb +100 -0
  200. data/test/hexapdf/test_importer.rb +106 -0
  201. data/test/hexapdf/test_object.rb +258 -0
  202. data/test/hexapdf/test_parser.rb +645 -0
  203. data/test/hexapdf/test_pdf_array.rb +169 -0
  204. data/test/hexapdf/test_rectangle.rb +73 -0
  205. data/test/hexapdf/test_reference.rb +50 -0
  206. data/test/hexapdf/test_revision.rb +188 -0
  207. data/test/hexapdf/test_revisions.rb +196 -0
  208. data/test/hexapdf/test_serializer.rb +195 -0
  209. data/test/hexapdf/test_stream.rb +274 -0
  210. data/test/hexapdf/test_tokenizer.rb +80 -0
  211. data/test/hexapdf/test_type.rb +18 -0
  212. data/test/hexapdf/test_writer.rb +140 -0
  213. data/test/hexapdf/test_xref_section.rb +61 -0
  214. data/test/hexapdf/type/acro_form/test_appearance_generator.rb +795 -0
  215. data/test/hexapdf/type/acro_form/test_button_field.rb +308 -0
  216. data/test/hexapdf/type/acro_form/test_choice_field.rb +220 -0
  217. data/test/hexapdf/type/acro_form/test_field.rb +259 -0
  218. data/test/hexapdf/type/acro_form/test_form.rb +357 -0
  219. data/test/hexapdf/type/acro_form/test_signature_field.rb +38 -0
  220. data/test/hexapdf/type/acro_form/test_text_field.rb +201 -0
  221. data/test/hexapdf/type/acro_form/test_variable_text_field.rb +88 -0
  222. data/test/hexapdf/type/actions/test_launch.rb +24 -0
  223. data/test/hexapdf/type/actions/test_uri.rb +23 -0
  224. data/test/hexapdf/type/annotations/test_markup_annotation.rb +22 -0
  225. data/test/hexapdf/type/annotations/test_text.rb +34 -0
  226. data/test/hexapdf/type/annotations/test_widget.rb +225 -0
  227. data/test/hexapdf/type/test_annotation.rb +97 -0
  228. data/test/hexapdf/type/test_catalog.rb +48 -0
  229. data/test/hexapdf/type/test_cid_font.rb +61 -0
  230. data/test/hexapdf/type/test_file_specification.rb +141 -0
  231. data/test/hexapdf/type/test_font.rb +67 -0
  232. data/test/hexapdf/type/test_font_descriptor.rb +61 -0
  233. data/test/hexapdf/type/test_font_simple.rb +176 -0
  234. data/test/hexapdf/type/test_font_true_type.rb +31 -0
  235. data/test/hexapdf/type/test_font_type0.rb +120 -0
  236. data/test/hexapdf/type/test_font_type1.rb +142 -0
  237. data/test/hexapdf/type/test_font_type3.rb +26 -0
  238. data/test/hexapdf/type/test_form.rb +120 -0
  239. data/test/hexapdf/type/test_image.rb +261 -0
  240. data/test/hexapdf/type/test_info.rb +9 -0
  241. data/test/hexapdf/type/test_object_stream.rb +117 -0
  242. data/test/hexapdf/type/test_page.rb +598 -0
  243. data/test/hexapdf/type/test_page_tree_node.rb +315 -0
  244. data/test/hexapdf/type/test_resources.rb +209 -0
  245. data/test/hexapdf/type/test_trailer.rb +116 -0
  246. data/test/hexapdf/type/test_xref_stream.rb +143 -0
  247. data/test/hexapdf/utils/test_bit_field.rb +63 -0
  248. data/test/hexapdf/utils/test_bit_stream.rb +69 -0
  249. data/test/hexapdf/utils/test_graphics_helpers.rb +37 -0
  250. data/test/hexapdf/utils/test_lru_cache.rb +22 -0
  251. data/test/hexapdf/utils/test_object_hash.rb +120 -0
  252. data/test/hexapdf/utils/test_pdf_doc_encoding.rb +18 -0
  253. data/test/hexapdf/utils/test_sorted_tree_node.rb +239 -0
  254. data/test/test_helper.rb +58 -0
  255. metadata +263 -3
@@ -0,0 +1,1279 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ require 'test_helper'
4
+ require_relative 'common'
5
+ require 'hexapdf/content/canvas'
6
+ require 'hexapdf/document'
7
+ require 'hexapdf/content/processor'
8
+ require 'hexapdf/content/parser'
9
+
10
+ describe HexaPDF::Content::Canvas do
11
+ before do
12
+ @doc = HexaPDF::Document.new
13
+ @doc.config['graphic_object.arc.max_curves'] = 4
14
+ @page = @doc.pages.add
15
+ @canvas = @page.canvas
16
+ end
17
+
18
+ # Asserts that a specific operator is invoked when the block is executed.
19
+ def assert_operator_invoked(op, *args)
20
+ mock = Minitest::Mock.new
21
+ if args.empty?
22
+ mock.expect(:invoke, nil) { true }
23
+ mock.expect(:serialize, '') { true }
24
+ else
25
+ mock.expect(:invoke, nil, [@canvas] + args)
26
+ mock.expect(:serialize, '', [@canvas.instance_variable_get(:@serializer)] + args)
27
+ end
28
+ op_before = @canvas.instance_variable_get(:@operators)[op]
29
+ @canvas.instance_variable_get(:@operators)[op] = mock
30
+ yield
31
+ assert(mock.verify)
32
+ ensure
33
+ @canvas.instance_variable_get(:@operators)[op] = op_before
34
+ end
35
+
36
+ # Asserts that the block raises an error when in one of the given graphics objects.
37
+ def assert_raises_in_graphics_object(*objects, &block)
38
+ objects.each do |graphics_object|
39
+ @canvas.graphics_object = graphics_object
40
+ assert_raises(HexaPDF::Error, &block)
41
+ end
42
+ end
43
+
44
+ describe "contents" do
45
+ it "returns the serialized contents of the canvas operations" do
46
+ @canvas.save_graphics_state {}
47
+ assert_equal("q\nQ\n", @canvas.contents)
48
+ end
49
+ end
50
+
51
+ describe "stream_data" do
52
+ it "it closes an open path object" do
53
+ @canvas.move_to(5, 5)
54
+ assert_equal("5 5 m\nn\n", @canvas.stream_data.fiber.resume)
55
+ end
56
+
57
+ it "it closes an open text object" do
58
+ @canvas.begin_text
59
+ assert_equal("BT\nET\n", @canvas.stream_data.fiber.resume)
60
+ end
61
+
62
+ it "rewinds the graphics state stack" do
63
+ @canvas.save_graphics_state
64
+ @canvas.begin_text
65
+ assert_equal("q\nBT\nET\nQ\n", @canvas.stream_data.fiber.resume)
66
+ end
67
+ end
68
+
69
+ describe "resources" do
70
+ it "returns the resources of the context object" do
71
+ assert_equal(@page.resources, @canvas.resources)
72
+ end
73
+ end
74
+
75
+ describe "save_graphics_state" do
76
+ it "invokes the operator implementation" do
77
+ assert_operator_invoked(:q) { @canvas.save_graphics_state }
78
+ end
79
+
80
+ it "is serialized correctly when no block is used" do
81
+ @canvas.save_graphics_state
82
+ assert_operators(@canvas.contents, [[:save_graphics_state]])
83
+ end
84
+
85
+ it "is serialized correctly when a block is used" do
86
+ @canvas.save_graphics_state {}
87
+ assert_operators(@canvas.contents, [[:save_graphics_state], [:restore_graphics_state]])
88
+ end
89
+
90
+ it "saves needed font state information not stored in the graphics state" do
91
+ @canvas.save_graphics_state do
92
+ @canvas.font("Times", size: 12)
93
+ @canvas.save_graphics_state do
94
+ @canvas.font("Helvetica")
95
+ end
96
+ assert_equal("Times-Roman", @canvas.font.wrapped_font.font_name)
97
+ end
98
+ assert_nil(@canvas.font)
99
+ end
100
+
101
+ it "fails if invoked while in an unsupported graphics objects" do
102
+ assert_raises_in_graphics_object(:path, :clipping_path) { @canvas.save_graphics_state }
103
+ end
104
+ end
105
+
106
+ describe "restore_graphics_state" do
107
+ it "invokes the operator implementation" do
108
+ assert_operator_invoked(:Q) { @canvas.restore_graphics_state }
109
+ end
110
+
111
+ it "is serialized correctly" do
112
+ @canvas.graphics_state.save
113
+ @canvas.restore_graphics_state
114
+ assert_operators(@page.contents, [[:restore_graphics_state]])
115
+ end
116
+
117
+ it "fails if invoked while in an unsupported graphics objects" do
118
+ assert_raises_in_graphics_object(:path, :clipping_path) { @canvas.restore_graphics_state }
119
+ end
120
+ end
121
+
122
+ describe "transform" do
123
+ it "invokes the operator implementation" do
124
+ assert_operator_invoked(:cm, 1, 2, 3, 4, 5, 6) { @canvas.transform(1, 2, 3, 4, 5, 6) }
125
+ end
126
+
127
+ it "is serialized correctly when no block is used" do
128
+ @canvas.transform(1, 2, 3, 4, 5, 6)
129
+ assert_operators(@page.contents, [[:concatenate_matrix, [1, 2, 3, 4, 5, 6]]])
130
+ end
131
+
132
+ it "is serialized correctly when a block is used" do
133
+ @canvas.transform(1, 2, 3, 4, 5, 6) {}
134
+ assert_operators(@page.contents, [[:save_graphics_state],
135
+ [:concatenate_matrix, [1, 2, 3, 4, 5, 6]],
136
+ [:restore_graphics_state]])
137
+ end
138
+
139
+ it "fails if invoked while in an unsupported graphics objects" do
140
+ assert_raises_in_graphics_object(:path, :clipping_path) { @canvas.transform(1, 2, 3, 4, 5, 6) }
141
+ end
142
+ end
143
+
144
+ describe "rotate" do
145
+ it "can rotate around the origin" do
146
+ @canvas.rotate(90)
147
+ assert_operators(@page.contents, [[:concatenate_matrix, [0, 1, -1, 0, 0, 0]]])
148
+ end
149
+
150
+ it "can rotate about an arbitrary point" do
151
+ @canvas.rotate(90, origin: [100, 200])
152
+ assert_operators(@page.contents, [[:concatenate_matrix, [0.0, 1.0, -1.0, 0.0, 300.0, 100.0]]])
153
+ end
154
+ end
155
+
156
+ describe "scale" do
157
+ it "can scale from the origin" do
158
+ @canvas.scale(5, 10)
159
+ assert_operators(@page.contents, [[:concatenate_matrix, [5, 0, 0, 10, 0, 0]]])
160
+ end
161
+
162
+ it "can scale from an arbitrary point" do
163
+ @canvas.scale(5, 10, origin: [100, 200])
164
+ assert_operators(@page.contents, [[:concatenate_matrix, [5, 0, 0, 10, -400, -1800]]])
165
+ end
166
+
167
+ it "works with a single scale factor" do
168
+ @canvas.scale(5)
169
+ assert_operators(@page.contents, [[:concatenate_matrix, [5, 0, 0, 5, 0, 0]]])
170
+ end
171
+ end
172
+
173
+ describe "translate" do
174
+ it "translates the origin" do
175
+ @canvas.translate(100, 200)
176
+ assert_operators(@page.contents, [[:concatenate_matrix, [1, 0, 0, 1, 100, 200]]])
177
+ end
178
+ end
179
+
180
+ describe "skew" do
181
+ it "can skew from the origin" do
182
+ @canvas.skew(45, 0)
183
+ assert_operators(@page.contents, [[:concatenate_matrix, [1, 1, 0, 1, 0, 0]]])
184
+ end
185
+
186
+ it "can skew from an arbitrary point" do
187
+ @canvas.skew(45, 0, origin: [100, 200])
188
+ assert_operators(@page.contents, [[:concatenate_matrix, [1, 1, 0, 1, 0, -100]]])
189
+ end
190
+ end
191
+
192
+ describe "private gs_getter_setter" do
193
+ it "returns the current value when used with a nil argument" do
194
+ @canvas.graphics_state.line_width = 5
195
+ assert_equal(5, @canvas.send(:gs_getter_setter, :line_width, :w, nil))
196
+ end
197
+
198
+ it "returns the canvas object when used with a non-nil argument or a block" do
199
+ assert_equal(@canvas, @canvas.send(:gs_getter_setter, :line_width, :w, 15))
200
+ assert_equal(@canvas, @canvas.send(:gs_getter_setter, :line_width, :w, 15) {})
201
+ end
202
+
203
+ it "invokes the operator implementation when a non-nil argument is used" do
204
+ assert_operator_invoked(:w, 5) { @canvas.send(:gs_getter_setter, :line_width, :w, 5) }
205
+ assert_operator_invoked(:w, 15) { @canvas.send(:gs_getter_setter, :line_width, :w, 15) {} }
206
+ end
207
+
208
+ it "doesn't add an operator if the value is equal to the current one" do
209
+ @canvas.send(:gs_getter_setter, :line_width, :w,
210
+ @canvas.send(:gs_getter_setter, :line_width, :w, nil))
211
+ assert_operators(@page.contents, [])
212
+ end
213
+
214
+ it "always saves and restores the graphics state if a block is used" do
215
+ @canvas.send(:gs_getter_setter, :line_width, :w,
216
+ @canvas.send(:gs_getter_setter, :line_width, :w, nil)) {}
217
+ assert_operators(@page.contents, [[:save_graphics_state], [:restore_graphics_state]])
218
+ end
219
+
220
+ it "is serialized correctly when no block is used" do
221
+ @canvas.send(:gs_getter_setter, :line_width, :w, 5)
222
+ assert_operators(@page.contents, [[:set_line_width, [5]]])
223
+ end
224
+
225
+ it "is serialized correctly when a block is used" do
226
+ @canvas.send(:gs_getter_setter, :line_width, :w, 5) do
227
+ @canvas.send(:gs_getter_setter, :line_width, :w, 15)
228
+ end
229
+ assert_operators(@page.contents, [[:save_graphics_state],
230
+ [:set_line_width, [5]],
231
+ [:set_line_width, [15]],
232
+ [:restore_graphics_state]])
233
+ end
234
+
235
+ it "fails if a block is given without an argument" do
236
+ assert_raises(ArgumentError) { @canvas.send(:gs_getter_setter, :line_width, :w, nil) {} }
237
+ end
238
+
239
+ it "fails if invoked while in an unsupported graphics objects" do
240
+ assert_raises_in_graphics_object(:path, :clipping_path) do
241
+ @canvas.send(:gs_getter_setter, :line_width, :w, 5)
242
+ end
243
+ end
244
+ end
245
+
246
+ # Asserts that the method +name+ invoked with +values+ invokes the #gs_getter_setter helper method
247
+ # with the +name+, +operator+ and +expected_value+ as arguments.
248
+ def assert_gs_getter_setter(name, operator, expected_value, *values)
249
+ args = [name, operator, expected_value]
250
+ assert_method_invoked(@canvas, :gs_getter_setter, args, check_block: true) do
251
+ @canvas.send(name, *values) {}
252
+ end
253
+ unless values.compact.empty?
254
+ @canvas.send(name, *values)
255
+ assert_equal(expected_value, @canvas.graphics_state.send(name))
256
+ end
257
+ assert_respond_to(@canvas, name)
258
+ end
259
+
260
+ describe "line_width" do
261
+ it "uses the gs_getter_setter implementation" do
262
+ assert_gs_getter_setter(:line_width, :w, 5, 5)
263
+ assert_gs_getter_setter(:line_width, :w, nil, nil)
264
+ end
265
+ end
266
+
267
+ describe "line_cap_style" do
268
+ it "uses the gs_getter_setter implementation" do
269
+ assert_gs_getter_setter(:line_cap_style, :J, 1, :round)
270
+ assert_gs_getter_setter(:line_cap_style, :J, nil, nil)
271
+ end
272
+ end
273
+
274
+ describe "line_join_style" do
275
+ it "uses the gs_getter_setter implementation" do
276
+ assert_gs_getter_setter(:line_join_style, :j, 1, :round)
277
+ assert_gs_getter_setter(:line_join_style, :j, nil, nil)
278
+ end
279
+ end
280
+
281
+ describe "miter_limit" do
282
+ it "uses the gs_getter_setter implementation" do
283
+ assert_gs_getter_setter(:miter_limit, :M, 15, 15)
284
+ assert_gs_getter_setter(:miter_limit, :M, nil, nil)
285
+ end
286
+ end
287
+
288
+ describe "line_dash_pattern" do
289
+ it "uses the gs_getter_setter implementation" do
290
+ assert_gs_getter_setter(:line_dash_pattern, :d, nil, nil)
291
+ assert_gs_getter_setter(:line_dash_pattern, :d,
292
+ HexaPDF::Content::LineDashPattern.new, 0)
293
+ assert_gs_getter_setter(:line_dash_pattern, :d,
294
+ HexaPDF::Content::LineDashPattern.new([5]), 5)
295
+ assert_gs_getter_setter(:line_dash_pattern, :d,
296
+ HexaPDF::Content::LineDashPattern.new([5], 2), 5, 2)
297
+ assert_gs_getter_setter(:line_dash_pattern, :d,
298
+ HexaPDF::Content::LineDashPattern.new([5, 3], 2), [5, 3], 2)
299
+ assert_gs_getter_setter(:line_dash_pattern, :d,
300
+ HexaPDF::Content::LineDashPattern.new([5, 3], 2),
301
+ HexaPDF::Content::LineDashPattern.new([5, 3], 2))
302
+ end
303
+ end
304
+
305
+ describe "rendering_intent" do
306
+ it "uses the gs_getter_setter implementation" do
307
+ assert_gs_getter_setter(:rendering_intent, :ri, :Perceptual, :Perceptual)
308
+ assert_gs_getter_setter(:rendering_intent, :ri, nil, nil)
309
+ end
310
+ end
311
+
312
+ describe "opacity" do
313
+ it "returns the current values when no argument/nil arguments are provided" do
314
+ assert_equal({fill_alpha: 1.0, stroke_alpha: 1.0}, @canvas.opacity)
315
+ end
316
+
317
+ it "returns the canvas object when at least one non-nil argument is provided" do
318
+ assert_equal(@canvas, @canvas.opacity(fill_alpha: 0.5))
319
+ end
320
+
321
+ it "invokes the operator implementation when at least one non-nil argument is used" do
322
+ assert_operator_invoked(:gs, :GS1) do
323
+ @canvas.opacity(fill_alpha: 1.0, stroke_alpha: 0.5)
324
+ end
325
+ end
326
+
327
+ it "doesn't add an operator if the values are not really changed" do
328
+ @canvas.opacity(fill_alpha: 1.0, stroke_alpha: 1.0)
329
+ assert_operators(@page.contents, [])
330
+ end
331
+
332
+ it "always saves and restores the graphics state if a block is used" do
333
+ @canvas.opacity(fill_alpha: 1.0, stroke_alpha: 1.0) {}
334
+ assert_operators(@page.contents, [[:save_graphics_state], [:restore_graphics_state]])
335
+ end
336
+
337
+ it "adds the needed entry to the /ExtGState resources dictionary" do
338
+ @canvas.graphics_state.alpha_source = true
339
+ @canvas.opacity(fill_alpha: 0.5, stroke_alpha: 0.7)
340
+ assert_equal({Type: :ExtGState, CA: 0.7, ca: 0.5, AIS: false},
341
+ @canvas.resources.ext_gstate(:GS1))
342
+ end
343
+
344
+ it "is serialized correctly when no block is used" do
345
+ @canvas.opacity(fill_alpha: 0.5, stroke_alpha: 0.7)
346
+ assert_operators(@page.contents, [[:set_graphics_state_parameters, [:GS1]]])
347
+ end
348
+
349
+ it "is serialized correctly when a block is used" do
350
+ @canvas.opacity(fill_alpha: 0.5) do
351
+ @canvas.opacity(stroke_alpha: 0.7)
352
+ end
353
+ assert_operators(@page.contents, [[:save_graphics_state],
354
+ [:set_graphics_state_parameters, [:GS1]],
355
+ [:set_graphics_state_parameters, [:GS2]],
356
+ [:restore_graphics_state]])
357
+ end
358
+
359
+ it "fails if a block is given without an argument" do
360
+ assert_raises(ArgumentError) { @canvas.opacity {} }
361
+ end
362
+
363
+ it "fails if invoked while in an unsupported graphics objects" do
364
+ assert_raises_in_graphics_object(:path, :clipping_path) { @canvas.opacity(fill_alpha: 1.0) }
365
+ end
366
+ end
367
+
368
+ describe "private color_getter_setter" do
369
+ def invoke(*params, &block)
370
+ @canvas.send(:color_getter_setter, :stroke_color, params, :RG, :G, :K, :CS, :SCN, &block)
371
+ end
372
+
373
+ it "returns the current value when used with no argument" do
374
+ color = @canvas.graphics_state.stroke_color
375
+ assert_equal(color, invoke)
376
+ end
377
+
378
+ it "returns the canvas when used with a non-nil argument and no block" do
379
+ assert_equal(@canvas, invoke(255))
380
+ assert_equal(@canvas, invoke(255) {})
381
+ end
382
+
383
+ it "doesn't add an operator if the value is equal to the current one" do
384
+ invoke(0.0)
385
+ assert_operators(@page.contents, [])
386
+ end
387
+
388
+ it "always saves and restores the graphics state if a block is used" do
389
+ invoke(0.0) {}
390
+ assert_operators(@page.contents, [[:save_graphics_state], [:restore_graphics_state]])
391
+ end
392
+
393
+ it "adds an unknown color space to the resource dictionary" do
394
+ invoke(HexaPDF::Content::ColorSpace::Universal.new([:Pattern, :DeviceRGB]).color(:Name))
395
+ assert_equal([:Pattern, :DeviceRGB], @page.resources.color_space(:CS1).definition)
396
+ end
397
+
398
+ it "is serialized correctly when no block is used" do
399
+ invoke(102)
400
+ invoke([102])
401
+ invoke("6600FF")
402
+ invoke(102, 0, 255)
403
+ invoke(0, 20, 40, 80)
404
+ invoke(HexaPDF::Content::ColorSpace::Universal.new([:Pattern]).color(:Name))
405
+ assert_operators(@page.contents, [[:set_device_gray_stroking_color, [0.4]],
406
+ [:set_device_rgb_stroking_color, [0.4, 0, 1]],
407
+ [:set_device_cmyk_stroking_color, [0, 0.2, 0.4, 0.8]],
408
+ [:set_stroking_color_space, [:CS1]],
409
+ [:set_stroking_color, [:Name]]])
410
+ end
411
+
412
+ it "is serialized correctly when a block is used" do
413
+ invoke(102) { invoke(255) }
414
+ assert_operators(@page.contents, [[:save_graphics_state],
415
+ [:set_device_gray_stroking_color, [0.4]],
416
+ [:set_device_gray_stroking_color, [1.0]],
417
+ [:restore_graphics_state]])
418
+ end
419
+
420
+ it "fails if a block is given without an argument" do
421
+ assert_raises(ArgumentError) { invoke {} }
422
+ end
423
+
424
+ it "fails if an unsupported number of component values is provided" do
425
+ assert_raises(ArgumentError) { invoke(5, 5) }
426
+ end
427
+
428
+ it "fails if invoked while in an unsupported graphics objects" do
429
+ assert_raises_in_graphics_object(:path, :clipping_path) { invoke(0.5) }
430
+ end
431
+ end
432
+
433
+ # Asserts that the method +name+ invoked with +values+ invokes the #color_getter_setter helper
434
+ # method with the +expected_values+ as arguments.
435
+ def assert_color_getter_setter(name, expected_values, *values)
436
+ assert_method_invoked(@canvas, :color_getter_setter, expected_values, check_block: true) do
437
+ @canvas.send(name, *values) {}
438
+ end
439
+ end
440
+
441
+ describe "stroke_color" do
442
+ it "uses the color_getter_setter implementation" do
443
+ assert_color_getter_setter(:stroke_color, [:stroke_color, [255], :RG, :G, :K, :CS, :SCN], 255)
444
+ assert_color_getter_setter(:stroke_color, [:stroke_color, [], :RG, :G, :K, :CS, :SCN])
445
+ end
446
+ end
447
+
448
+ describe "fill_color" do
449
+ it "uses the color_getter_setter implementation" do
450
+ assert_color_getter_setter(:fill_color, [:fill_color, [255], :rg, :g, :k, :cs, :scn], 255)
451
+ assert_color_getter_setter(:fill_color, [:fill_color, [], :rg, :g, :k, :cs, :scn])
452
+ end
453
+ end
454
+
455
+ describe "move_to" do
456
+ it "invokes the operator implementation" do
457
+ assert_operator_invoked(:m, 5, 6) { @canvas.move_to(5, 6) }
458
+ end
459
+
460
+ it "returns the canvas object" do
461
+ assert_equal(@canvas, @canvas.move_to(5, 6))
462
+ end
463
+
464
+ it "sets the current point correctly" do
465
+ @canvas.move_to(5, 6)
466
+ assert_equal([5, 6], @canvas.current_point)
467
+ end
468
+
469
+ it "fails if invoked while in an unsupported graphics objects" do
470
+ assert_raises_in_graphics_object(:clipping_path) { @canvas.move_to(5, 6) }
471
+ end
472
+ end
473
+
474
+ describe "line_to" do
475
+ before do
476
+ @canvas.graphics_object = :path
477
+ end
478
+
479
+ it "invokes the operator implementation" do
480
+ assert_operator_invoked(:l, 5, 6) { @canvas.line_to(5, 6) }
481
+ end
482
+
483
+ it "returns the canvas object" do
484
+ assert_equal(@canvas, @canvas.line_to(5, 6))
485
+ end
486
+
487
+ it "sets the current point correctly" do
488
+ @canvas.line_to(5, 6)
489
+ assert_equal([5, 6], @canvas.current_point)
490
+ end
491
+
492
+ it "fails if invoked while in an unsupported graphics objects" do
493
+ assert_raises_in_graphics_object(:none, :text, :clipping_path) { @canvas.line_to(5, 6) }
494
+ end
495
+ end
496
+
497
+ describe "curve_to" do
498
+ before do
499
+ @canvas.graphics_object = :path
500
+ end
501
+
502
+ it "invokes the operator implementation" do
503
+ assert_operator_invoked(:c, 5, 6, 7, 8, 9, 10) { @canvas.curve_to(9, 10, p1: [5, 6], p2: [7, 8]) }
504
+ assert_operator_invoked(:v, 7, 8, 9, 10) { @canvas.curve_to(9, 10, p2: [7, 8]) }
505
+ assert_operator_invoked(:y, 5, 6, 9, 10) { @canvas.curve_to(9, 10, p1: [5, 6]) }
506
+ end
507
+
508
+ it "returns the canvas object" do
509
+ assert_equal(@canvas, @canvas.curve_to(5, 6, p1: [7, 8]))
510
+ end
511
+
512
+ it "sets the current point correctly" do
513
+ @canvas.curve_to(5, 6, p1: [9, 10])
514
+ assert_equal([5, 6], @canvas.current_point)
515
+ end
516
+
517
+ it "raises an error if both control points are omitted" do
518
+ assert_raises(ArgumentError) { @canvas.curve_to(9, 10) }
519
+ end
520
+
521
+ it "fails if invoked while in an unsupported graphics objects" do
522
+ assert_raises_in_graphics_object(:none, :text, :clipping_path) do
523
+ @canvas.curve_to(5, 6, p1: [7, 8])
524
+ end
525
+ end
526
+ end
527
+
528
+ describe "rectangle" do
529
+ it "invokes the operator implementation when radius == 0" do
530
+ assert_operator_invoked(:re, 5, 10, 15, 20) { @canvas.rectangle(5, 10, 15, 20) }
531
+ end
532
+
533
+ it "invokes the polygon method when radius != 0" do
534
+ args = [0, 0, 10, 0, 10, 10, 0, 10, {radius: 5}]
535
+ assert_method_invoked(@canvas, :polygon, args) do
536
+ @canvas.rectangle(0, 0, 10, 10, radius: 5)
537
+ end
538
+ end
539
+
540
+ it "returns the canvas object" do
541
+ assert_equal(@canvas, @canvas.rectangle(5, 6, 7, 8))
542
+ end
543
+
544
+ it "sets the current point correctly" do
545
+ @canvas.rectangle(5, 6, 7, 8)
546
+ assert_equal([5, 6], @canvas.current_point)
547
+ end
548
+
549
+ it "fails if invoked while in an unsupported graphics objects" do
550
+ assert_raises_in_graphics_object(:clipping_path) { @canvas.rectangle(5, 6, 7, 8) }
551
+ end
552
+ end
553
+
554
+ describe "close_subpath" do
555
+ before do
556
+ @canvas.graphics_object = :path
557
+ end
558
+
559
+ it "invokes the operator implementation" do
560
+ assert_operator_invoked(:h) { @canvas.close_subpath }
561
+ end
562
+
563
+ it "returns the canvas object" do
564
+ assert_equal(@canvas, @canvas.close_subpath)
565
+ end
566
+
567
+ it "sets the current point correctly" do
568
+ @canvas.move_to(1, 1)
569
+ @canvas.line_to(10, 10)
570
+ @canvas.close_subpath
571
+ assert_equal([1, 1], @canvas.current_point)
572
+ end
573
+
574
+ it "fails if invoked while in an unsupported graphics objects" do
575
+ assert_raises_in_graphics_object(:none, :text) { @canvas.close_subpath }
576
+ end
577
+ end
578
+
579
+ describe "line" do
580
+ it "serializes correctly" do
581
+ @canvas.line(1, 2, 3, 4)
582
+ assert_operators(@canvas.contents, [[:move_to, [1, 2]], [:line_to, [3, 4]]])
583
+ end
584
+
585
+ it "returns the canvas object" do
586
+ assert_equal(@canvas, @canvas.line(1, 2, 3, 4))
587
+ end
588
+ end
589
+
590
+ describe "polyline" do
591
+ it "serializes correctly" do
592
+ @canvas.polyline(1, 2, 3, 4, 5, 6)
593
+ assert_operators(@canvas.contents, [[:move_to, [1, 2]], [:line_to, [3, 4]], [:line_to, [5, 6]]])
594
+ end
595
+
596
+ it "returns the canvas object" do
597
+ assert_equal(@canvas, @canvas.polyline(1, 2, 3, 4))
598
+ end
599
+
600
+ it "fails if not enought points are supplied" do
601
+ assert_raises(ArgumentError) { @canvas.polyline(5, 6) }
602
+ end
603
+
604
+ it "fails if a y-coordinate is missing" do
605
+ assert_raises(ArgumentError) { @canvas.polyline(5, 6, 7, 8, 9) }
606
+ end
607
+ end
608
+
609
+ describe "polygon" do
610
+ it "serializes correctly with no radius" do
611
+ @canvas.polygon(1, 2, 3, 4, 5, 6)
612
+ assert_operators(@canvas.contents, [[:move_to, [1, 2]], [:line_to, [3, 4]],
613
+ [:line_to, [5, 6]], [:close_subpath]])
614
+ end
615
+
616
+ it "serializes correctly with a radius" do
617
+ @canvas.polygon(-1, -1, -1, 1, 1, 1, 1, -1, radius: 1)
618
+ k = @canvas.class::KAPPA.round(6)
619
+ assert_operators(@canvas.contents, [[:move_to, [-1, 0]],
620
+ [:line_to, [-1, 0]], [:curve_to, [-1, k, -k, 1, 0, 1]],
621
+ [:line_to, [0, 1]], [:curve_to, [k, 1, 1, k, 1, 0]],
622
+ [:line_to, [1, 0]], [:curve_to, [1, -k, k, -1, 0, -1]],
623
+ [:line_to, [0, -1]], [:curve_to, [-k, -1, -1, -k, -1, 0]],
624
+ [:close_subpath]])
625
+ end
626
+
627
+ it "returns the canvas object" do
628
+ assert_equal(@canvas, @canvas.polyline(1, 2, 3, 4, 5, 6))
629
+ end
630
+ end
631
+
632
+ describe "circle" do
633
+ it "uses arc for the hard work" do
634
+ assert_method_invoked(@canvas, :arc, [5, 6, {a: 7}]) do
635
+ @canvas.graphics_object = :path
636
+ @canvas.circle(5, 6, 7)
637
+ end
638
+ end
639
+
640
+ it "serializes correctly" do
641
+ @canvas.circle(0, 0, 1)
642
+ assert_operators(@canvas.contents,
643
+ [:move_to, :curve_to, :curve_to, :curve_to, :curve_to, :close_subpath],
644
+ only_names: true)
645
+ end
646
+
647
+ it "returns the canvas object" do
648
+ assert_equal(@canvas, @canvas.circle(1, 2, 3))
649
+ end
650
+ end
651
+
652
+ describe "ellipse" do
653
+ it "uses arc for the hard work" do
654
+ assert_method_invoked(@canvas, :ellipse, [5, 6, {a: 7, b: 5, inclination: 10}]) do
655
+ @canvas.ellipse(5, 6, a: 7, b: 5, inclination: 10)
656
+ end
657
+ end
658
+
659
+ it "serializes correctly" do
660
+ @canvas.ellipse(0, 0, a: 10, b: 5, inclination: 10)
661
+ assert_operators(@canvas.contents,
662
+ [:move_to, :curve_to, :curve_to, :curve_to, :curve_to, :close_subpath],
663
+ only_names: true)
664
+ end
665
+
666
+ it "returns the canvas object" do
667
+ assert_equal(@canvas, @canvas.circle(1, 2, 3))
668
+ end
669
+ end
670
+
671
+ describe "arc" do
672
+ it "serializes correctly" do
673
+ @canvas.arc(0, 0, a: 1, b: 1, start_angle: 0, end_angle: 360, inclination: 0)
674
+ @canvas.arc(0, 0, a: 1, b: 1, start_angle: 0, end_angle: 360, clockwise: true, inclination: 0)
675
+ assert_operators(@canvas.contents, [[:move_to, [1, 0]],
676
+ [:curve_to, [1, 0.548584, 0.548584, 1, 0, 1]],
677
+ [:curve_to, [-0.548584, 1, -1, 0.548584, -1, 0]],
678
+ [:curve_to, [-1, -0.548584, -0.548584, -1, 0, -1]],
679
+ [:curve_to, [0.548584, -1, 1, -0.548584, 1, 0]],
680
+ [:move_to, [1, 0]],
681
+ [:curve_to, [1, -0.548584, 0.548584, -1, 0, -1]],
682
+ [:curve_to, [-0.548584, -1, -1, -0.548584, -1, 0]],
683
+ [:curve_to, [-1, 0.548584, -0.548584, 1, 0, 1]],
684
+ [:curve_to, [0.548584, 1, 1, 0.548584, 1, 0]]])
685
+ end
686
+
687
+ it "returns the canvas object" do
688
+ assert_equal(@canvas, @canvas.arc(1, 2, a: 3))
689
+ end
690
+ end
691
+
692
+ describe "line_with_rounded_corner" do
693
+ it "serializes correctly" do
694
+ @canvas.move_to(10, 10)
695
+ @canvas.line_with_rounded_corner(30, 10, 30, 50, in_radius: 10, out_radius: 5)
696
+ @canvas.line_with_rounded_corner(30, 50, 30, 10, 30, 50, in_radius: 10, out_radius: 5)
697
+ assert_operators(@canvas.contents,
698
+ [[:move_to, [10, 10]],
699
+ [:line_to, [20, 10]],
700
+ [:curve_to, [25.51915, 10.0, 30.0, 12.240425, 30.0, 15.0]],
701
+ [:line_to, [30, 20]],
702
+ [:curve_to, [30.0, 14.48085, 30.0, 12.240425, 30.0, 15.0]]])
703
+ end
704
+
705
+ describe "degraded cases" do
706
+ it "p0 equal p1" do
707
+ @canvas.move_to(10, 10)
708
+ @canvas.line_with_rounded_corner(10, 10, 10, 20, in_radius: 5)
709
+ assert_operators(@canvas.contents,
710
+ [[:move_to, [10, 10]],
711
+ [:line_to, [10, 10]]])
712
+ end
713
+
714
+ it "p1 equal p2" do
715
+ @canvas.move_to(10, 10)
716
+ @canvas.line_with_rounded_corner(20, 10, 20, 10, in_radius: 5)
717
+ assert_operators(@canvas.contents,
718
+ [[:move_to, [10, 10]],
719
+ [:line_to, [20, 10]]])
720
+ end
721
+
722
+ it "p0 equal p1 equal p2" do
723
+ @canvas.move_to(10, 10)
724
+ @canvas.line_with_rounded_corner(10, 10, 10, 10, in_radius: 5)
725
+ assert_operators(@canvas.contents,
726
+ [[:move_to, [10, 10]],
727
+ [:line_to, [10, 10]]])
728
+ end
729
+
730
+ it "in_radius = 0" do
731
+ @canvas.move_to(10, 10)
732
+ @canvas.line_with_rounded_corner(20, 10, 20, 20, in_radius: 0, out_radius: 5)
733
+ assert_operators(@canvas.contents,
734
+ [[:move_to, [10, 10]],
735
+ [:line_to, [20, 10]]])
736
+ end
737
+
738
+ it "out_radius = 0" do
739
+ @canvas.move_to(10, 10)
740
+ @canvas.line_with_rounded_corner(20, 10, 20, 20, in_radius: 5, out_radius: 0)
741
+ assert_operators(@canvas.contents,
742
+ [[:move_to, [10, 10]],
743
+ [:line_to, [20, 10]]])
744
+ end
745
+ end
746
+
747
+ it "returns the canvas object" do
748
+ @canvas.move_to(10, 10)
749
+ assert_equal(@canvas, @canvas.line_with_rounded_corner(30, 30, 30, 50, in_radius: 10))
750
+ end
751
+ end
752
+
753
+ describe "graphic_object" do
754
+ it "returns a new graphic object given a name" do
755
+ arc = @canvas.graphic_object(:arc)
756
+ assert_respond_to(arc, :draw)
757
+ arc1 = @canvas.graphic_object(:arc)
758
+ refute_same(arc, arc1)
759
+ end
760
+
761
+ it "returns a configured graphic object given a name" do
762
+ arc = @canvas.graphic_object(:arc, cx: 10)
763
+ assert_equal(10, arc.cx)
764
+ end
765
+
766
+ it "reconfigures the given graphic object" do
767
+ arc = @canvas.graphic_object(:arc)
768
+ arc1 = @canvas.graphic_object(arc, cx: 10)
769
+ assert_same(arc, arc1)
770
+ assert_equal(10, arc.cx)
771
+ end
772
+ end
773
+
774
+ describe "draw" do
775
+ it "draws the, optionally configured, graphic object onto the canvas" do
776
+ obj = Object.new
777
+ obj.define_singleton_method(:options) { @options }
778
+ obj.define_singleton_method(:configure) {|**kwargs| @options = kwargs; self }
779
+ obj.define_singleton_method(:draw) {|canvas| canvas.move_to(@options[:x], @options[:y]) }
780
+ @canvas.draw(obj, x: 5, y: 6)
781
+ assert_operators(@canvas.contents, [[:move_to, [5, 6]]])
782
+ end
783
+
784
+ it "returns the canvas object" do
785
+ assert_equal(@canvas, @canvas.draw(:arc))
786
+ end
787
+ end
788
+
789
+ describe "path painting methods" do
790
+ before do
791
+ @canvas.graphics_object = :path
792
+ end
793
+
794
+ it "invokes the respective operator implementation" do
795
+ assert_operator_invoked(:S) { @canvas.stroke }
796
+ assert_operator_invoked(:s) { @canvas.close_stroke }
797
+ assert_operator_invoked(:f) { @canvas.fill(:nonzero) }
798
+ assert_operator_invoked(:'f*') { @canvas.fill(:even_odd) }
799
+ assert_operator_invoked(:B) { @canvas.fill_stroke(:nonzero) }
800
+ assert_operator_invoked(:'B*') { @canvas.fill_stroke(:even_odd) }
801
+ assert_operator_invoked(:b) { @canvas.close_fill_stroke(:nonzero) }
802
+ assert_operator_invoked(:'b*') { @canvas.close_fill_stroke(:even_odd) }
803
+ assert_operator_invoked(:n) { @canvas.end_path }
804
+ end
805
+
806
+ it "returns the canvas object" do
807
+ [:stroke, :close_stroke, :fill, :fill_stroke, :close_fill_stroke, :end_path].each do |m|
808
+ @canvas.graphics_object = :path
809
+ assert_equal(@canvas, @canvas.send(m))
810
+ end
811
+ end
812
+
813
+ it "fails if invoked while in an unsupported graphics objects" do
814
+ [:stroke, :close_stroke, :fill, :fill_stroke, :close_fill_stroke, :end_path].each do |m|
815
+ assert_raises_in_graphics_object(:none, :text) { @canvas.send(m) }
816
+ end
817
+ end
818
+ end
819
+
820
+ describe "clip_path" do
821
+ before do
822
+ @canvas.graphics_object = :path
823
+ end
824
+
825
+ it "invokes the respective operator implementation" do
826
+ assert_operator_invoked(:W) { @canvas.clip_path(:nonzero) }
827
+ assert_operator_invoked(:'W*') { @canvas.clip_path(:even_odd) }
828
+ end
829
+
830
+ it "returns the canvas object" do
831
+ assert_equal(@canvas, @canvas.clip_path)
832
+ end
833
+
834
+ it "fails if invoked while in an unsupported graphics objects" do
835
+ assert_raises_in_graphics_object(:none, :text, :clipping_path) { @canvas.clip_path }
836
+ end
837
+ end
838
+
839
+ describe "xobject" do
840
+ before do
841
+ @image = @doc.add({Type: :XObject, Subtype: :Image, Width: 10, Height: 5})
842
+ @image.source_path = File.join(TEST_DATA_DIR, 'images', 'gray.jpg')
843
+ @form = @doc.add({Type: :XObject, Subtype: :Form, BBox: [100, 50, 200, 100]})
844
+ end
845
+
846
+ it "can use any xobject specified via a filename" do
847
+ xobject = @canvas.xobject(@image.source_path, at: [0, 0])
848
+ assert_equal(xobject, @page.resources.xobject(:XO1))
849
+ end
850
+
851
+ it "can use any xobject specified via an IO object" do
852
+ File.open(@image.source_path, 'rb') do |file|
853
+ xobject = @canvas.xobject(file, at: [0, 0])
854
+ assert_equal(xobject, @page.resources.xobject(:XO1))
855
+ end
856
+ end
857
+
858
+ it "can use an already existing xobject" do
859
+ xobject = @canvas.xobject(@image, at: [0, 0])
860
+ assert_equal(xobject, @page.resources.xobject(:XO1))
861
+ end
862
+
863
+ it "correctly serializes the image with no options" do
864
+ @canvas.xobject(@image, at: [1, 2])
865
+ assert_operators(@page.contents, [[:save_graphics_state],
866
+ [:concatenate_matrix, [10, 0, 0, 5, 1, 2]],
867
+ [:paint_xobject, [:XO1]],
868
+ [:restore_graphics_state]])
869
+ end
870
+
871
+ it "correctly serializes the image with just the width given" do
872
+ @canvas.image(@image, at: [1, 2], width: 20)
873
+ assert_operators(@page.contents, [[:save_graphics_state],
874
+ [:concatenate_matrix, [20, 0, 0, 10, 1, 2]],
875
+ [:paint_xobject, [:XO1]],
876
+ [:restore_graphics_state]])
877
+ end
878
+
879
+ it "correctly serializes the image with just the height given" do
880
+ @canvas.image(@image, at: [1, 2], height: 10)
881
+ assert_operators(@page.contents, [[:save_graphics_state],
882
+ [:concatenate_matrix, [20, 0, 0, 10, 1, 2]],
883
+ [:paint_xobject, [:XO1]],
884
+ [:restore_graphics_state]])
885
+ end
886
+
887
+ it "correctly serializes the image with both width and height given" do
888
+ @canvas.image(@image, at: [1, 2], width: 10, height: 20)
889
+ assert_operators(@page.contents, [[:save_graphics_state],
890
+ [:concatenate_matrix, [10, 0, 0, 20, 1, 2]],
891
+ [:paint_xobject, [:XO1]],
892
+ [:restore_graphics_state]])
893
+ end
894
+
895
+ it "doesn't do anything if the image's width or height is zero" do
896
+ @image[:Width] = 0
897
+ @canvas.xobject(@image, at: [0, 0])
898
+ assert_operators(@page.contents, [])
899
+
900
+ @image[:Width] = 10
901
+ @image[:Height] = 0
902
+ @canvas.xobject(@image, at: [0, 0])
903
+ assert_operators(@page.contents, [])
904
+ end
905
+
906
+ it "correctly serializes the form with no options" do
907
+ @canvas.xobject(@form, at: [1, 2])
908
+ assert_operators(@page.contents, [[:save_graphics_state],
909
+ [:concatenate_matrix, [1, 0, 0, 1, -99, -48]],
910
+ [:paint_xobject, [:XO1]],
911
+ [:restore_graphics_state]])
912
+ end
913
+
914
+ it "correctly serializes the form with just the width given" do
915
+ @canvas.image(@form, at: [1, 2], width: 50)
916
+ assert_operators(@page.contents, [[:save_graphics_state],
917
+ [:concatenate_matrix, [0.5, 0, 0, 0.5, -99, -48]],
918
+ [:paint_xobject, [:XO1]],
919
+ [:restore_graphics_state]])
920
+ end
921
+
922
+ it "correctly serializes the form with just the height given" do
923
+ @canvas.image(@form, at: [1, 2], height: 10)
924
+ assert_operators(@page.contents, [[:save_graphics_state],
925
+ [:concatenate_matrix, [0.2, 0, 0, 0.2, -99, -48]],
926
+ [:paint_xobject, [:XO1]],
927
+ [:restore_graphics_state]])
928
+ end
929
+
930
+ it "correctly serializes the form with both width and height given" do
931
+ @canvas.image(@form, at: [1, 2], width: 50, height: 10)
932
+ assert_operators(@page.contents, [[:save_graphics_state],
933
+ [:concatenate_matrix, [0.5, 0, 0, 0.2, -99, -48]],
934
+ [:paint_xobject, [:XO1]],
935
+ [:restore_graphics_state]])
936
+ end
937
+
938
+ it "doesn't do anything if the form's width or height is zero" do
939
+ @form[:BBox] = [100, 50, 100, 200]
940
+ @canvas.xobject(@form, at: [0, 0])
941
+ assert_operators(@page.contents, [])
942
+
943
+ @form[:BBox] = [100, 50, 150, 50]
944
+ @canvas.xobject(@form, at: [0, 0])
945
+ assert_operators(@page.contents, [])
946
+ end
947
+ end
948
+
949
+ describe "character_spacing" do
950
+ it "uses the gs_getter_setter implementation" do
951
+ assert_gs_getter_setter(:character_spacing, :Tc, 0.25, 0.25)
952
+ assert_gs_getter_setter(:character_spacing, :Tc, nil, nil)
953
+ end
954
+ end
955
+
956
+ describe "word_spacing" do
957
+ it "uses the gs_getter_setter implementation" do
958
+ assert_gs_getter_setter(:word_spacing, :Tw, 0.25, 0.25)
959
+ assert_gs_getter_setter(:word_spacing, :Tw, nil, nil)
960
+ end
961
+ end
962
+
963
+ describe "horizontal_scaling" do
964
+ it "uses the gs_getter_setter implementation" do
965
+ assert_gs_getter_setter(:horizontal_scaling, :Tz, 50, 50)
966
+ assert_gs_getter_setter(:horizontal_scaling, :Tz, nil, nil)
967
+ end
968
+ end
969
+
970
+ describe "leading" do
971
+ it "uses the gs_getter_setter implementation" do
972
+ assert_gs_getter_setter(:leading, :TL, 15, 15)
973
+ assert_gs_getter_setter(:leading, :TL, nil, nil)
974
+ end
975
+ end
976
+
977
+ describe "text_rendering_mode" do
978
+ it "uses the gs_getter_setter implementation" do
979
+ assert_gs_getter_setter(:text_rendering_mode, :Tr, 0, :fill)
980
+ assert_gs_getter_setter(:text_rendering_mode, :Tr, nil, nil)
981
+ end
982
+ end
983
+
984
+ describe "text_rise" do
985
+ it "uses the gs_getter_setter implementation" do
986
+ assert_gs_getter_setter(:text_rise, :Ts, 15, 15)
987
+ assert_gs_getter_setter(:text_rise, :Ts, nil, nil)
988
+ end
989
+ end
990
+
991
+ describe "begin_text" do
992
+ it "invokes the operator implementation" do
993
+ assert_operator_invoked(:BT) { @canvas.begin_text }
994
+ end
995
+
996
+ it "serializes correctly" do
997
+ @canvas.begin_text
998
+ @canvas.begin_text
999
+ @canvas.begin_text(force_new: true)
1000
+ assert_operators(@canvas.contents, [:begin_text, :end_text, :begin_text], only_names: true)
1001
+ end
1002
+
1003
+ it "returns the canvas object" do
1004
+ assert_equal(@canvas, @canvas.begin_text)
1005
+ end
1006
+
1007
+ it "fails if the current graphics object doesn't allow a new text object" do
1008
+ assert_raises(HexaPDF::Error) do
1009
+ @canvas.graphics_object = :path
1010
+ @canvas.begin_text
1011
+ end
1012
+ end
1013
+
1014
+ it "fails if invoked while in an unsupported graphics objects" do
1015
+ assert_raises_in_graphics_object(:path, :clipping_path) { @canvas.begin_text }
1016
+ end
1017
+ end
1018
+
1019
+ describe "end_text" do
1020
+ it "invokes the operator implementation" do
1021
+ @canvas.graphics_object = :text
1022
+ assert_operator_invoked(:ET) { @canvas.end_text }
1023
+ end
1024
+
1025
+ it "serializes correctly" do
1026
+ @canvas.end_text
1027
+ @canvas.begin_text
1028
+ @canvas.end_text
1029
+ @canvas.end_text
1030
+ assert_operators(@page.contents, [:begin_text, :end_text], only_names: true)
1031
+ end
1032
+
1033
+ it "returns the canvas object" do
1034
+ assert_equal(@canvas, @canvas.begin_text)
1035
+ end
1036
+
1037
+ it "fails if invoked while in an unsupported graphics objects" do
1038
+ assert_raises_in_graphics_object(:path, :clipping_path) { @canvas.end_text }
1039
+ end
1040
+ end
1041
+
1042
+ describe "text_matrix" do
1043
+ it "invokes the operator implementation" do
1044
+ @canvas.text_matrix(1, 2, 3, 4, 5, 6)
1045
+ assert_operators(@canvas.contents, [[:begin_text],
1046
+ [:set_text_matrix, [1, 2, 3, 4, 5, 6]]])
1047
+ end
1048
+
1049
+ it "returns the canvas object" do
1050
+ assert_equal(@canvas, @canvas.text_matrix(1, 1, 1, 1, 1, 1))
1051
+ end
1052
+ end
1053
+
1054
+ describe "move_text_cursor" do
1055
+ describe "invokes the operator implementation" do
1056
+ it "moves to the next line" do
1057
+ @canvas.move_text_cursor
1058
+ assert_operators(@canvas.contents, [[:begin_text],
1059
+ [:move_text_next_line]])
1060
+ end
1061
+
1062
+ it "moves to the next line with an offset" do
1063
+ @canvas.move_text_cursor(offset: [5, 10], absolute: false)
1064
+ assert_operators(@canvas.contents, [[:begin_text],
1065
+ [:move_text, [5, 10]]])
1066
+ end
1067
+
1068
+ it "moves to an absolute position" do
1069
+ @canvas.move_text_cursor(offset: [5, 10], absolute: true)
1070
+ assert_operators(@canvas.contents, [[:begin_text],
1071
+ [:set_text_matrix, [1, 0, 0, 1, 5, 10]]])
1072
+ end
1073
+ end
1074
+
1075
+ it "returns the canvas object" do
1076
+ assert_equal(@canvas, @canvas.move_text_cursor)
1077
+ end
1078
+ end
1079
+
1080
+ describe "text_cursor" do
1081
+ it "returns the text cursor position" do
1082
+ @canvas.move_text_cursor(offset: [5, 10])
1083
+ assert_equal([5, 10], @canvas.text_cursor)
1084
+ end
1085
+
1086
+ it "fails if invoked outside a text object" do
1087
+ assert_raises_in_graphics_object(:none, :path, :clipping_path) { @canvas.text_cursor }
1088
+ end
1089
+ end
1090
+
1091
+ describe "font" do
1092
+ it "returns the set font" do
1093
+ assert_nil(@canvas.font)
1094
+ @canvas.font("Times", size: 10)
1095
+ assert_same(@doc.fonts.add("Times"), @canvas.font)
1096
+ @canvas.font(@canvas.font)
1097
+ assert_same(@doc.fonts.add("Times"), @canvas.font)
1098
+ @canvas.font("Helvetica", size: 10)
1099
+ assert_operators(@canvas.contents, [[:set_font_and_size, [:F1, 10]],
1100
+ [:set_font_and_size, [:F2, 10]]])
1101
+ end
1102
+
1103
+ it "sets the font and optionally the font size" do
1104
+ @canvas.font("Times", size: 12, variant: :italic)
1105
+ assert_same(@doc.fonts.add("Times", variant: :italic), @canvas.font)
1106
+ assert_equal(12, @canvas.font_size)
1107
+ @canvas.font("Helvetica")
1108
+ assert_equal(12, @canvas.font_size)
1109
+ end
1110
+ end
1111
+
1112
+ describe "font_size" do
1113
+ it "returns the set font size" do
1114
+ assert_equal(0, @canvas.font_size)
1115
+ @canvas.font("Times", size: 10) # calls #font_size
1116
+ assert_equal(10, @canvas.font_size)
1117
+ end
1118
+
1119
+ it "sets the font size" do
1120
+ @canvas.font("Times", size: 10)
1121
+ assert_equal(10, @canvas.font_size)
1122
+ end
1123
+
1124
+ it "fails if no valid font is already set" do
1125
+ assert_raises(HexaPDF::Error) { @canvas.font_size(10) }
1126
+ end
1127
+ end
1128
+
1129
+ describe "show_glyphs" do
1130
+ it "serializes correctly" do
1131
+ @canvas.font("Times", size: 20)
1132
+ @canvas.horizontal_scaling(200)
1133
+ @canvas.character_spacing(1)
1134
+ @canvas.word_spacing(2)
1135
+
1136
+ font = @canvas.font
1137
+ @canvas.show_glyphs(font.decode_utf8("Hal lo").insert(2, -35).insert(0, -10))
1138
+ assert_in_delta(116.68, @canvas.text_cursor[0])
1139
+ assert_equal(0, @canvas.text_cursor[1])
1140
+ @canvas.font_size(10)
1141
+ @canvas.show_glyphs(font.decode_utf8("Hal"))
1142
+ assert_operators(@canvas.contents, [[:set_font_and_size, [:F1, 20]],
1143
+ [:set_horizontal_scaling, [200]],
1144
+ [:set_character_spacing, [1]],
1145
+ [:set_word_spacing, [2]],
1146
+ [:begin_text],
1147
+ [:show_text_with_positioning, [['', -10, "Ha", -35, "l lo"]]],
1148
+ [:set_font_and_size, [:F1, 10]],
1149
+ [:show_text_with_positioning, [["Hal"]]]])
1150
+ end
1151
+
1152
+ it "does nothing if there are no glyphs" do
1153
+ @canvas.show_glyphs_only([])
1154
+ assert_operators(@canvas.contents, [])
1155
+ end
1156
+ end
1157
+
1158
+ describe "show_glyphs_only" do
1159
+ it "serializes positioned glyphs correctly" do
1160
+ @canvas.font("Times", size: 20)
1161
+ font = @canvas.font
1162
+ @canvas.show_glyphs_only(font.decode_utf8("Hal lo").insert(2, -35))
1163
+ assert_equal(0, @canvas.text_cursor[0])
1164
+ assert_equal(0, @canvas.text_cursor[1])
1165
+ assert_operators(@canvas.contents, [[:set_font_and_size, [:F1, 20]],
1166
+ [:begin_text],
1167
+ [:show_text_with_positioning, [["Ha", -35, "l lo"]]]])
1168
+ end
1169
+
1170
+ it "serializes unpositioned glyphs correctly" do
1171
+ @canvas.font("Times", size: 20)
1172
+ font = @canvas.font
1173
+ @canvas.show_glyphs_only(font.decode_utf8("Hallo"))
1174
+ assert_operators(@canvas.contents, [[:set_font_and_size, [:F1, 20]],
1175
+ [:begin_text],
1176
+ [:show_text, ["Hallo"]]])
1177
+ end
1178
+
1179
+ it "does nothing if there are no glyphs" do
1180
+ @canvas.show_glyphs_only([])
1181
+ assert_operators(@canvas.contents, [])
1182
+ end
1183
+ end
1184
+
1185
+ describe "text" do
1186
+ it "sets the text cursor position if instructed" do
1187
+ @canvas.font("Times", size: 10)
1188
+ @canvas.text("Hallo", at: [100, 100])
1189
+ assert_operators(@canvas.contents, [[:set_font_and_size, [:F1, 10]],
1190
+ [:begin_text],
1191
+ [:set_text_matrix, [1, 0, 0, 1, 100, 100]],
1192
+ [:set_leading, [10]],
1193
+ [:show_text_with_positioning, [["Hallo"]]]])
1194
+ end
1195
+
1196
+ it "shows text, possibly split over multiple lines" do
1197
+ @canvas.font("Times", size: 10)
1198
+ @canvas.text("H\u{D A}H\u{A}H\u{B}H\u{c}H\u{D}H\u{85}H\u{2028}H\u{2029}H")
1199
+ assert_operators(@canvas.contents, [[:set_font_and_size, [:F1, 10]],
1200
+ [:set_leading, [10]],
1201
+ [:begin_text],
1202
+ [:show_text_with_positioning, [["H"]]], [:move_text_next_line],
1203
+ [:show_text_with_positioning, [["H"]]], [:move_text_next_line],
1204
+ [:show_text_with_positioning, [["H"]]], [:move_text_next_line],
1205
+ [:show_text_with_positioning, [["H"]]], [:move_text_next_line],
1206
+ [:show_text_with_positioning, [["H"]]], [:move_text_next_line],
1207
+ [:show_text_with_positioning, [["H"]]], [:move_text_next_line],
1208
+ [:show_text_with_positioning, [["H"]]], [:move_text_next_line],
1209
+ [:show_text_with_positioning, [["H"]]], [:move_text_next_line],
1210
+ [:show_text_with_positioning, [["H"]]]])
1211
+ end
1212
+
1213
+ it "fails if no valid font is set" do
1214
+ error = assert_raises(HexaPDF::Error) { @canvas.text("test") }
1215
+ assert_match(/if a font is set/, error.message)
1216
+ end
1217
+ end
1218
+
1219
+ describe "marked_content_point" do
1220
+ it "invokes the operator implementation" do
1221
+ assert_operator_invoked(:MP, :tag) { @canvas.marked_content_point(:tag) }
1222
+ assert_operator_invoked(:DP, :tag, :P1) do
1223
+ @canvas.marked_content_point(:tag, property_list: {key: 5})
1224
+ end
1225
+ end
1226
+
1227
+ it "is serialized correctly" do
1228
+ @canvas.marked_content_point(:tag)
1229
+ assert_operators(@canvas.contents, [[:designate_marked_content_point, [:tag]]])
1230
+ end
1231
+
1232
+ it "fails if invoked while in an unsupported graphics objects" do
1233
+ assert_raises_in_graphics_object(:path, :clipping_path) { @canvas.marked_content_point(:tag) }
1234
+ end
1235
+ end
1236
+
1237
+ describe "marked_content_sequence" do
1238
+ it "invokes the operator implementation" do
1239
+ assert_operator_invoked(:BMC, :tag) { @canvas.marked_content_sequence(:tag) }
1240
+ assert_operator_invoked(:BDC, :tag, :P1) do
1241
+ @canvas.marked_content_sequence(:tag, property_list: {key: 5})
1242
+ end
1243
+ end
1244
+
1245
+ it "is serialized correctly when no block is used" do
1246
+ @canvas.marked_content_sequence(:tag)
1247
+ assert_operators(@canvas.contents, [[:begin_marked_content, [:tag]]])
1248
+ end
1249
+
1250
+ it "is serialized correctly when a block is used" do
1251
+ @canvas.marked_content_sequence(:tag, property_list: {key: 5}) {}
1252
+ assert_operators(@canvas.contents, [[:begin_marked_content_with_property_list, [:tag, :P1]],
1253
+ [:end_marked_content]])
1254
+ end
1255
+
1256
+ it "fails if invoked while in an unsupported graphics objects" do
1257
+ assert_raises_in_graphics_object(:path, :clipping_path) do
1258
+ @canvas.marked_content_sequence(:tag)
1259
+ end
1260
+ end
1261
+ end
1262
+
1263
+ describe "end_marked_content_sequence" do
1264
+ it "invokes the operator implementation" do
1265
+ assert_operator_invoked(:EMC) { @canvas.end_marked_content_sequence }
1266
+ end
1267
+
1268
+ it "is serialized correctly" do
1269
+ @canvas.end_marked_content_sequence
1270
+ assert_operators(@page.contents, [[:end_marked_content]])
1271
+ end
1272
+
1273
+ it "fails if invoked while in an unsupported graphics objects" do
1274
+ assert_raises_in_graphics_object(:path, :clipping_path) do
1275
+ @canvas.end_marked_content_sequence
1276
+ end
1277
+ end
1278
+ end
1279
+ end