hexapdf 0.17.1 → 0.17.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (255) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +1024 -0
  3. data/LICENSE +29 -0
  4. data/README.md +129 -0
  5. data/Rakefile +109 -0
  6. data/agpl-3.0.txt +661 -0
  7. data/examples/001-hello_world.rb +16 -0
  8. data/examples/002-graphics.rb +275 -0
  9. data/examples/003-arcs.rb +50 -0
  10. data/examples/004-optimizing.rb +23 -0
  11. data/examples/005-merging.rb +27 -0
  12. data/examples/006-standard_pdf_fonts.rb +73 -0
  13. data/examples/007-truetype.rb +42 -0
  14. data/examples/008-show_char_bboxes.rb +55 -0
  15. data/examples/009-text_layouter_alignment.rb +47 -0
  16. data/examples/010-text_layouter_inline_boxes.rb +64 -0
  17. data/examples/011-text_layouter_line_wrapping.rb +57 -0
  18. data/examples/012-text_layouter_styling.rb +122 -0
  19. data/examples/013-text_layouter_shapes.rb +176 -0
  20. data/examples/014-text_in_polygon.rb +60 -0
  21. data/examples/015-boxes.rb +76 -0
  22. data/examples/016-frame_automatic_box_placement.rb +90 -0
  23. data/examples/017-frame_text_flow.rb +60 -0
  24. data/examples/018-composer.rb +44 -0
  25. data/examples/019-acro_form.rb +88 -0
  26. data/examples/emoji-smile.png +0 -0
  27. data/examples/emoji-wink.png +0 -0
  28. data/examples/machupicchu.jpg +0 -0
  29. data/lib/hexapdf/content/graphic_object/endpoint_arc.rb +66 -0
  30. data/lib/hexapdf/content/graphic_object/geom2d.rb +13 -0
  31. data/lib/hexapdf/version.rb +1 -1
  32. data/test/data/aes-test-vectors/CBCGFSbox-128-decrypt.data.gz +0 -0
  33. data/test/data/aes-test-vectors/CBCGFSbox-128-encrypt.data.gz +0 -0
  34. data/test/data/aes-test-vectors/CBCGFSbox-192-decrypt.data.gz +0 -0
  35. data/test/data/aes-test-vectors/CBCGFSbox-192-encrypt.data.gz +0 -0
  36. data/test/data/aes-test-vectors/CBCGFSbox-256-decrypt.data.gz +0 -0
  37. data/test/data/aes-test-vectors/CBCGFSbox-256-encrypt.data.gz +0 -0
  38. data/test/data/aes-test-vectors/CBCKeySbox-128-decrypt.data.gz +0 -0
  39. data/test/data/aes-test-vectors/CBCKeySbox-128-encrypt.data.gz +0 -0
  40. data/test/data/aes-test-vectors/CBCKeySbox-192-decrypt.data.gz +0 -0
  41. data/test/data/aes-test-vectors/CBCKeySbox-192-encrypt.data.gz +0 -0
  42. data/test/data/aes-test-vectors/CBCKeySbox-256-decrypt.data.gz +0 -0
  43. data/test/data/aes-test-vectors/CBCKeySbox-256-encrypt.data.gz +0 -0
  44. data/test/data/aes-test-vectors/CBCVarKey-128-decrypt.data.gz +0 -0
  45. data/test/data/aes-test-vectors/CBCVarKey-128-encrypt.data.gz +0 -0
  46. data/test/data/aes-test-vectors/CBCVarKey-192-decrypt.data.gz +0 -0
  47. data/test/data/aes-test-vectors/CBCVarKey-192-encrypt.data.gz +0 -0
  48. data/test/data/aes-test-vectors/CBCVarKey-256-decrypt.data.gz +0 -0
  49. data/test/data/aes-test-vectors/CBCVarKey-256-encrypt.data.gz +0 -0
  50. data/test/data/aes-test-vectors/CBCVarTxt-128-decrypt.data.gz +0 -0
  51. data/test/data/aes-test-vectors/CBCVarTxt-128-encrypt.data.gz +0 -0
  52. data/test/data/aes-test-vectors/CBCVarTxt-192-decrypt.data.gz +0 -0
  53. data/test/data/aes-test-vectors/CBCVarTxt-192-encrypt.data.gz +0 -0
  54. data/test/data/aes-test-vectors/CBCVarTxt-256-decrypt.data.gz +0 -0
  55. data/test/data/aes-test-vectors/CBCVarTxt-256-encrypt.data.gz +0 -0
  56. data/test/data/fonts/Ubuntu-Title.ttf +0 -0
  57. data/test/data/images/cmyk.jpg +0 -0
  58. data/test/data/images/fillbytes.jpg +0 -0
  59. data/test/data/images/gray.jpg +0 -0
  60. data/test/data/images/greyscale-1bit.png +0 -0
  61. data/test/data/images/greyscale-2bit.png +0 -0
  62. data/test/data/images/greyscale-4bit.png +0 -0
  63. data/test/data/images/greyscale-8bit.png +0 -0
  64. data/test/data/images/greyscale-alpha-8bit.png +0 -0
  65. data/test/data/images/greyscale-trns-8bit.png +0 -0
  66. data/test/data/images/greyscale-with-gamma1.0.png +0 -0
  67. data/test/data/images/greyscale-with-gamma1.5.png +0 -0
  68. data/test/data/images/indexed-1bit.png +0 -0
  69. data/test/data/images/indexed-2bit.png +0 -0
  70. data/test/data/images/indexed-4bit.png +0 -0
  71. data/test/data/images/indexed-8bit.png +0 -0
  72. data/test/data/images/indexed-alpha-4bit.png +0 -0
  73. data/test/data/images/indexed-alpha-8bit.png +0 -0
  74. data/test/data/images/rgb.jpg +0 -0
  75. data/test/data/images/truecolour-8bit.png +0 -0
  76. data/test/data/images/truecolour-alpha-8bit.png +0 -0
  77. data/test/data/images/truecolour-gama-chrm-8bit.png +0 -0
  78. data/test/data/images/truecolour-srgb-8bit.png +0 -0
  79. data/test/data/images/ycck.jpg +0 -0
  80. data/test/data/minimal.pdf +44 -0
  81. data/test/data/standard-security-handler/README +9 -0
  82. data/test/data/standard-security-handler/bothpwd-aes-128bit-V4.pdf +44 -0
  83. data/test/data/standard-security-handler/bothpwd-aes-256bit-V5.pdf +0 -0
  84. data/test/data/standard-security-handler/bothpwd-arc4-128bit-V2.pdf +43 -0
  85. data/test/data/standard-security-handler/bothpwd-arc4-128bit-V4.pdf +43 -0
  86. data/test/data/standard-security-handler/bothpwd-arc4-40bit-V1.pdf +0 -0
  87. data/test/data/standard-security-handler/nopwd-aes-128bit-V4.pdf +43 -0
  88. data/test/data/standard-security-handler/nopwd-aes-256bit-V5.pdf +0 -0
  89. data/test/data/standard-security-handler/nopwd-arc4-128bit-V2.pdf +43 -0
  90. data/test/data/standard-security-handler/nopwd-arc4-128bit-V4.pdf +43 -0
  91. data/test/data/standard-security-handler/nopwd-arc4-40bit-V1.pdf +43 -0
  92. data/test/data/standard-security-handler/ownerpwd-aes-128bit-V4.pdf +0 -0
  93. data/test/data/standard-security-handler/ownerpwd-aes-256bit-V5.pdf +43 -0
  94. data/test/data/standard-security-handler/ownerpwd-arc4-128bit-V2.pdf +43 -0
  95. data/test/data/standard-security-handler/ownerpwd-arc4-128bit-V4.pdf +43 -0
  96. data/test/data/standard-security-handler/ownerpwd-arc4-40bit-V1.pdf +43 -0
  97. data/test/data/standard-security-handler/userpwd-aes-128bit-V4.pdf +43 -0
  98. data/test/data/standard-security-handler/userpwd-aes-256bit-V5.pdf +43 -0
  99. data/test/data/standard-security-handler/userpwd-arc4-128bit-V2.pdf +0 -0
  100. data/test/data/standard-security-handler/userpwd-arc4-128bit-V4.pdf +0 -0
  101. data/test/data/standard-security-handler/userpwd-arc4-40bit-V1.pdf +43 -0
  102. data/test/hexapdf/common_tokenizer_tests.rb +236 -0
  103. data/test/hexapdf/content/common.rb +39 -0
  104. data/test/hexapdf/content/graphic_object/test_arc.rb +102 -0
  105. data/test/hexapdf/content/graphic_object/test_endpoint_arc.rb +90 -0
  106. data/test/hexapdf/content/graphic_object/test_geom2d.rb +79 -0
  107. data/test/hexapdf/content/graphic_object/test_solid_arc.rb +86 -0
  108. data/test/hexapdf/content/test_canvas.rb +1279 -0
  109. data/test/hexapdf/content/test_color_space.rb +176 -0
  110. data/test/hexapdf/content/test_graphics_state.rb +151 -0
  111. data/test/hexapdf/content/test_operator.rb +619 -0
  112. data/test/hexapdf/content/test_parser.rb +99 -0
  113. data/test/hexapdf/content/test_processor.rb +163 -0
  114. data/test/hexapdf/content/test_transformation_matrix.rb +64 -0
  115. data/test/hexapdf/document/test_files.rb +72 -0
  116. data/test/hexapdf/document/test_fonts.rb +60 -0
  117. data/test/hexapdf/document/test_images.rb +72 -0
  118. data/test/hexapdf/document/test_pages.rb +130 -0
  119. data/test/hexapdf/encryption/common.rb +87 -0
  120. data/test/hexapdf/encryption/test_aes.rb +129 -0
  121. data/test/hexapdf/encryption/test_arc4.rb +39 -0
  122. data/test/hexapdf/encryption/test_fast_aes.rb +17 -0
  123. data/test/hexapdf/encryption/test_fast_arc4.rb +12 -0
  124. data/test/hexapdf/encryption/test_identity.rb +21 -0
  125. data/test/hexapdf/encryption/test_ruby_aes.rb +23 -0
  126. data/test/hexapdf/encryption/test_ruby_arc4.rb +20 -0
  127. data/test/hexapdf/encryption/test_security_handler.rb +380 -0
  128. data/test/hexapdf/encryption/test_standard_security_handler.rb +322 -0
  129. data/test/hexapdf/filter/common.rb +53 -0
  130. data/test/hexapdf/filter/test_ascii85_decode.rb +59 -0
  131. data/test/hexapdf/filter/test_ascii_hex_decode.rb +38 -0
  132. data/test/hexapdf/filter/test_crypt.rb +21 -0
  133. data/test/hexapdf/filter/test_encryption.rb +24 -0
  134. data/test/hexapdf/filter/test_flate_decode.rb +44 -0
  135. data/test/hexapdf/filter/test_lzw_decode.rb +52 -0
  136. data/test/hexapdf/filter/test_predictor.rb +219 -0
  137. data/test/hexapdf/filter/test_run_length_decode.rb +32 -0
  138. data/test/hexapdf/font/cmap/test_parser.rb +102 -0
  139. data/test/hexapdf/font/cmap/test_writer.rb +66 -0
  140. data/test/hexapdf/font/encoding/test_base.rb +45 -0
  141. data/test/hexapdf/font/encoding/test_difference_encoding.rb +29 -0
  142. data/test/hexapdf/font/encoding/test_glyph_list.rb +59 -0
  143. data/test/hexapdf/font/encoding/test_zapf_dingbats_encoding.rb +16 -0
  144. data/test/hexapdf/font/test_cmap.rb +104 -0
  145. data/test/hexapdf/font/test_encoding.rb +27 -0
  146. data/test/hexapdf/font/test_invalid_glyph.rb +34 -0
  147. data/test/hexapdf/font/test_true_type_wrapper.rb +186 -0
  148. data/test/hexapdf/font/test_type1_wrapper.rb +107 -0
  149. data/test/hexapdf/font/true_type/common.rb +17 -0
  150. data/test/hexapdf/font/true_type/table/common.rb +27 -0
  151. data/test/hexapdf/font/true_type/table/test_cmap.rb +47 -0
  152. data/test/hexapdf/font/true_type/table/test_cmap_subtable.rb +141 -0
  153. data/test/hexapdf/font/true_type/table/test_directory.rb +30 -0
  154. data/test/hexapdf/font/true_type/table/test_glyf.rb +58 -0
  155. data/test/hexapdf/font/true_type/table/test_head.rb +56 -0
  156. data/test/hexapdf/font/true_type/table/test_hhea.rb +26 -0
  157. data/test/hexapdf/font/true_type/table/test_hmtx.rb +30 -0
  158. data/test/hexapdf/font/true_type/table/test_kern.rb +61 -0
  159. data/test/hexapdf/font/true_type/table/test_loca.rb +33 -0
  160. data/test/hexapdf/font/true_type/table/test_maxp.rb +50 -0
  161. data/test/hexapdf/font/true_type/table/test_name.rb +76 -0
  162. data/test/hexapdf/font/true_type/table/test_os2.rb +55 -0
  163. data/test/hexapdf/font/true_type/table/test_post.rb +78 -0
  164. data/test/hexapdf/font/true_type/test_builder.rb +42 -0
  165. data/test/hexapdf/font/true_type/test_font.rb +116 -0
  166. data/test/hexapdf/font/true_type/test_optimizer.rb +26 -0
  167. data/test/hexapdf/font/true_type/test_subsetter.rb +73 -0
  168. data/test/hexapdf/font/true_type/test_table.rb +48 -0
  169. data/test/hexapdf/font/type1/common.rb +6 -0
  170. data/test/hexapdf/font/type1/test_afm_parser.rb +65 -0
  171. data/test/hexapdf/font/type1/test_font.rb +104 -0
  172. data/test/hexapdf/font/type1/test_font_metrics.rb +22 -0
  173. data/test/hexapdf/font/type1/test_pfb_parser.rb +37 -0
  174. data/test/hexapdf/font_loader/test_from_configuration.rb +43 -0
  175. data/test/hexapdf/font_loader/test_from_file.rb +36 -0
  176. data/test/hexapdf/font_loader/test_standard14.rb +33 -0
  177. data/test/hexapdf/image_loader/test_jpeg.rb +93 -0
  178. data/test/hexapdf/image_loader/test_pdf.rb +47 -0
  179. data/test/hexapdf/image_loader/test_png.rb +259 -0
  180. data/test/hexapdf/layout/test_box.rb +154 -0
  181. data/test/hexapdf/layout/test_frame.rb +350 -0
  182. data/test/hexapdf/layout/test_image_box.rb +73 -0
  183. data/test/hexapdf/layout/test_inline_box.rb +71 -0
  184. data/test/hexapdf/layout/test_line.rb +206 -0
  185. data/test/hexapdf/layout/test_style.rb +790 -0
  186. data/test/hexapdf/layout/test_text_box.rb +140 -0
  187. data/test/hexapdf/layout/test_text_fragment.rb +375 -0
  188. data/test/hexapdf/layout/test_text_layouter.rb +758 -0
  189. data/test/hexapdf/layout/test_text_shaper.rb +62 -0
  190. data/test/hexapdf/layout/test_width_from_polygon.rb +109 -0
  191. data/test/hexapdf/task/test_dereference.rb +51 -0
  192. data/test/hexapdf/task/test_optimize.rb +162 -0
  193. data/test/hexapdf/test_composer.rb +258 -0
  194. data/test/hexapdf/test_configuration.rb +93 -0
  195. data/test/hexapdf/test_data_dir.rb +32 -0
  196. data/test/hexapdf/test_dictionary.rb +340 -0
  197. data/test/hexapdf/test_dictionary_fields.rb +269 -0
  198. data/test/hexapdf/test_document.rb +641 -0
  199. data/test/hexapdf/test_filter.rb +100 -0
  200. data/test/hexapdf/test_importer.rb +106 -0
  201. data/test/hexapdf/test_object.rb +258 -0
  202. data/test/hexapdf/test_parser.rb +645 -0
  203. data/test/hexapdf/test_pdf_array.rb +169 -0
  204. data/test/hexapdf/test_rectangle.rb +73 -0
  205. data/test/hexapdf/test_reference.rb +50 -0
  206. data/test/hexapdf/test_revision.rb +188 -0
  207. data/test/hexapdf/test_revisions.rb +196 -0
  208. data/test/hexapdf/test_serializer.rb +195 -0
  209. data/test/hexapdf/test_stream.rb +274 -0
  210. data/test/hexapdf/test_tokenizer.rb +80 -0
  211. data/test/hexapdf/test_type.rb +18 -0
  212. data/test/hexapdf/test_writer.rb +140 -0
  213. data/test/hexapdf/test_xref_section.rb +61 -0
  214. data/test/hexapdf/type/acro_form/test_appearance_generator.rb +795 -0
  215. data/test/hexapdf/type/acro_form/test_button_field.rb +308 -0
  216. data/test/hexapdf/type/acro_form/test_choice_field.rb +220 -0
  217. data/test/hexapdf/type/acro_form/test_field.rb +259 -0
  218. data/test/hexapdf/type/acro_form/test_form.rb +357 -0
  219. data/test/hexapdf/type/acro_form/test_signature_field.rb +38 -0
  220. data/test/hexapdf/type/acro_form/test_text_field.rb +201 -0
  221. data/test/hexapdf/type/acro_form/test_variable_text_field.rb +88 -0
  222. data/test/hexapdf/type/actions/test_launch.rb +24 -0
  223. data/test/hexapdf/type/actions/test_uri.rb +23 -0
  224. data/test/hexapdf/type/annotations/test_markup_annotation.rb +22 -0
  225. data/test/hexapdf/type/annotations/test_text.rb +34 -0
  226. data/test/hexapdf/type/annotations/test_widget.rb +225 -0
  227. data/test/hexapdf/type/test_annotation.rb +97 -0
  228. data/test/hexapdf/type/test_catalog.rb +48 -0
  229. data/test/hexapdf/type/test_cid_font.rb +61 -0
  230. data/test/hexapdf/type/test_file_specification.rb +141 -0
  231. data/test/hexapdf/type/test_font.rb +67 -0
  232. data/test/hexapdf/type/test_font_descriptor.rb +61 -0
  233. data/test/hexapdf/type/test_font_simple.rb +176 -0
  234. data/test/hexapdf/type/test_font_true_type.rb +31 -0
  235. data/test/hexapdf/type/test_font_type0.rb +120 -0
  236. data/test/hexapdf/type/test_font_type1.rb +142 -0
  237. data/test/hexapdf/type/test_font_type3.rb +26 -0
  238. data/test/hexapdf/type/test_form.rb +120 -0
  239. data/test/hexapdf/type/test_image.rb +261 -0
  240. data/test/hexapdf/type/test_info.rb +9 -0
  241. data/test/hexapdf/type/test_object_stream.rb +117 -0
  242. data/test/hexapdf/type/test_page.rb +598 -0
  243. data/test/hexapdf/type/test_page_tree_node.rb +315 -0
  244. data/test/hexapdf/type/test_resources.rb +209 -0
  245. data/test/hexapdf/type/test_trailer.rb +116 -0
  246. data/test/hexapdf/type/test_xref_stream.rb +143 -0
  247. data/test/hexapdf/utils/test_bit_field.rb +63 -0
  248. data/test/hexapdf/utils/test_bit_stream.rb +69 -0
  249. data/test/hexapdf/utils/test_graphics_helpers.rb +37 -0
  250. data/test/hexapdf/utils/test_lru_cache.rb +22 -0
  251. data/test/hexapdf/utils/test_object_hash.rb +120 -0
  252. data/test/hexapdf/utils/test_pdf_doc_encoding.rb +18 -0
  253. data/test/hexapdf/utils/test_sorted_tree_node.rb +239 -0
  254. data/test/test_helper.rb +58 -0
  255. metadata +263 -3
@@ -0,0 +1,140 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ require 'test_helper'
4
+ require_relative '../content/common'
5
+ require 'hexapdf/document'
6
+ require 'hexapdf/layout/text_box'
7
+
8
+ describe HexaPDF::Layout::TextBox do
9
+ before do
10
+ @frame = HexaPDF::Layout::Frame.new(0, 0, 100, 100)
11
+ @inline_box = HexaPDF::Layout::InlineBox.create(width: 10, height: 10) {}
12
+ end
13
+
14
+ def create_box(items, **kwargs)
15
+ HexaPDF::Layout::TextBox.new(items, **kwargs)
16
+ end
17
+
18
+ describe "initialize" do
19
+ it "takes the inline items to be layed out in the box" do
20
+ box = create_box([], width: 100)
21
+ assert_equal(100, box.width)
22
+ end
23
+ end
24
+
25
+ describe "fit" do
26
+ it "fits into a rectangular area" do
27
+ box = create_box([@inline_box] * 5, style: {padding: 10})
28
+ assert(box.fit(100, 100, @frame))
29
+ assert_equal(70, box.width)
30
+ assert_equal(30, box.height)
31
+ end
32
+
33
+ it "respects the set width and height" do
34
+ box = create_box([@inline_box], width: 40, height: 50, style: {padding: 10})
35
+ assert(box.fit(100, 100, @frame))
36
+ assert_equal(40, box.width)
37
+ assert_equal(50, box.height)
38
+ assert_equal([10], box.instance_variable_get(:@result).lines.map(&:width))
39
+ end
40
+
41
+ it "fits into the frame's outline" do
42
+ inline_box = HexaPDF::Layout::InlineBox.create(width: 10, height: 10) {}
43
+ box = create_box([inline_box] * 20, style: {position: :flow})
44
+ assert(box.fit(100, 100, @frame))
45
+ assert_equal(100, box.width)
46
+ assert_equal(20, box.height)
47
+ end
48
+
49
+ it "takes the style option last_line_gap into account" do
50
+ box = create_box([@inline_box] * 5, style: {last_line_gap: true, line_spacing: :double})
51
+ assert(box.fit(100, 100, @frame))
52
+ assert_equal(50, box.width)
53
+ assert_equal(20, box.height)
54
+ end
55
+
56
+ it "can't fit the text box if the set width is bigger than the available width" do
57
+ box = create_box([@inline_box], width: 101)
58
+ refute(box.fit(100, 100, @frame))
59
+ end
60
+
61
+ it "can't fit the text box if the set height is bigger than the available height" do
62
+ box = create_box([@inline_box], height: 101)
63
+ refute(box.fit(100, 100, @frame))
64
+ end
65
+ end
66
+
67
+ describe "split" do
68
+ it "works for an empty text box" do
69
+ box = create_box([])
70
+ assert_equal([box], box.split(100, 100, @frame))
71
+ end
72
+
73
+ it "doesn't need to split the box if it completely fits" do
74
+ box = create_box([@inline_box] * 5)
75
+ assert_equal([box], box.split(50, 100, @frame))
76
+ end
77
+
78
+ it "works if no item of the text box fits" do
79
+ box = create_box([@inline_box])
80
+ assert_equal([nil, box], box.split(5, 20, @frame))
81
+ end
82
+
83
+ it "works if the whole text box doesn't fits" do
84
+ box = create_box([@inline_box], width: 102)
85
+ assert_equal([nil, box], box.split(100, 100, @frame))
86
+
87
+ box = create_box([@inline_box], height: 102)
88
+ assert_equal([nil, box], box.split(100, 100, @frame))
89
+ end
90
+
91
+ it "splits the box if necessary" do
92
+ box = create_box([@inline_box] * 10)
93
+ boxes = box.split(50, 10, @frame)
94
+ assert_equal(2, boxes.length)
95
+ assert_equal(box, boxes[0])
96
+ assert_equal(5, boxes[1].instance_variable_get(:@items).length)
97
+ end
98
+ end
99
+
100
+ describe "draw" do
101
+ it "draws the layed out inline items onto the canvas" do
102
+ inline_box = HexaPDF::Layout::InlineBox.create(width: 10, height: 10,
103
+ border: {width: 1})
104
+ box = create_box([inline_box], width: 100, height: 30, style: {padding: [10, 5]})
105
+ box.fit(100, 100, nil)
106
+
107
+ @canvas = HexaPDF::Document.new.pages.add.canvas
108
+ box.draw(@canvas, 0, 0)
109
+ assert_operators(@canvas.contents, [[:save_graphics_state],
110
+ [:restore_graphics_state],
111
+ [:save_graphics_state],
112
+ [:append_rectangle, [5, 10, 10, 10]],
113
+ [:clip_path_non_zero],
114
+ [:end_path],
115
+ [:append_rectangle, [5.5, 10.5, 9, 9]],
116
+ [:stroke_path],
117
+ [:restore_graphics_state],
118
+ [:save_graphics_state],
119
+ [:restore_graphics_state]])
120
+ end
121
+
122
+ it "draws nothing onto the canvas if the box is empty" do
123
+ @canvas = HexaPDF::Document.new.pages.add.canvas
124
+ box = create_box([])
125
+ box.draw(@canvas, 5, 5)
126
+ assert_operators(@canvas.contents, [])
127
+ end
128
+ end
129
+
130
+ it "is empty if there is a result without any text lines" do
131
+ box = create_box([])
132
+ assert(box.empty?)
133
+ box.fit(100, 100, @frame)
134
+ assert(box.empty?)
135
+
136
+ box = create_box([@inline_box])
137
+ box.fit(100, 100, @frame)
138
+ refute(box.empty?)
139
+ end
140
+ end
@@ -0,0 +1,375 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ require 'test_helper'
4
+ require_relative '../content/common'
5
+ require 'hexapdf/document'
6
+ require 'hexapdf/layout/text_fragment'
7
+
8
+ # Numeric values were manually calculated using the information from the AFM file.
9
+ describe HexaPDF::Layout::TextFragment do
10
+ before do
11
+ @doc = HexaPDF::Document.new
12
+ @font = @doc.fonts.add("Times", custom_encoding: true)
13
+ end
14
+
15
+ def setup_fragment(items, text_rise = 0)
16
+ style = HexaPDF::Layout::Style.new(font: @font, font_size: 20,
17
+ horizontal_scaling: 200, character_spacing: 1,
18
+ word_spacing: 2, text_rise: text_rise)
19
+ @fragment = HexaPDF::Layout::TextFragment.new(items, style)
20
+ end
21
+
22
+ describe "create" do
23
+ it "creates a TextFragment from text and options" do
24
+ frag = HexaPDF::Layout::TextFragment.create("Tom", font: @font, font_size: 20,
25
+ font_features: {kern: true})
26
+ assert_equal(4, frag.items.length)
27
+ assert_equal(36.18, frag.width)
28
+ assert_equal(13.66 + 4.34, frag.height)
29
+ end
30
+
31
+ it "creates a TextFragment from text and a Style object" do
32
+ style = HexaPDF::Layout::Style.new(font: @font)
33
+ frag = HexaPDF::Layout::TextFragment.create("Tom", style)
34
+ assert_equal(style, frag.style)
35
+ end
36
+ end
37
+
38
+ describe "initialize" do
39
+ before do
40
+ @items = @font.decode_utf8("Tom")
41
+ end
42
+
43
+ it "can use a Style object" do
44
+ style = HexaPDF::Layout::Style.new(font: @font, font_size: 20)
45
+ frag = HexaPDF::Layout::TextFragment.new(@items, style)
46
+ assert_equal(20, frag.style.font_size)
47
+ end
48
+
49
+ it "can use style options" do
50
+ frag = HexaPDF::Layout::TextFragment.new(@items, font: @font, font_size: 20)
51
+ assert_equal(20, frag.style.font_size)
52
+ end
53
+ end
54
+
55
+ it "returns :text for valign" do
56
+ assert_equal(:text, setup_fragment([]).valign)
57
+ end
58
+
59
+ describe "draw" do
60
+ def setup_with_style(**styles)
61
+ setup_fragment(@font.decode_utf8('H'), 2)
62
+ styles.each {|name, value| @fragment.style.send(name, value) }
63
+ @canvas = @doc.pages.add.canvas
64
+ @fragment.draw(@canvas, 10, 15)
65
+ end
66
+
67
+ def assert_draw_operators(*args, front: [], middle: args, back: [])
68
+ ops = [
69
+ *front,
70
+ [:set_font_and_size, [:F1, 20]],
71
+ [:set_horizontal_scaling, [200]],
72
+ [:set_character_spacing, [1]],
73
+ [:set_word_spacing, [2]],
74
+ [:set_text_rise, [2]],
75
+ *middle,
76
+ [:begin_text],
77
+ [:set_text_matrix, [1, 0, 0, 1, 10, 15]],
78
+ [:show_text, ['!']],
79
+ *back,
80
+ ].compact
81
+ assert_operators(@canvas.contents, ops)
82
+ end
83
+
84
+ it "draws text onto the canvas" do
85
+ setup_with_style
86
+ assert_draw_operators
87
+ end
88
+
89
+ it "doesn't set the text properties if instructed to do so" do
90
+ setup_fragment([])
91
+ @canvas = @doc.pages.add.canvas
92
+ @fragment.draw(@canvas, 10, 15, ignore_text_properties: true)
93
+ assert_operators(@canvas.contents, [[:begin_text],
94
+ [:set_text_matrix, [1, 0, 0, 1, 10, 15]]])
95
+ end
96
+
97
+ describe "uses an appropriate text position setter" do
98
+ before do
99
+ setup_fragment([])
100
+ @canvas = @doc.pages.add.canvas
101
+ end
102
+
103
+ it "with text leading graphics state" do
104
+ @canvas.begin_text.leading(10)
105
+ @fragment.draw(@canvas, 0, -10, ignore_text_properties: true)
106
+ assert_operators(@canvas.contents, [[:begin_text],
107
+ [:set_leading, [10]],
108
+ [:move_text_next_line]])
109
+ end
110
+
111
+ it "only horizontal movement" do
112
+ @fragment.draw(@canvas, 20, 0, ignore_text_properties: true)
113
+ assert_operators(@canvas.contents, [[:begin_text],
114
+ [:move_text, [20, 0]]])
115
+ end
116
+
117
+ it "only vertical movement" do
118
+ @fragment.draw(@canvas, 0, 10, ignore_text_properties: true)
119
+ assert_operators(@canvas.contents, [[:begin_text],
120
+ [:move_text, [0, 10]]])
121
+ end
122
+
123
+ it "horizontal and vertical movement" do
124
+ @fragment.draw(@canvas, 10, 10, ignore_text_properties: true)
125
+ assert_operators(@canvas.contents, [[:begin_text],
126
+ [:set_text_matrix, [1, 0, 0, 1, 10, 10]]])
127
+ end
128
+ end
129
+
130
+ it "draws styled filled text" do
131
+ setup_with_style(fill_color: 0.5, fill_alpha: 0.5)
132
+ assert_draw_operators([:set_graphics_state_parameters, [:GS1]],
133
+ [:set_device_gray_non_stroking_color, [0.5]])
134
+ assert_equal({Type: :ExtGState, CA: 1, ca: 0.5}, @canvas.resources[:ExtGState][:GS1])
135
+ end
136
+
137
+ it "draws style stroked text" do
138
+ setup_with_style(text_rendering_mode: :stroke,
139
+ stroke_color: [1.0, 0, 0], stroke_alpha: 0.5, stroke_width: 2,
140
+ stroke_cap_style: :round, stroke_join_style: :round, stroke_miter_limit: 5,
141
+ stroke_dash_pattern: [1, 2, 3])
142
+ assert_draw_operators([:set_text_rendering_mode, [1]],
143
+ [:set_graphics_state_parameters, [:GS1]],
144
+ [:set_device_rgb_stroking_color, [1, 0, 0]],
145
+ [:set_line_width, [2]],
146
+ [:set_line_cap_style, [1]],
147
+ [:set_line_join_style, [1]],
148
+ [:set_miter_limit, [5]],
149
+ [:set_line_dash_pattern, [[1, 2, 3], 0]])
150
+ assert_equal({Type: :ExtGState, CA: 0.5, ca: 1}, @canvas.resources[:ExtGState][:GS1])
151
+ end
152
+
153
+ it "invokes the underlays" do
154
+ setup_with_style(underlays: [proc { @canvas.stroke_color(0.5) }])
155
+ assert_draw_operators(front: [[:save_graphics_state],
156
+ [:concatenate_matrix, [1, 0, 0, 1, 10, 15 + @fragment.y_min]],
157
+ [:save_graphics_state],
158
+ [:set_device_gray_stroking_color, [0.5]],
159
+ [:restore_graphics_state],
160
+ [:restore_graphics_state]])
161
+ end
162
+
163
+ it "invokes the overlays" do
164
+ setup_with_style(overlays: [proc { @canvas.stroke_color(0.5) }])
165
+ assert_draw_operators(back: [[:end_text],
166
+ [:save_graphics_state],
167
+ [:concatenate_matrix, [1, 0, 0, 1, 10, 15 + @fragment.y_min]],
168
+ [:save_graphics_state],
169
+ [:set_device_gray_stroking_color, [0.5]],
170
+ [:restore_graphics_state],
171
+ [:restore_graphics_state]])
172
+ end
173
+
174
+ it "draws the underline" do
175
+ setup_with_style(underline: true, text_rendering_mode: :stroke,
176
+ stroke_width: 5, stroke_color: [0.5], stroke_cap_style: :round,
177
+ stroke_dash_pattern: 5)
178
+ assert_draw_operators(middle: [[:set_text_rendering_mode, [1]],
179
+ [:set_device_gray_stroking_color, [0.5]],
180
+ [:set_line_width, [5]],
181
+ [:set_line_cap_style, [1]],
182
+ [:set_line_dash_pattern, [[5], 0]]],
183
+ back: [[:end_text],
184
+ [:save_graphics_state],
185
+ [:set_device_gray_stroking_color, [0]],
186
+ [:set_line_width, [@fragment.style.calculated_underline_thickness]],
187
+ [:set_line_cap_style, [0]],
188
+ [:set_line_dash_pattern, [[], 0]],
189
+ [:move_to, [10, 15]],
190
+ [:line_to, [40.88, 15]],
191
+ [:stroke_path],
192
+ [:restore_graphics_state]])
193
+ end
194
+
195
+ it "draws the strikeout line" do
196
+ setup_with_style(strikeout: true, text_rendering_mode: :stroke,
197
+ stroke_width: 5, stroke_color: [0.5], stroke_cap_style: :round,
198
+ stroke_dash_pattern: 5)
199
+ assert_draw_operators(middle: [[:set_text_rendering_mode, [1]],
200
+ [:set_device_gray_stroking_color, [0.5]],
201
+ [:set_line_width, [5]],
202
+ [:set_line_cap_style, [1]],
203
+ [:set_line_dash_pattern, [[5], 0]]],
204
+ back: [[:end_text],
205
+ [:save_graphics_state],
206
+ [:set_device_gray_stroking_color, [0]],
207
+ [:set_line_width, [@fragment.style.calculated_strikeout_thickness]],
208
+ [:set_line_cap_style, [0]],
209
+ [:set_line_dash_pattern, [[], 0]],
210
+ [:move_to, [10, 21.01]],
211
+ [:line_to, [40.88, 21.01]],
212
+ [:stroke_path],
213
+ [:restore_graphics_state]])
214
+ end
215
+ end
216
+
217
+ describe "empty fragment" do
218
+ before do
219
+ setup_fragment([])
220
+ end
221
+
222
+ it "calculates the x_min" do
223
+ assert_equal(0, @fragment.x_min)
224
+ end
225
+
226
+ it "calculates the x_max" do
227
+ assert_equal(0, @fragment.x_max)
228
+ end
229
+
230
+ it "calculates the y_min" do
231
+ assert_equal(-4.34, @fragment.y_min)
232
+ end
233
+
234
+ it "calculates the y_max" do
235
+ assert_equal(13.66, @fragment.y_max)
236
+ end
237
+
238
+ it "calculates the width" do
239
+ assert_equal(0, @fragment.width)
240
+ end
241
+
242
+ it "calculates the height" do
243
+ assert_equal(13.66 + 4.34, @fragment.height)
244
+ end
245
+ end
246
+
247
+ describe "normal text" do
248
+ before do
249
+ setup_fragment(@font.decode_utf8("Hal lo").insert(2, -35).insert(1, -10))
250
+ end
251
+
252
+ it "calculates the x_min" do
253
+ assert_in_delta(0.76, @fragment.x_min)
254
+ end
255
+
256
+ it "calculates the x_max" do
257
+ assert_in_delta(116.68 - 1.2 - 2, @fragment.x_max)
258
+ end
259
+
260
+ it "calculates the exact y_min" do
261
+ assert_in_delta(-0.2, @fragment.exact_y_min)
262
+ end
263
+
264
+ it "calculates the exact y_max" do
265
+ assert_in_delta(13.66, @fragment.exact_y_max)
266
+ end
267
+
268
+ it "calculates the y_min" do
269
+ assert_in_delta(-4.34, @fragment.y_min)
270
+ end
271
+
272
+ it "calculates the y_max" do
273
+ assert_in_delta(13.66, @fragment.y_max)
274
+ end
275
+
276
+ it "calculates the width" do
277
+ assert_in_delta(116.68, @fragment.width)
278
+ end
279
+
280
+ it "calculates the height" do
281
+ assert_in_delta(13.66 + 4.34, @fragment.height)
282
+ end
283
+ end
284
+
285
+ describe "with a positive text rise" do
286
+ before do
287
+ setup_fragment(@font.decode_utf8("l,"), 4)
288
+ end
289
+
290
+ it "calculates the y_min" do
291
+ assert_in_delta(-4.34 + 4, @fragment.y_min)
292
+ end
293
+
294
+ it "calculates the y_max" do
295
+ assert_in_delta(13.66 + 4, @fragment.y_max)
296
+ end
297
+
298
+ it "calculates the height" do
299
+ assert_in_delta(13.66 + 4 + 0.34, @fragment.height)
300
+ end
301
+ end
302
+
303
+ describe "with a negative text rise" do
304
+ before do
305
+ setup_fragment(@font.decode_utf8("l,"), -15)
306
+ end
307
+
308
+ it "calculates the y_min" do
309
+ assert_in_delta(-4.34 - 15, @fragment.y_min)
310
+ end
311
+
312
+ it "calculates the y_max" do
313
+ assert_in_delta(13.66 - 15, @fragment.y_max)
314
+ end
315
+
316
+ it "calculates the height" do
317
+ assert_in_delta(4.34 + 15, @fragment.height)
318
+ end
319
+ end
320
+
321
+ describe "with a glyph without outline as last item" do
322
+ before do
323
+ setup_fragment(@font.decode_utf8("H "))
324
+ end
325
+
326
+ it "calculates the x_max" do
327
+ assert_in_delta(46.88 - 2 - 4, @fragment.x_max)
328
+ end
329
+
330
+ it "calculates the width" do
331
+ assert_in_delta(46.88, @fragment.width)
332
+ end
333
+ end
334
+
335
+ describe "with a glyph with x_min < 0 and x_max > width as first and last item" do
336
+ before do
337
+ setup_fragment(@font.decode_utf8("\u{2044}o\u{2044}".unicode_normalize(:nfd)))
338
+ end
339
+
340
+ it "calculates the x_min" do
341
+ assert_in_delta(-6.72, @fragment.x_min)
342
+ end
343
+
344
+ it "calculates the x_max" do
345
+ assert_in_delta(39.36 + 6.56 - 2, @fragment.x_max)
346
+ end
347
+
348
+ it "calculates the width" do
349
+ assert_in_delta(39.36, @fragment.width)
350
+ end
351
+ end
352
+
353
+ describe "with positive kerning values as first and last items" do
354
+ before do
355
+ setup_fragment([100, 100] + @font.decode_utf8("Hallo") + [100, 100])
356
+ end
357
+
358
+ it "calculates the x_min" do
359
+ assert_in_delta(-7.24, @fragment.x_min)
360
+ end
361
+
362
+ it "calculates the x_max" do
363
+ assert_in_delta(82.88 - 1.2 - 2 - -4 - -4, @fragment.x_max)
364
+ end
365
+
366
+ it "calculates the width" do
367
+ assert_in_delta(82.88, @fragment.width)
368
+ end
369
+ end
370
+
371
+ it "can be inspected" do
372
+ frag = setup_fragment(@font.decode_utf8("H"))
373
+ assert_match(/:H/, frag.inspect)
374
+ end
375
+ end