hexapdf 0.17.1 → 0.17.2

Sign up to get free protection for your applications and to get access to all the features.
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