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,790 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ require 'test_helper'
4
+ require_relative '../content/common'
5
+ require 'hexapdf/document'
6
+ require 'hexapdf/layout/style'
7
+ require 'hexapdf/layout/text_layouter'
8
+ require 'hexapdf/layout/box'
9
+
10
+ describe HexaPDF::Layout::Style::LineSpacing do
11
+ before do
12
+ @line1 = Object.new
13
+ @line1.define_singleton_method(:y_min) { - 1 }
14
+ @line1.define_singleton_method(:y_max) { 2 }
15
+ @line2 = Object.new
16
+ @line2.define_singleton_method(:y_min) { -3 }
17
+ @line2.define_singleton_method(:y_max) { 4 }
18
+ end
19
+
20
+ def line_spacing(type, value = nil)
21
+ HexaPDF::Layout::Style::LineSpacing.new(type: type, value: value)
22
+ end
23
+
24
+ it "allows single line spacing" do
25
+ obj = line_spacing(:single)
26
+ assert_equal(:proportional, obj.type)
27
+ assert_equal(1, obj.value)
28
+ assert_equal(1 + 4, obj.baseline_distance(@line1, @line2))
29
+ assert_equal(0, obj.gap(@line1, @line2))
30
+ end
31
+
32
+ it "allows double line spacing" do
33
+ obj = line_spacing(:double)
34
+ assert_equal(:proportional, obj.type)
35
+ assert_equal(2, obj.value)
36
+ assert_equal((1 + 4) * 2, obj.baseline_distance(@line1, @line2))
37
+ assert_equal(1 + 4, obj.gap(@line1, @line2))
38
+ end
39
+
40
+ it "allows proportional line spacing" do
41
+ obj = line_spacing(:proportional, 1.5)
42
+ assert_equal(:proportional, obj.type)
43
+ assert_equal(1.5, obj.value)
44
+ assert_equal((1 + 4) * 1.5, obj.baseline_distance(@line1, @line2))
45
+ assert_equal((1 + 4) * 0.5, obj.gap(@line1, @line2))
46
+ end
47
+
48
+ it "allows using an Integer or Float as type to mean proportional line spacing" do
49
+ obj = line_spacing(2)
50
+ assert_equal(:proportional, obj.type)
51
+ assert_equal(2, obj.value)
52
+
53
+ obj = line_spacing(2.5)
54
+ assert_equal(:proportional, obj.type)
55
+ assert_equal(2.5, obj.value)
56
+ end
57
+
58
+ it "allows fixed line spacing" do
59
+ obj = line_spacing(:fixed, 7)
60
+ assert_equal(:fixed, obj.type)
61
+ assert_equal(7, obj.value)
62
+ assert_equal(7, obj.baseline_distance(@line1, @line2))
63
+ assert_equal(7 - 1 - 4, obj.gap(@line1, @line2))
64
+ end
65
+
66
+ it "allows line spacing using a leading value" do
67
+ obj = line_spacing(:leading, 3)
68
+ assert_equal(:leading, obj.type)
69
+ assert_equal(3, obj.value)
70
+ assert_equal(1 + 4 + 3, obj.baseline_distance(@line1, @line2))
71
+ assert_equal(3, obj.gap(@line1, @line2))
72
+ end
73
+
74
+ it "allows using a LineSpacing object as type" do
75
+ obj = line_spacing(line_spacing(:single))
76
+ assert_equal(:proportional, obj.type)
77
+ end
78
+
79
+ it "raises an error if a value is needed and none is provided" do
80
+ assert_raises(ArgumentError) { line_spacing(:proportional) }
81
+ end
82
+
83
+ it "raises an error if an invalid type is provided" do
84
+ assert_raises(ArgumentError) { line_spacing(:invalid) }
85
+ end
86
+ end
87
+
88
+ describe HexaPDF::Layout::Style::Quad do
89
+ def create_quad(val)
90
+ HexaPDF::Layout::Style::Quad.new(val)
91
+ end
92
+
93
+ describe "initialize" do
94
+ it "works with a single value" do
95
+ quad = create_quad(5)
96
+ assert_equal(5, quad.top)
97
+ assert_equal(5, quad.right)
98
+ assert_equal(5, quad.bottom)
99
+ assert_equal(5, quad.left)
100
+
101
+ quad = create_quad([5])
102
+ assert_equal(5, quad.top)
103
+ assert_equal(5, quad.right)
104
+ assert_equal(5, quad.bottom)
105
+ assert_equal(5, quad.left)
106
+ end
107
+
108
+ it "works with two values" do
109
+ quad = create_quad([5, 2])
110
+ assert_equal(5, quad.top)
111
+ assert_equal(2, quad.right)
112
+ assert_equal(5, quad.bottom)
113
+ assert_equal(2, quad.left)
114
+ end
115
+
116
+ it "works with three values" do
117
+ quad = create_quad([5, 2, 7])
118
+ assert_equal(5, quad.top)
119
+ assert_equal(2, quad.right)
120
+ assert_equal(7, quad.bottom)
121
+ assert_equal(2, quad.left)
122
+ end
123
+
124
+ it "works with four or more values" do
125
+ quad = create_quad([5, 2, 7, 1, 9])
126
+ assert_equal(5, quad.top)
127
+ assert_equal(2, quad.right)
128
+ assert_equal(7, quad.bottom)
129
+ assert_equal(1, quad.left)
130
+ end
131
+
132
+ it "works with a Quad as value" do
133
+ quad = create_quad([5, 2, 7, 1])
134
+ new_quad = create_quad(quad)
135
+ assert_equal(new_quad.top, quad.top)
136
+ assert_equal(new_quad.right, quad.right)
137
+ assert_equal(new_quad.bottom, quad.bottom)
138
+ assert_equal(new_quad.left, quad.left)
139
+ end
140
+ end
141
+
142
+ it "can be asked if it contains only a single value" do
143
+ assert(create_quad(5).simple?)
144
+ refute(create_quad([5, 2]).simple?)
145
+ end
146
+ end
147
+
148
+ describe HexaPDF::Layout::Style::Border do
149
+ def create_border(**args)
150
+ HexaPDF::Layout::Style::Border.new(**args)
151
+ end
152
+
153
+ it "has accessors for with, color and style that return Quads" do
154
+ border = create_border
155
+ assert_kind_of(HexaPDF::Layout::Style::Quad, border.width)
156
+ assert_kind_of(HexaPDF::Layout::Style::Quad, border.color)
157
+ assert_kind_of(HexaPDF::Layout::Style::Quad, border.style)
158
+ end
159
+
160
+ it "can be duplicated" do
161
+ border = create_border
162
+ copy = border.dup
163
+ border.width.top = 10
164
+ border.color.top = :red
165
+ border.style.top = :dotted
166
+ assert_equal(0, copy.width.top)
167
+ assert_equal(0, copy.color.top)
168
+ assert_equal(:solid, copy.style.top)
169
+ end
170
+
171
+ it "can be asked whether a border is defined" do
172
+ assert(create_border.none?)
173
+ refute(create_border(width: 5).none?)
174
+ end
175
+
176
+ describe "draw" do
177
+ before do
178
+ @canvas = HexaPDF::Document.new.pages.add.canvas
179
+ end
180
+
181
+ it "draws nothing if no border is defined" do
182
+ border = create_border
183
+ border.draw(@canvas, 0, 0, 100, 100)
184
+ assert_operators(@canvas.contents, [])
185
+ end
186
+
187
+ describe "simple - same width, color and style on all sides" do
188
+ it "works with style solid" do
189
+ border = create_border(width: 10, color: 0.5, style: :solid)
190
+ border.draw(@canvas, 0, 0, 100, 100)
191
+ assert_operators(@canvas.contents, [[:save_graphics_state],
192
+ [:set_device_gray_stroking_color, [0.5]],
193
+ [:set_line_width, [10]],
194
+ [:append_rectangle, [0, 0, 100, 100]],
195
+ [:clip_path_non_zero], [:end_path],
196
+ [:append_rectangle, [5, 5, 90, 90]],
197
+ [:stroke_path],
198
+ [:restore_graphics_state]])
199
+ end
200
+
201
+ it "works with style dashed" do
202
+ border = create_border(width: 10, color: 0.5, style: :dashed)
203
+ border.draw(@canvas, 0, 0, 200, 300)
204
+ ops = [[:save_graphics_state],
205
+ [:set_device_gray_stroking_color, [0.5]],
206
+ [:set_line_width, [10]],
207
+ [:set_line_cap_style, [2]],
208
+ [:append_rectangle, [0, 0, 200, 300]],
209
+ [:clip_path_non_zero], [:end_path],
210
+ [:set_line_dash_pattern, [[10, 20], 25]],
211
+ [:move_to, [0, 295]], [:line_to, [200, 295]],
212
+ [:move_to, [200, 5]], [:line_to, [0, 5]],
213
+ [:stroke_path],
214
+ [:set_line_dash_pattern, [[10, 18], 23]],
215
+ [:move_to, [195, 300]], [:line_to, [195, 0]],
216
+ [:move_to, [5, 0]], [:line_to, [5, 300]],
217
+ [:stroke_path],
218
+ [:restore_graphics_state]]
219
+ assert_operators(@canvas.contents, ops)
220
+ end
221
+
222
+ it "works with style dashed_round" do
223
+ border = create_border(width: 10, color: 0.5, style: :dashed_round)
224
+ border.draw(@canvas, 0, 0, 200, 300)
225
+ ops = [[:save_graphics_state],
226
+ [:set_device_gray_stroking_color, [0.5]],
227
+ [:set_line_width, [10]],
228
+ [:set_line_cap_style, [1]],
229
+ [:append_rectangle, [0, 0, 200, 300]],
230
+ [:clip_path_non_zero], [:end_path],
231
+ [:set_line_dash_pattern, [[10, 20], 25]],
232
+ [:move_to, [0, 295]], [:line_to, [200, 295]],
233
+ [:move_to, [200, 5]], [:line_to, [0, 5]],
234
+ [:stroke_path],
235
+ [:set_line_dash_pattern, [[10, 18], 23]],
236
+ [:move_to, [195, 300]], [:line_to, [195, 0]],
237
+ [:move_to, [5, 0]], [:line_to, [5, 300]],
238
+ [:stroke_path],
239
+ [:restore_graphics_state]]
240
+ assert_operators(@canvas.contents, ops)
241
+ end
242
+
243
+ it "works with style dotted" do
244
+ border = create_border(width: 10, color: 0.5, style: :dotted)
245
+ border.draw(@canvas, 0, 0, 100, 200)
246
+ ops = [[:save_graphics_state],
247
+ [:set_device_gray_stroking_color, [0.5]],
248
+ [:set_line_width, [10]],
249
+ [:set_line_cap_style, [1]],
250
+ [:append_rectangle, [0, 0, 100, 200]],
251
+ [:clip_path_non_zero], [:end_path],
252
+ [:set_line_dash_pattern, [[0, 18], 13]],
253
+ [:move_to, [0, 195]], [:line_to, [100, 195]],
254
+ [:move_to, [100, 5]], [:line_to, [0, 5]],
255
+ [:stroke_path],
256
+ [:set_line_dash_pattern, [[0, 19], 14]],
257
+ [:move_to, [95, 200]], [:line_to, [95, 0]],
258
+ [:move_to, [5, 0]], [:line_to, [5, 200]],
259
+ [:stroke_path],
260
+ [:restore_graphics_state]]
261
+ assert_operators(@canvas.contents, ops)
262
+ end
263
+ end
264
+
265
+ describe "complex borders where edges have different width/color/style values" do
266
+ it "works correctly for the top border" do
267
+ border = create_border(width: [10, 0, 0, 0], color: 0.5, style: :dashed)
268
+ border.draw(@canvas, 0, 0, 200, 300)
269
+ ops = [[:save_graphics_state],
270
+ [:save_graphics_state],
271
+ [:move_to, [0, 300]], [:line_to, [200, 300]],
272
+ [:line_to, [200, 290]], [:line_to, [0, 290]],
273
+ [:clip_path_non_zero], [:end_path],
274
+ [:set_device_gray_stroking_color, [0.5]],
275
+ [:set_line_width, [10]],
276
+ [:set_line_cap_style, [2]],
277
+ [:set_line_dash_pattern, [[10, 20], 25]],
278
+ [:move_to, [0, 295]], [:line_to, [200, 295]],
279
+ [:stroke_path],
280
+ [:restore_graphics_state],
281
+ [:restore_graphics_state]]
282
+ assert_operators(@canvas.contents, ops)
283
+ end
284
+
285
+ it "works correctly for the right border" do
286
+ border = create_border(width: [0, 10, 0, 0], color: 0.5, style: :dashed)
287
+ border.draw(@canvas, 0, 0, 200, 300)
288
+ ops = [[:save_graphics_state],
289
+ [:save_graphics_state],
290
+ [:move_to, [200, 300]], [:line_to, [200, 0]],
291
+ [:line_to, [190, 0]], [:line_to, [190, 300]],
292
+ [:clip_path_non_zero], [:end_path],
293
+ [:set_device_gray_stroking_color, [0.5]],
294
+ [:set_line_width, [10]],
295
+ [:set_line_cap_style, [2]],
296
+ [:set_line_dash_pattern, [[10, 18], 23]],
297
+ [:move_to, [195, 300]], [:line_to, [195, 0]],
298
+ [:stroke_path],
299
+ [:restore_graphics_state],
300
+ [:restore_graphics_state]]
301
+ assert_operators(@canvas.contents, ops)
302
+ end
303
+
304
+ it "works correctly for the bottom border" do
305
+ border = create_border(width: [0, 0, 10, 0], color: 0.5, style: :dashed)
306
+ border.draw(@canvas, 0, 0, 200, 300)
307
+ ops = [[:save_graphics_state],
308
+ [:save_graphics_state],
309
+ [:move_to, [200, 0]], [:line_to, [0, 0]],
310
+ [:line_to, [0, 10]], [:line_to, [200, 10]],
311
+ [:clip_path_non_zero], [:end_path],
312
+ [:set_device_gray_stroking_color, [0.5]],
313
+ [:set_line_width, [10]],
314
+ [:set_line_cap_style, [2]],
315
+ [:set_line_dash_pattern, [[10, 20], 25]],
316
+ [:move_to, [200, 5]], [:line_to, [0, 5]],
317
+ [:stroke_path],
318
+ [:restore_graphics_state],
319
+ [:restore_graphics_state]]
320
+ assert_operators(@canvas.contents, ops)
321
+ end
322
+
323
+ it "works correctly for the left border" do
324
+ border = create_border(width: [0, 0, 0, 10], color: 0.5, style: :dashed)
325
+ border.draw(@canvas, 0, 0, 200, 300)
326
+ ops = [[:save_graphics_state],
327
+ [:save_graphics_state],
328
+ [:move_to, [0, 0]], [:line_to, [0, 300]],
329
+ [:line_to, [10, 300]], [:line_to, [10, 0]],
330
+ [:clip_path_non_zero], [:end_path],
331
+ [:set_device_gray_stroking_color, [0.5]],
332
+ [:set_line_width, [10]],
333
+ [:set_line_cap_style, [2]],
334
+ [:set_line_dash_pattern, [[10, 18], 23]],
335
+ [:move_to, [5, 0]], [:line_to, [5, 300]],
336
+ [:stroke_path],
337
+ [:restore_graphics_state],
338
+ [:restore_graphics_state]]
339
+ assert_operators(@canvas.contents, ops)
340
+ end
341
+
342
+ it "works with all values combined" do
343
+ border = create_border(width: [20, 10, 40, 30], color: [0, 0.25, 0.5, 0.75],
344
+ style: [:solid, :dashed, :dashed_round, :dotted])
345
+ border.draw(@canvas, 0, 0, 100, 200)
346
+ ops = [[:save_graphics_state],
347
+ [:save_graphics_state],
348
+ [:move_to, [0, 200]], [:line_to, [100, 200]],
349
+ [:line_to, [90, 180]], [:line_to, [30, 180]], [:clip_path_non_zero], [:end_path],
350
+ [:set_line_width, [20]],
351
+ [:move_to, [0, 190]], [:line_to, [100, 190]], [:stroke_path],
352
+ [:restore_graphics_state], [:save_graphics_state],
353
+ [:move_to, [100, 200]], [:line_to, [100, 0]],
354
+ [:line_to, [90, 40]], [:line_to, [90, 180]], [:clip_path_non_zero], [:end_path],
355
+ [:set_device_gray_stroking_color, [0.25]], [:set_line_width, [10]],
356
+ [:set_line_cap_style, [2]], [:set_line_dash_pattern, [[10, 20], 25]],
357
+ [:move_to, [95, 200]], [:line_to, [95, 0]], [:stroke_path],
358
+ [:restore_graphics_state], [:save_graphics_state],
359
+ [:move_to, [100, 0]], [:line_to, [0, 0]],
360
+ [:line_to, [30, 40]], [:line_to, [90, 40]], [:clip_path_non_zero], [:end_path],
361
+ [:set_device_gray_stroking_color, [0.5]], [:set_line_width, [40]],
362
+ [:set_line_cap_style, [1]], [:set_line_dash_pattern, [[40, 0], 20]],
363
+ [:move_to, [100, 20]], [:line_to, [0, 20]], [:stroke_path],
364
+ [:restore_graphics_state], [:save_graphics_state],
365
+ [:move_to, [0, 0]], [:line_to, [0, 200]],
366
+ [:line_to, [30, 180]], [:line_to, [30, 40]], [:clip_path_non_zero], [:end_path],
367
+ [:set_device_gray_stroking_color, [0.75]], [:set_line_width, [30]],
368
+ [:set_line_cap_style, [1]], [:set_line_dash_pattern, [[0, 42.5], 27.5]],
369
+ [:move_to, [15, 0]], [:line_to, [15, 200]], [:stroke_path],
370
+ [:restore_graphics_state], [:restore_graphics_state]]
371
+ assert_operators(@canvas.contents, ops)
372
+ end
373
+ end
374
+
375
+ describe "border width greater than edge length" do
376
+ it "works for solid borders" do
377
+ border = create_border(width: 100, style: :solid)
378
+ border.draw(@canvas, 0, 0, 10, 10)
379
+ assert_operators(@canvas.contents, [[:save_graphics_state],
380
+ [:set_line_width, [100]],
381
+ [:append_rectangle, [0, 0, 10, 10]],
382
+ [:clip_path_non_zero], [:end_path],
383
+ [:append_rectangle, [50, 50, -90, -90]],
384
+ [:stroke_path],
385
+ [:restore_graphics_state]])
386
+ end
387
+
388
+ it "works for dashed borders" do
389
+ border = create_border(width: 100, style: :dashed)
390
+ border.draw(@canvas, 0, 0, 10, 10)
391
+ assert_operators(@canvas.contents, [[:save_graphics_state],
392
+ [:set_line_width, [100]],
393
+ [:set_line_cap_style, [2]],
394
+ [:append_rectangle, [0, 0, 10, 10]],
395
+ [:clip_path_non_zero], [:end_path],
396
+ [:set_line_dash_pattern, [[100, 0], 50]],
397
+ [:move_to, [0, -40]], [:line_to, [10, -40]],
398
+ [:move_to, [10, 50]], [:line_to, [0, 50]],
399
+ [:stroke_path],
400
+ [:move_to, [-40, 10]], [:line_to, [-40, 0]],
401
+ [:move_to, [50, 0]], [:line_to, [50, 10]],
402
+ [:stroke_path],
403
+ [:restore_graphics_state]])
404
+ end
405
+ it "works for dashed-round borders" do
406
+ border = create_border(width: 100, style: :dashed_round)
407
+ border.draw(@canvas, 0, 0, 10, 10)
408
+ assert_operators(@canvas.contents, [[:save_graphics_state],
409
+ [:set_line_width, [100]],
410
+ [:set_line_cap_style, [1]],
411
+ [:append_rectangle, [0, 0, 10, 10]],
412
+ [:clip_path_non_zero], [:end_path],
413
+ [:set_line_dash_pattern, [[100, 0], 50]],
414
+ [:move_to, [0, -40]], [:line_to, [10, -40]],
415
+ [:move_to, [10, 50]], [:line_to, [0, 50]],
416
+ [:stroke_path],
417
+ [:move_to, [-40, 10]], [:line_to, [-40, 0]],
418
+ [:move_to, [50, 0]], [:line_to, [50, 10]],
419
+ [:stroke_path],
420
+ [:restore_graphics_state]])
421
+ end
422
+ it "works for dotted borders" do
423
+ border = create_border(width: 100, style: :dotted)
424
+ border.draw(@canvas, 0, 0, 10, 10)
425
+ assert_operators(@canvas.contents, [[:save_graphics_state],
426
+ [:set_line_width, [100]],
427
+ [:set_line_cap_style, [1]],
428
+ [:append_rectangle, [0, 0, 10, 10]],
429
+ [:clip_path_non_zero], [:end_path],
430
+ [:set_line_dash_pattern, [[0, 1], 0]],
431
+ [:move_to, [0, -40]], [:line_to, [10, -40]],
432
+ [:move_to, [10, 50]], [:line_to, [0, 50]],
433
+ [:stroke_path],
434
+ [:move_to, [-40, 10]], [:line_to, [-40, 0]],
435
+ [:move_to, [50, 0]], [:line_to, [50, 10]],
436
+ [:stroke_path],
437
+ [:restore_graphics_state]])
438
+ end
439
+ end
440
+
441
+ it "raises an error if an invalid style is provided" do
442
+ assert_raises(ArgumentError) do
443
+ create_border(width: 1, color: 0, style: :unknown).draw(@canvas, 0, 0, 10, 10)
444
+ end
445
+ end
446
+ end
447
+ end
448
+
449
+ describe HexaPDF::Layout::Style::Layers do
450
+ before do
451
+ @layers = HexaPDF::Layout::Style::Layers.new
452
+ end
453
+
454
+ it "can be initialized with an array of layers" do
455
+ data = [lambda {}]
456
+ layers = HexaPDF::Layout::Style::Layers.new(data)
457
+ assert_equal(data, layers.enum_for(:each, {}).to_a)
458
+ end
459
+
460
+ it "can be duplicated" do
461
+ copy = @layers.dup
462
+ @layers.add(lambda {})
463
+ assert(copy.none?)
464
+ end
465
+
466
+ describe "add and each" do
467
+ it "can use a given block" do
468
+ block = proc { true }
469
+ @layers.add(&block)
470
+ assert_equal([block], @layers.enum_for(:each, {}).to_a)
471
+ end
472
+
473
+ it "can store a reference" do
474
+ @layers.add(:link, option: :value)
475
+ value = Object.new
476
+ value.define_singleton_method(:new) {|*| :new }
477
+ config = Object.new
478
+ config.define_singleton_method(:constantize) {|*| value }
479
+ assert_equal([:new], @layers.enum_for(:each, config).to_a)
480
+ end
481
+
482
+ it "fails if neither a block nor a name is given when adding a layer" do
483
+ assert_raises(ArgumentError) { @layers.add }
484
+ end
485
+ end
486
+
487
+ it "can determine whether layers are defined" do
488
+ assert(@layers.none?)
489
+ @layers.add {}
490
+ refute(@layers.none?)
491
+ end
492
+
493
+ it "draws the layers onto a canvas" do
494
+ box = Object.new
495
+ value = nil
496
+ klass = Class.new
497
+ klass.send(:define_method, :initialize) {|**args| @args = args }
498
+ klass.send(:define_method, :call) do |canvas, _|
499
+ value = @args
500
+ canvas.line_width(5)
501
+ end
502
+ canvas = HexaPDF::Document.new.pages.add.canvas
503
+ canvas.context.document.config['style.layers_map'][:test] = klass
504
+
505
+ @layers.draw(canvas, 10, 15, box)
506
+ @layers.add {|canv, ibox| assert_equal(box, ibox); canv.line_width(10) }
507
+ @layers.add(:test, option: :value)
508
+ @layers.draw(canvas, 10, 15, box)
509
+ ops = [[:save_graphics_state],
510
+ [:concatenate_matrix, [1, 0, 0, 1, 10, 15]],
511
+ [:save_graphics_state],
512
+ [:set_line_width, [10]],
513
+ [:restore_graphics_state],
514
+ [:save_graphics_state],
515
+ [:set_line_width, [5]],
516
+ [:restore_graphics_state],
517
+ [:restore_graphics_state]]
518
+ assert_operators(canvas.contents, ops)
519
+ assert_equal({option: :value}, value)
520
+ end
521
+ end
522
+
523
+ describe HexaPDF::Layout::Style::LinkLayer do
524
+ describe "initialize" do
525
+ it "fails if more than one possible target is chosen" do
526
+ assert_raises(ArgumentError) { HexaPDF::Layout::Style::LinkLayer.new(dest: true, uri: true) }
527
+ assert_raises(ArgumentError) { HexaPDF::Layout::Style::LinkLayer.new(dest: true, file: true) }
528
+ assert_raises(ArgumentError) { HexaPDF::Layout::Style::LinkLayer.new(uri: true, file: true) }
529
+ end
530
+
531
+ it "fails if an invalid border is provided" do
532
+ assert_raises(ArgumentError) { HexaPDF::Layout::Style::LinkLayer.new(border: 5) }
533
+ end
534
+ end
535
+
536
+ describe "call" do
537
+ before do
538
+ @canvas = HexaPDF::Document.new.pages.add.canvas
539
+ @canvas.translate(10, 10)
540
+ @box = HexaPDF::Layout::Box.new(width: 15, height: 10)
541
+ end
542
+
543
+ def call_link(hash)
544
+ link = HexaPDF::Layout::Style::LinkLayer.new(**hash)
545
+ link.call(@canvas, @box)
546
+ @canvas.context[:Annots]&.first
547
+ end
548
+
549
+ it "does nothing if the context is not a page object" do
550
+ @canvas = HexaPDF::Document.new.add({Type: :XObject, Subtype: :Form, BBox: [0, 0, 1, 1]}).canvas
551
+ assert_nil(call_link(dest: true))
552
+ end
553
+
554
+ it "sets general values like /Rect and /QuadPoints" do
555
+ annot = call_link(dest: true)
556
+ assert_equal(:Link, annot[:Subtype])
557
+ assert_equal([10, 10, 25, 20], annot[:Rect].value)
558
+ assert_equal([10, 10, 25, 10, 25, 20, 10, 20], annot[:QuadPoints].value)
559
+ end
560
+
561
+ it "removes the border by default" do
562
+ annot = call_link(dest: true)
563
+ assert_equal([0, 0, 0], annot[:Border].value)
564
+ end
565
+
566
+ it "uses a default border if no specific border style is specified" do
567
+ annot = call_link(dest: true, border: true)
568
+ assert_equal([0, 0, 1], annot[:Border].value)
569
+ end
570
+
571
+ it "uses the specified border and border color" do
572
+ annot = call_link(dest: true, border: [10, 10, 2], border_color: [255])
573
+ assert_equal([10, 10, 2], annot[:Border].value)
574
+ assert_equal([1.0], annot[:C].value)
575
+ end
576
+
577
+ it "works for simple destinations" do
578
+ annot = call_link(dest: [@canvas.context, :FitH])
579
+ assert_equal([@canvas.context, :FitH], annot[:Dest].value)
580
+ assert_nil(annot[:A])
581
+ end
582
+
583
+ it "works for URIs" do
584
+ annot = call_link(uri: "test.html")
585
+ assert_equal({S: :URI, URI: "test.html"}, annot[:A].value)
586
+ assert_nil(annot[:Dest])
587
+ end
588
+
589
+ it "works for files" do
590
+ annot = call_link(file: "local-file.pdf")
591
+ assert_equal({S: :Launch, F: "local-file.pdf", NewWindow: true}, annot[:A].value)
592
+ assert_nil(annot[:Dest])
593
+ end
594
+ end
595
+ end
596
+
597
+ describe HexaPDF::Layout::Style do
598
+ before do
599
+ @style = HexaPDF::Layout::Style.new
600
+ end
601
+
602
+ it "can assign values on initialization" do
603
+ style = HexaPDF::Layout::Style.new(font_size: 10)
604
+ assert_equal(10, style.font_size)
605
+ end
606
+
607
+ describe "initialize_copy" do
608
+ it "can be duplicated" do
609
+ @style.font_features[:kerning] = true
610
+ @style.padding.top = 10
611
+ @style.margin.top = 10
612
+ @style.border.width.top = 10
613
+ @style.overlays.add(lambda {})
614
+ @style.underlays.add(lambda {})
615
+
616
+ copy = @style.dup
617
+ @style.font_features[:kerning] = false
618
+ @style.padding.top = 5
619
+ @style.margin.top = 5
620
+ @style.border.width.top = 5
621
+ @style.overlays.add(lambda {})
622
+ @style.underlays.add(lambda {})
623
+
624
+ assert_equal({kerning: true}, copy.font_features)
625
+ assert_equal(10, copy.padding.top)
626
+ assert_equal(10, copy.margin.top)
627
+ assert_equal(10, copy.border.width.top)
628
+ assert_equal(1, copy.underlays.instance_variable_get(:@layers).size)
629
+ assert_equal(1, copy.overlays.instance_variable_get(:@layers).size)
630
+ end
631
+
632
+ it "resets the cache" do
633
+ @style.horizontal_scaling(200)
634
+ assert_equal(2.0, @style.scaled_horizontal_scaling)
635
+ assert_equal(-1.06, @style.scaled_item_width(53))
636
+
637
+ style = @style.dup
638
+ style.horizontal_scaling(100)
639
+ assert_equal(2.0, @style.scaled_horizontal_scaling)
640
+ assert_equal(-1.06, @style.scaled_item_width(53))
641
+ assert_equal(1.0, style.scaled_horizontal_scaling)
642
+ assert_equal(-0.53, style.scaled_item_width(53))
643
+ end
644
+ end
645
+
646
+ it "has several simple and dynamically generated properties with default values" do
647
+ assert_raises(HexaPDF::Error) { @style.font }
648
+ assert_equal(10, @style.font_size)
649
+ assert_equal(0, @style.character_spacing)
650
+ assert_equal(0, @style.word_spacing)
651
+ assert_equal(100, @style.horizontal_scaling)
652
+ assert_equal(0, @style.text_rise)
653
+ assert_equal({}, @style.font_features)
654
+ assert_equal(HexaPDF::Content::TextRenderingMode::FILL, @style.text_rendering_mode)
655
+ assert_equal([0], @style.fill_color.components)
656
+ assert_equal(1, @style.fill_alpha)
657
+ assert_equal([0], @style.stroke_color.components)
658
+ assert_equal(1, @style.stroke_alpha)
659
+ assert_equal(1, @style.stroke_width)
660
+ assert_equal(HexaPDF::Content::LineCapStyle::BUTT_CAP, @style.stroke_cap_style)
661
+ assert_equal(HexaPDF::Content::LineJoinStyle::MITER_JOIN, @style.stroke_join_style)
662
+ assert_equal(10.0, @style.stroke_miter_limit)
663
+ assert_equal(:left, @style.align)
664
+ assert_equal(:top, @style.valign)
665
+ assert_equal(0, @style.text_indent)
666
+ assert_nil(@style.background_color)
667
+ assert_equal(1, @style.background_alpha)
668
+ assert(@style.padding.simple?)
669
+ assert_equal(0, @style.padding.top)
670
+ assert(@style.margin.simple?)
671
+ assert_equal(0, @style.margin.top)
672
+ assert(@style.border.none?)
673
+ assert_equal([[], 0], @style.stroke_dash_pattern.to_operands)
674
+ assert_equal([:proportional, 1], [@style.line_spacing.type, @style.line_spacing.value])
675
+ refute(@style.subscript)
676
+ refute(@style.superscript)
677
+ refute(@style.last_line_gap)
678
+ assert_kind_of(HexaPDF::Layout::Style::Layers, @style.underlays)
679
+ assert_kind_of(HexaPDF::Layout::Style::Layers, @style.overlays)
680
+ end
681
+
682
+ it "allows using a non-standard setter for generated properties" do
683
+ @style.padding = [5, 3]
684
+ assert_equal(5, @style.padding.top)
685
+ assert_equal(3, @style.padding.left)
686
+
687
+ @style.stroke_dash_pattern(5, 2)
688
+ assert_equal([[5], 2], @style.stroke_dash_pattern.to_operands)
689
+ end
690
+
691
+ it "allows checking for valid values" do
692
+ error = assert_raises(ArgumentError) { @style.align = :none }
693
+ assert_match(/not a valid align value \(:left, :center, :right, :justify\)/, error.message)
694
+ end
695
+
696
+ it "allows checking whether a property has been set or accessed" do
697
+ refute(@style.align?)
698
+ assert_equal(:left, @style.align)
699
+ assert(@style.align?)
700
+
701
+ refute(@style.valign?)
702
+ @style.valign = :bottom
703
+ assert(@style.valign?)
704
+ end
705
+
706
+ it "has several dynamically generated properties with default values that take blocks" do
707
+ assert_equal(HexaPDF::Layout::TextLayouter::SimpleTextSegmentation,
708
+ @style.text_segmentation_algorithm)
709
+ assert_equal(HexaPDF::Layout::TextLayouter::SimpleLineWrapping,
710
+ @style.text_line_wrapping_algorithm)
711
+
712
+ block = proc { :y }
713
+ @style.text_segmentation_algorithm(&block)
714
+ assert_equal(block, @style.text_segmentation_algorithm)
715
+
716
+ @style.text_segmentation_algorithm(:callable)
717
+ assert_equal(:callable, @style.text_segmentation_algorithm)
718
+ end
719
+
720
+ describe "methods for some derived and cached values" do
721
+ before do
722
+ wrapped_font = Object.new
723
+ wrapped_font.define_singleton_method(:ascender) { 600 }
724
+ wrapped_font.define_singleton_method(:descender) { -100 }
725
+ font = Object.new
726
+ font.define_singleton_method(:scaling_factor) { 1 }
727
+ font.define_singleton_method(:wrapped_font) { wrapped_font }
728
+ @style.font = font
729
+ end
730
+
731
+ it "computes them correctly" do
732
+ @style.horizontal_scaling(200).character_spacing(1).word_spacing(2)
733
+ assert_equal(0.02, @style.scaled_font_size)
734
+ assert_equal(2, @style.scaled_character_spacing)
735
+ assert_equal(4, @style.scaled_word_spacing)
736
+ assert_equal(2, @style.scaled_horizontal_scaling)
737
+
738
+ assert_equal(6, @style.scaled_font_ascender)
739
+ assert_equal(-1, @style.scaled_font_descender)
740
+ end
741
+
742
+ it "computes item widths correctly" do
743
+ @style.horizontal_scaling(200).character_spacing(1).word_spacing(2)
744
+
745
+ assert_equal(-1.0, @style.scaled_item_width(50))
746
+
747
+ obj = Object.new
748
+ obj.define_singleton_method(:width) { 100 }
749
+ obj.define_singleton_method(:apply_word_spacing?) { true }
750
+ assert_equal(8, @style.scaled_item_width(obj))
751
+ end
752
+
753
+ it "handles subscript" do
754
+ @style.subscript = true
755
+ assert_in_delta(5.83, @style.calculated_font_size)
756
+ assert_in_delta(0.00583, @style.scaled_font_size, 0.000001)
757
+ assert_in_delta(-2.00, @style.calculated_text_rise)
758
+ end
759
+
760
+ it "handles superscript" do
761
+ @style.superscript = true
762
+ assert_in_delta(5.83, @style.calculated_font_size)
763
+ assert_in_delta(3.30, @style.calculated_text_rise)
764
+ end
765
+
766
+ it "handles underline" do
767
+ @style.font.wrapped_font.define_singleton_method(:underline_position) { -100 }
768
+ @style.font.wrapped_font.define_singleton_method(:underline_thickness) { 10 }
769
+ @style.text_rise = 10
770
+ assert_in_delta(-1.05 + 10, @style.calculated_underline_position)
771
+ assert_equal(0.1, @style.calculated_underline_thickness)
772
+ end
773
+
774
+ it "handles strikeout" do
775
+ @style.font.wrapped_font.define_singleton_method(:strikeout_position) { 300 }
776
+ @style.font.wrapped_font.define_singleton_method(:strikeout_thickness) { 10 }
777
+ @style.text_rise = 10
778
+ assert_in_delta(2.95 + 10, @style.calculated_strikeout_position)
779
+ assert_equal(0.1, @style.calculated_strikeout_thickness)
780
+ end
781
+ end
782
+
783
+ it "can clear cached values" do
784
+ assert_equal(0.01, @style.scaled_font_size)
785
+ @style.font_size = 20
786
+ assert_equal(0.01, @style.scaled_font_size)
787
+ @style.clear_cache
788
+ assert_equal(0.02, @style.scaled_font_size)
789
+ end
790
+ end