prawn 2.1.0 → 2.4.0

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 (280) hide show
  1. checksums.yaml +5 -5
  2. checksums.yaml.gz.sig +0 -0
  3. data.tar.gz.sig +0 -0
  4. data/GPLv2 +20 -21
  5. data/Gemfile +3 -9
  6. data/Rakefile +9 -41
  7. data/lib/prawn.rb +37 -49
  8. data/lib/prawn/document.rb +193 -141
  9. data/lib/prawn/document/bounding_box.rb +50 -30
  10. data/lib/prawn/document/column_box.rb +7 -7
  11. data/lib/prawn/document/internals.rb +8 -6
  12. data/lib/prawn/document/span.rb +22 -16
  13. data/lib/prawn/encoding.rb +69 -68
  14. data/lib/prawn/errors.rb +12 -7
  15. data/lib/prawn/font.rb +104 -69
  16. data/lib/prawn/font_metric_cache.rb +20 -13
  17. data/lib/prawn/{font → fonts}/afm.rb +108 -72
  18. data/lib/prawn/{font → fonts}/dfont.rb +5 -11
  19. data/lib/prawn/fonts/otf.rb +11 -0
  20. data/lib/prawn/fonts/ttc.rb +36 -0
  21. data/lib/prawn/{font → fonts}/ttf.rb +126 -81
  22. data/lib/prawn/graphics.rb +119 -81
  23. data/lib/prawn/graphics/blend_mode.rb +9 -8
  24. data/lib/prawn/graphics/cap_style.rb +3 -3
  25. data/lib/prawn/graphics/color.rb +43 -39
  26. data/lib/prawn/graphics/dash.rb +23 -11
  27. data/lib/prawn/graphics/join_style.rb +9 -3
  28. data/lib/prawn/graphics/patterns.rb +204 -102
  29. data/lib/prawn/graphics/transformation.rb +15 -9
  30. data/lib/prawn/graphics/transparency.rb +17 -13
  31. data/lib/prawn/grid.rb +84 -48
  32. data/lib/prawn/image_handler.rb +5 -5
  33. data/lib/prawn/images.rb +60 -49
  34. data/lib/prawn/images/image.rb +2 -1
  35. data/lib/prawn/images/jpg.rb +31 -22
  36. data/lib/prawn/images/png.rb +67 -63
  37. data/lib/prawn/measurement_extensions.rb +10 -9
  38. data/lib/prawn/measurements.rb +19 -15
  39. data/lib/prawn/outline.rb +98 -77
  40. data/lib/prawn/repeater.rb +15 -11
  41. data/lib/prawn/security.rb +93 -70
  42. data/lib/prawn/security/arcfour.rb +2 -2
  43. data/lib/prawn/soft_mask.rb +26 -26
  44. data/lib/prawn/stamp.rb +20 -13
  45. data/lib/prawn/text.rb +76 -60
  46. data/lib/prawn/text/box.rb +18 -14
  47. data/lib/prawn/text/formatted.rb +5 -5
  48. data/lib/prawn/text/formatted/arranger.rb +80 -40
  49. data/lib/prawn/text/formatted/box.rb +140 -101
  50. data/lib/prawn/text/formatted/fragment.rb +11 -14
  51. data/lib/prawn/text/formatted/line_wrap.rb +128 -67
  52. data/lib/prawn/text/formatted/parser.rb +147 -123
  53. data/lib/prawn/text/formatted/wrap.rb +48 -32
  54. data/lib/prawn/transformation_stack.rb +7 -5
  55. data/lib/prawn/utilities.rb +7 -22
  56. data/lib/prawn/version.rb +2 -2
  57. data/lib/prawn/view.rb +17 -7
  58. data/manual/basic_concepts/adding_pages.rb +6 -7
  59. data/manual/basic_concepts/basic_concepts.rb +31 -22
  60. data/manual/basic_concepts/creation.rb +10 -11
  61. data/manual/basic_concepts/cursor.rb +4 -5
  62. data/manual/basic_concepts/measurement.rb +7 -8
  63. data/manual/basic_concepts/origin.rb +5 -6
  64. data/manual/basic_concepts/other_cursor_helpers.rb +11 -12
  65. data/manual/basic_concepts/view.rb +22 -16
  66. data/manual/bounding_box/bounding_box.rb +29 -24
  67. data/manual/bounding_box/bounds.rb +11 -12
  68. data/manual/bounding_box/canvas.rb +7 -8
  69. data/manual/bounding_box/creation.rb +6 -7
  70. data/manual/bounding_box/indentation.rb +14 -15
  71. data/manual/bounding_box/nesting.rb +25 -18
  72. data/manual/bounding_box/russian_boxes.rb +14 -13
  73. data/manual/bounding_box/stretchy.rb +12 -13
  74. data/manual/contents.rb +28 -22
  75. data/manual/cover.rb +33 -28
  76. data/manual/document_and_page_options/background.rb +15 -13
  77. data/manual/document_and_page_options/document_and_page_options.rb +25 -20
  78. data/manual/document_and_page_options/metadata.rb +18 -16
  79. data/manual/document_and_page_options/page_margins.rb +18 -20
  80. data/manual/document_and_page_options/page_size.rb +13 -12
  81. data/manual/document_and_page_options/print_scaling.rb +18 -15
  82. data/manual/example_helper.rb +5 -4
  83. data/manual/graphics/blend_mode.rb +12 -9
  84. data/manual/graphics/circle_and_ellipse.rb +4 -5
  85. data/manual/graphics/color.rb +7 -9
  86. data/manual/graphics/common_lines.rb +7 -8
  87. data/manual/graphics/fill_and_stroke.rb +5 -6
  88. data/manual/graphics/fill_rules.rb +10 -10
  89. data/manual/graphics/gradients.rb +27 -21
  90. data/manual/graphics/graphics.rb +48 -40
  91. data/manual/graphics/helper.rb +19 -9
  92. data/manual/graphics/line_width.rb +8 -7
  93. data/manual/graphics/lines_and_curves.rb +7 -8
  94. data/manual/graphics/polygon.rb +6 -8
  95. data/manual/graphics/rectangle.rb +4 -5
  96. data/manual/graphics/rotate.rb +6 -7
  97. data/manual/graphics/scale.rb +14 -15
  98. data/manual/graphics/soft_masks.rb +3 -4
  99. data/manual/graphics/stroke_cap.rb +6 -7
  100. data/manual/graphics/stroke_dash.rb +15 -16
  101. data/manual/graphics/stroke_join.rb +5 -6
  102. data/manual/graphics/translate.rb +10 -10
  103. data/manual/graphics/transparency.rb +7 -8
  104. data/manual/how_to_read_this_manual.rb +6 -6
  105. data/manual/images/absolute_position.rb +6 -7
  106. data/manual/images/fit.rb +7 -8
  107. data/manual/images/horizontal.rb +10 -11
  108. data/manual/images/images.rb +28 -24
  109. data/manual/images/plain_image.rb +5 -6
  110. data/manual/images/scale.rb +9 -10
  111. data/manual/images/vertical.rb +16 -14
  112. data/manual/images/width_and_height.rb +10 -11
  113. data/manual/layout/boxes.rb +5 -6
  114. data/manual/layout/content.rb +7 -8
  115. data/manual/layout/layout.rb +18 -16
  116. data/manual/layout/simple_grid.rb +6 -7
  117. data/manual/outline/add_subsection_to.rb +20 -21
  118. data/manual/outline/insert_section_after.rb +15 -16
  119. data/manual/outline/outline.rb +21 -17
  120. data/manual/outline/sections_and_pages.rb +17 -18
  121. data/manual/repeatable_content/alternate_page_numbering.rb +21 -17
  122. data/manual/repeatable_content/page_numbering.rb +17 -16
  123. data/manual/repeatable_content/repeatable_content.rb +25 -19
  124. data/manual/repeatable_content/repeater.rb +14 -15
  125. data/manual/repeatable_content/stamp.rb +14 -15
  126. data/manual/security/encryption.rb +9 -10
  127. data/manual/security/permissions.rb +21 -14
  128. data/manual/security/security.rb +19 -16
  129. data/manual/table.rb +3 -3
  130. data/manual/text/alignment.rb +16 -17
  131. data/manual/text/color.rb +12 -11
  132. data/manual/text/column_box.rb +9 -10
  133. data/manual/text/fallback_fonts.rb +25 -21
  134. data/manual/text/font.rb +11 -12
  135. data/manual/text/font_size.rb +13 -14
  136. data/manual/text/font_style.rb +10 -8
  137. data/manual/text/formatted_callbacks.rb +33 -24
  138. data/manual/text/formatted_text.rb +36 -25
  139. data/manual/text/free_flowing_text.rb +22 -23
  140. data/manual/text/inline.rb +18 -19
  141. data/manual/text/kerning_and_character_spacing.rb +14 -15
  142. data/manual/text/leading.rb +7 -8
  143. data/manual/text/line_wrapping.rb +37 -18
  144. data/manual/text/paragraph_indentation.rb +12 -14
  145. data/manual/text/positioned_text.rb +15 -16
  146. data/manual/text/registering_families.rb +20 -21
  147. data/manual/text/rendering_and_color.rb +9 -10
  148. data/manual/text/right_to_left_text.rb +26 -19
  149. data/manual/text/rotation.rb +33 -23
  150. data/manual/text/single_usage.rb +8 -9
  151. data/manual/text/text.rb +57 -52
  152. data/manual/text/text_box_excess.rb +20 -17
  153. data/manual/text/text_box_extensions.rb +18 -15
  154. data/manual/text/text_box_overflow.rb +20 -19
  155. data/manual/text/utf8.rb +11 -12
  156. data/manual/text/win_ansi_charset.rb +27 -25
  157. data/prawn.gemspec +41 -34
  158. data/spec/extensions/encoding_helpers.rb +3 -3
  159. data/spec/prawn/document/bounding_box_spec.rb +550 -0
  160. data/spec/prawn/document/column_box_spec.rb +75 -0
  161. data/spec/prawn/document/security_spec.rb +176 -0
  162. data/spec/prawn/document_annotations_spec.rb +76 -0
  163. data/spec/prawn/document_destinations_spec.rb +15 -0
  164. data/spec/prawn/document_grid_spec.rb +99 -0
  165. data/spec/prawn/document_reference_spec.rb +27 -0
  166. data/spec/prawn/document_span_spec.rb +44 -0
  167. data/spec/prawn/document_spec.rb +805 -0
  168. data/spec/prawn/font_metric_cache_spec.rb +54 -0
  169. data/spec/prawn/font_spec.rb +544 -0
  170. data/spec/prawn/graphics/blend_mode_spec.rb +63 -0
  171. data/spec/prawn/graphics/transparency_spec.rb +81 -0
  172. data/spec/prawn/graphics_spec.rb +872 -0
  173. data/spec/prawn/graphics_stroke_styles_spec.rb +229 -0
  174. data/spec/{image_handler_spec.rb → prawn/image_handler_spec.rb} +14 -14
  175. data/spec/prawn/images/jpg_spec.rb +20 -0
  176. data/spec/prawn/images/png_spec.rb +283 -0
  177. data/spec/prawn/images_spec.rb +229 -0
  178. data/spec/prawn/measurements_extensions_spec.rb +24 -0
  179. data/spec/prawn/outline_spec.rb +512 -0
  180. data/spec/prawn/repeater_spec.rb +166 -0
  181. data/spec/prawn/soft_mask_spec.rb +74 -0
  182. data/spec/prawn/stamp_spec.rb +173 -0
  183. data/spec/prawn/text/box_spec.rb +1110 -0
  184. data/spec/prawn/text/formatted/arranger_spec.rb +466 -0
  185. data/spec/prawn/text/formatted/box_spec.rb +849 -0
  186. data/spec/prawn/text/formatted/fragment_spec.rb +343 -0
  187. data/spec/prawn/text/formatted/line_wrap_spec.rb +495 -0
  188. data/spec/prawn/text/formatted/parser_spec.rb +697 -0
  189. data/spec/prawn/text_draw_text_spec.rb +150 -0
  190. data/spec/prawn/text_rendering_mode_spec.rb +48 -0
  191. data/spec/prawn/text_spacing_spec.rb +95 -0
  192. data/spec/prawn/text_spec.rb +603 -0
  193. data/spec/prawn/text_with_inline_formatting_spec.rb +35 -0
  194. data/spec/{transformation_stack_spec.rb → prawn/transformation_stack_spec.rb} +22 -19
  195. data/spec/prawn/view_spec.rb +63 -0
  196. data/spec/prawn_manual_spec.rb +35 -0
  197. data/spec/spec_helper.rb +18 -19
  198. metadata +102 -222
  199. metadata.gz.sig +0 -0
  200. data/data/images/16bit.alpha +0 -0
  201. data/data/images/16bit.color +0 -0
  202. data/data/images/16bit.png +0 -0
  203. data/data/images/arrow.png +0 -0
  204. data/data/images/arrow2.png +0 -0
  205. data/data/images/blend_modes_bottom_layer.jpg +0 -0
  206. data/data/images/blend_modes_top_layer.jpg +0 -0
  207. data/data/images/dice.alpha +0 -0
  208. data/data/images/dice.color +0 -0
  209. data/data/images/dice.png +0 -0
  210. data/data/images/dice_interlaced.png +0 -0
  211. data/data/images/fractal.jpg +0 -0
  212. data/data/images/indexed_color.dat +0 -0
  213. data/data/images/indexed_color.png +0 -0
  214. data/data/images/indexed_transparency.png +0 -0
  215. data/data/images/indexed_transparency_alpha.dat +0 -0
  216. data/data/images/indexed_transparency_color.dat +0 -0
  217. data/data/images/letterhead.jpg +0 -0
  218. data/data/images/license.md +0 -8
  219. data/data/images/page_white_text.alpha +0 -0
  220. data/data/images/page_white_text.color +0 -0
  221. data/data/images/page_white_text.png +0 -0
  222. data/data/images/pigs.jpg +0 -0
  223. data/data/images/prawn.png +0 -0
  224. data/data/images/ruport.png +0 -0
  225. data/data/images/ruport_data.dat +0 -0
  226. data/data/images/ruport_transparent.png +0 -0
  227. data/data/images/ruport_type0.png +0 -0
  228. data/data/images/stef.jpg +0 -0
  229. data/data/images/tru256.bmp +0 -0
  230. data/data/images/web-links.dat +0 -1
  231. data/data/images/web-links.png +0 -0
  232. data/data/pdfs/complex_template.pdf +0 -0
  233. data/data/pdfs/contains_ttf_font.pdf +0 -0
  234. data/data/pdfs/encrypted.pdf +0 -0
  235. data/data/pdfs/form.pdf +1 -819
  236. data/data/pdfs/hexagon.pdf +0 -61
  237. data/data/pdfs/indirect_reference.pdf +0 -86
  238. data/data/pdfs/multipage_template.pdf +0 -127
  239. data/data/pdfs/nested_pages.pdf +0 -118
  240. data/data/pdfs/page_without_mediabox.pdf +0 -193
  241. data/data/pdfs/resources_as_indirect_object.pdf +0 -83
  242. data/data/pdfs/two_hexagons.pdf +0 -90
  243. data/data/pdfs/version_1_6.pdf +0 -61
  244. data/data/shift_jis_text.txt +0 -1
  245. data/spec/acceptance/png_spec.rb +0 -35
  246. data/spec/annotations_spec.rb +0 -67
  247. data/spec/blend_mode_spec.rb +0 -71
  248. data/spec/bounding_box_spec.rb +0 -501
  249. data/spec/column_box_spec.rb +0 -59
  250. data/spec/destinations_spec.rb +0 -13
  251. data/spec/document_spec.rb +0 -738
  252. data/spec/font_metric_cache_spec.rb +0 -52
  253. data/spec/font_spec.rb +0 -475
  254. data/spec/formatted_text_arranger_spec.rb +0 -452
  255. data/spec/formatted_text_box_spec.rb +0 -716
  256. data/spec/formatted_text_fragment_spec.rb +0 -299
  257. data/spec/graphics_spec.rb +0 -705
  258. data/spec/grid_spec.rb +0 -95
  259. data/spec/images_spec.rb +0 -167
  260. data/spec/inline_formatted_text_parser_spec.rb +0 -568
  261. data/spec/jpg_spec.rb +0 -23
  262. data/spec/line_wrap_spec.rb +0 -366
  263. data/spec/measurement_units_spec.rb +0 -22
  264. data/spec/outline_spec.rb +0 -409
  265. data/spec/png_spec.rb +0 -257
  266. data/spec/reference_spec.rb +0 -25
  267. data/spec/repeater_spec.rb +0 -154
  268. data/spec/security_spec.rb +0 -151
  269. data/spec/soft_mask_spec.rb +0 -78
  270. data/spec/span_spec.rb +0 -43
  271. data/spec/stamp_spec.rb +0 -179
  272. data/spec/stroke_styles_spec.rb +0 -208
  273. data/spec/text_at_spec.rb +0 -142
  274. data/spec/text_box_spec.rb +0 -1042
  275. data/spec/text_rendering_mode_spec.rb +0 -45
  276. data/spec/text_spacing_spec.rb +0 -93
  277. data/spec/text_spec.rb +0 -543
  278. data/spec/text_with_inline_formatting_spec.rb +0 -35
  279. data/spec/transparency_spec.rb +0 -91
  280. data/spec/view_spec.rb +0 -42
@@ -0,0 +1,849 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ describe Prawn::Text::Formatted::Box do
6
+ let(:pdf) { create_pdf }
7
+
8
+ describe 'wrapping' do
9
+ it 'does not wrap between two fragments' do
10
+ texts = [
11
+ { text: 'Hello ' },
12
+ { text: 'World' },
13
+ { text: '2', styles: [:superscript] }
14
+ ]
15
+ text_box = described_class.new(
16
+ texts,
17
+ document: pdf,
18
+ width: pdf.width_of('Hello World')
19
+ )
20
+ text_box.render
21
+ expect(text_box.text).to eq("Hello\nWorld2")
22
+ end
23
+
24
+ it 'does not raise an Encoding::CompatibilityError when keeping a TTF and '\
25
+ 'an AFM font together' do
26
+ file = "#{Prawn::DATADIR}/fonts/gkai00mp.ttf"
27
+
28
+ pdf.font_families['Kai'] = {
29
+ normal: { file: file, font: 'Kai' }
30
+ }
31
+
32
+ texts = [
33
+ { text: 'Hello ' },
34
+ { text: '再见', font: 'Kai' },
35
+ { text: 'World' }
36
+ ]
37
+ text_box = described_class.new(
38
+ texts,
39
+ document: pdf,
40
+ width: pdf.width_of('Hello World')
41
+ )
42
+
43
+ text_box.render
44
+ end
45
+
46
+ it 'wraps between two fragments when the preceding fragment ends with '\
47
+ 'a white space' do
48
+ texts = [
49
+ { text: 'Hello ' },
50
+ { text: 'World ' },
51
+ { text: '2', styles: [:superscript] }
52
+ ]
53
+ text_box = described_class.new(
54
+ texts,
55
+ document: pdf,
56
+ width: pdf.width_of('Hello World')
57
+ )
58
+ text_box.render
59
+ expect(text_box.text).to eq("Hello World\n2")
60
+
61
+ texts = [
62
+ { text: 'Hello ' },
63
+ { text: "World\n" },
64
+ { text: '2', styles: [:superscript] }
65
+ ]
66
+ text_box = described_class.new(
67
+ texts,
68
+ document: pdf,
69
+ width: pdf.width_of('Hello World')
70
+ )
71
+ text_box.render
72
+ expect(text_box.text).to eq("Hello World\n2")
73
+ end
74
+
75
+ it 'wraps between two fragments when the final fragment begins with '\
76
+ 'a white space' do
77
+ texts = [
78
+ { text: 'Hello ' },
79
+ { text: 'World' },
80
+ { text: ' 2', styles: [:superscript] }
81
+ ]
82
+ text_box = described_class.new(
83
+ texts,
84
+ document: pdf,
85
+ width: pdf.width_of('Hello World')
86
+ )
87
+ text_box.render
88
+ expect(text_box.text).to eq("Hello World\n2")
89
+
90
+ texts = [
91
+ { text: 'Hello ' },
92
+ { text: 'World' },
93
+ { text: "\n2", styles: [:superscript] }
94
+ ]
95
+ text_box = described_class.new(
96
+ texts,
97
+ document: pdf,
98
+ width: pdf.width_of('Hello World')
99
+ )
100
+ text_box.render
101
+ expect(text_box.text).to eq("Hello World\n2")
102
+ end
103
+
104
+ it 'properlies handle empty slices using default encoding' do
105
+ texts = [{
106
+ text: 'Noua Delineatio Geographica generalis | Apostolicarum ' \
107
+ 'peregrinationum | S FRANCISCI XAUERII | Indiarum & Iaponiæ Apostoli',
108
+ font: 'Courier',
109
+ size: 10
110
+ }]
111
+ text_box = described_class.new(
112
+ texts,
113
+ document: pdf,
114
+ width: pdf.width_of('Noua Delineatio Geographica gen')
115
+ )
116
+ expect do
117
+ text_box.render
118
+ end.to_not raise_error
119
+ expect(text_box.text).to eq(
120
+ "Noua Delineatio Geographica\ngeneralis | Apostolicarum\n" \
121
+ "peregrinationum | S FRANCISCI\nXAUERII | Indiarum & Iaponi\346\n" \
122
+ 'Apostoli'
123
+ )
124
+ end
125
+ end
126
+
127
+ describe 'Text::Formatted::Box with :fallback_fonts option that includes' \
128
+ 'a Chinese font and set of Chinese glyphs not in the current font' do
129
+ it 'changes the font to the Chinese font for the Chinese glyphs' do
130
+ file = "#{Prawn::DATADIR}/fonts/gkai00mp.ttf"
131
+ pdf.font_families['Kai'] = {
132
+ normal: { file: file, font: 'Kai' }
133
+ }
134
+ formatted_text = [
135
+ { text: 'hello你好' },
136
+ { text: '再见goodbye' }
137
+ ]
138
+ pdf.formatted_text_box(formatted_text, fallback_fonts: ['Kai'])
139
+
140
+ text = PDF::Inspector::Text.analyze(pdf.render)
141
+
142
+ fonts_used = text.font_settings.map { |e| e[:name] }
143
+ expect(fonts_used.length).to eq(4)
144
+ expect(fonts_used[0]).to eq(:Helvetica)
145
+ expect(fonts_used[1].to_s).to match(/GBZenKai-Medium/)
146
+ expect(fonts_used[2].to_s).to match(/GBZenKai-Medium/)
147
+ expect(fonts_used[3]).to eq(:Helvetica)
148
+
149
+ expect(text.strings[0]).to eq('hello')
150
+ expect(text.strings[1]).to eq('你好')
151
+ expect(text.strings[2]).to eq('再见')
152
+ expect(text.strings[3]).to eq('goodbye')
153
+ end
154
+ end
155
+
156
+ describe 'Text::Formatted::Box with :fallback_fonts option that includes' \
157
+ 'an AFM font and Win-Ansi glyph not in the current Chinese font' do
158
+ it 'changes the font to the AFM font for the Win-Ansi glyph' do
159
+ file = "#{Prawn::DATADIR}/fonts/gkai00mp.ttf"
160
+ pdf.font_families['Kai'] = {
161
+ normal: { file: file, font: 'Kai' }
162
+ }
163
+ pdf.font('Kai')
164
+ formatted_text = [
165
+ { text: 'hello你好' },
166
+ { text: '再见€' }
167
+ ]
168
+ pdf.formatted_text_box(formatted_text, fallback_fonts: ['Helvetica'])
169
+
170
+ text = PDF::Inspector::Text.analyze(pdf.render)
171
+
172
+ fonts_used = text.font_settings.map { |e| e[:name] }
173
+ expect(fonts_used.length).to eq(4)
174
+ expect(fonts_used[0].to_s).to match(/GBZenKai-Medium/)
175
+ expect(fonts_used[1].to_s).to match(/GBZenKai-Medium/)
176
+ expect(fonts_used[2].to_s).to match(/GBZenKai-Medium/)
177
+ expect(fonts_used[3]).to eq(:Helvetica)
178
+
179
+ expect(text.strings[0]).to eq('hello')
180
+ expect(text.strings[1]).to eq('你好')
181
+ expect(text.strings[2]).to eq('再见')
182
+ expect(text.strings[3]).to eq('€')
183
+ end
184
+ end
185
+
186
+ describe 'Text::Formatted::Box with :fallback_fonts option and fragment ' \
187
+ 'level font' do
188
+ it 'uses the fragment level font except for glyphs not in that font' do
189
+ file = "#{Prawn::DATADIR}/fonts/gkai00mp.ttf"
190
+ pdf.font_families['Kai'] = {
191
+ normal: { file: file, font: 'Kai' }
192
+ }
193
+
194
+ file = "#{Prawn::DATADIR}/fonts/DejaVuSans.ttf"
195
+ pdf.font_families['DejaVu Sans'] = {
196
+ normal: { file: file }
197
+ }
198
+
199
+ formatted_text = [
200
+ { text: 'hello你好' },
201
+ { text: '再见goodbye', font: 'Times-Roman' }
202
+ ]
203
+ pdf.formatted_text_box(formatted_text, fallback_fonts: ['Kai'])
204
+
205
+ text = PDF::Inspector::Text.analyze(pdf.render)
206
+
207
+ fonts_used = text.font_settings.map { |e| e[:name] }
208
+ expect(fonts_used.length).to eq(4)
209
+ expect(fonts_used[0]).to eq(:Helvetica)
210
+ expect(fonts_used[1].to_s).to match(/GBZenKai-Medium/)
211
+ expect(fonts_used[2].to_s).to match(/GBZenKai-Medium/)
212
+ expect(fonts_used[3]).to eq(:"Times-Roman")
213
+
214
+ expect(text.strings[0]).to eq('hello')
215
+ expect(text.strings[1]).to eq('你好')
216
+ expect(text.strings[2]).to eq('再见')
217
+ expect(text.strings[3]).to eq('goodbye')
218
+ end
219
+ end
220
+
221
+ describe 'Text::Formatted::Box' do
222
+ let(:formatted_text) { [{ text: 'hello你好' }] }
223
+
224
+ before do
225
+ file = "#{Prawn::DATADIR}/fonts/gkai00mp.ttf"
226
+ pdf.font_families['Kai'] = {
227
+ normal: { file: file, font: 'Kai' }
228
+ }
229
+
230
+ file = "#{Prawn::DATADIR}/fonts/DejaVuSans.ttf"
231
+ pdf.font_families['DejaVu Sans'] = {
232
+ normal: { file: file }
233
+ }
234
+
235
+ pdf.fallback_fonts(['Kai'])
236
+ pdf.fallback_fonts = ['Kai']
237
+ end
238
+
239
+ it '#fallback_fonts should return the document-wide fallback fonts' do
240
+ expect(pdf.fallback_fonts).to eq(['Kai'])
241
+ end
242
+
243
+ it 'is able to set text fallback_fonts document-wide' do
244
+ pdf.formatted_text_box(formatted_text)
245
+
246
+ text = PDF::Inspector::Text.analyze(pdf.render)
247
+
248
+ fonts_used = text.font_settings.map { |e| e[:name] }
249
+ expect(fonts_used.length).to eq(2)
250
+ expect(fonts_used[0]).to eq(:Helvetica)
251
+ expect(fonts_used[1].to_s).to match(/GBZenKai-Medium/)
252
+ end
253
+
254
+ it 'is able to override document-wide fallback_fonts' do
255
+ pdf.fallback_fonts = ['DejaVu Sans']
256
+ pdf.formatted_text_box(formatted_text, fallback_fonts: ['Kai'])
257
+
258
+ text = PDF::Inspector::Text.analyze(pdf.render)
259
+
260
+ fonts_used = text.font_settings.map { |e| e[:name] }
261
+ expect(fonts_used.length).to eq(2)
262
+ expect(fonts_used[0]).to eq(:Helvetica)
263
+ expect(fonts_used[1]).to match(/Kai/)
264
+ end
265
+
266
+ it 'omits the fallback fonts overhead when passing an empty array ' \
267
+ 'as the :fallback_fonts' do
268
+ pdf.font('Kai')
269
+
270
+ box = described_class.new(
271
+ formatted_text,
272
+ document: pdf,
273
+ fallback_fonts: []
274
+ )
275
+
276
+ allow(box).to receive(:process_fallback_fonts)
277
+ box.render
278
+ expect(box).to_not have_received(:process_fallback_fonts)
279
+ end
280
+
281
+ it 'is able to clear document-wide fallback_fonts' do
282
+ pdf.fallback_fonts([])
283
+ box = described_class.new(formatted_text, document: pdf)
284
+
285
+ pdf.font('Kai')
286
+
287
+ allow(box).to receive(:process_fallback_fonts)
288
+ box.render
289
+ expect(box).to_not have_received(:process_fallback_fonts)
290
+ end
291
+ end
292
+
293
+ describe 'Text::Formatted::Box with :fallback_fonts option ' \
294
+ 'with glyphs not in the primary or the fallback fonts' do
295
+ it 'raises an exception' do
296
+ formatted_text = [{ text: 'hello world. 世界你好。' }]
297
+
298
+ expect do
299
+ pdf.formatted_text_box(formatted_text, fallback_fonts: ['Courier'])
300
+ end.to raise_error(Prawn::Errors::IncompatibleStringEncoding)
301
+ end
302
+ end
303
+
304
+ describe 'Text::Formatted::Box#extensions' do
305
+ let(:formatted_wrap_override) do
306
+ Module.new do
307
+ # rubocop: disable RSpec/InstanceVariable
308
+ def wrap(_array)
309
+ initialize_wrap([{ text: 'all your base are belong to us' }])
310
+ @line_wrap.wrap_line(
311
+ document: @document,
312
+ kerning: @kerning,
313
+ width: 10_000,
314
+ arranger: @arranger
315
+ )
316
+ fragment = @arranger.retrieve_fragment
317
+ format_and_draw_fragment(fragment, 0, @line_wrap.width, 0)
318
+
319
+ []
320
+ end
321
+ # rubocop: enable RSpec/InstanceVariable
322
+ end
323
+ end
324
+
325
+ it 'is able to override default line wrapping' do
326
+ described_class.extensions << formatted_wrap_override
327
+ pdf.formatted_text_box([{ text: 'hello world' }], {})
328
+ described_class.extensions.delete(formatted_wrap_override)
329
+ text = PDF::Inspector::Text.analyze(pdf.render)
330
+ expect(text.strings[0]).to eq('all your base are belong to us')
331
+ end
332
+
333
+ it 'overrides Text::Formatted::Box line wrapping does not affect ' \
334
+ 'Text::Box wrapping' do
335
+ described_class.extensions << formatted_wrap_override
336
+ pdf.text_box('hello world', {})
337
+ described_class.extensions.delete(formatted_wrap_override)
338
+ text = PDF::Inspector::Text.analyze(pdf.render)
339
+ expect(text.strings[0]).to eq('hello world')
340
+ end
341
+
342
+ it "overring Text::Box line wrapping doesn't override Text::Box wrapping" do
343
+ Prawn::Text::Box.extensions << formatted_wrap_override
344
+ pdf.text_box('hello world', {})
345
+ Prawn::Text::Box.extensions.delete(formatted_wrap_override)
346
+ text = PDF::Inspector::Text.analyze(pdf.render)
347
+ expect(text.strings[0]).to eq('all your base are belong to us')
348
+ end
349
+ end
350
+
351
+ describe 'Text::Formatted::Box#render' do
352
+ let(:fragment_callback_class) do
353
+ Class.new do
354
+ def initialize(_string, _number, _options); end
355
+
356
+ def render_behind(fragment); end
357
+
358
+ def render_in_front(fragment); end
359
+ end
360
+ end
361
+
362
+ it 'handles newlines' do
363
+ array = [{ text: "hello\nworld" }]
364
+ options = { document: pdf }
365
+ text_box = described_class.new(array, options)
366
+ text_box.render
367
+ expect(text_box.text).to eq("hello\nworld")
368
+ end
369
+
370
+ it 'omits spaces from the beginning of the line' do
371
+ array = [{ text: " hello\n world" }]
372
+ options = { document: pdf }
373
+ text_box = described_class.new(array, options)
374
+ text_box.render
375
+ expect(text_box.text).to eq("hello\nworld")
376
+ end
377
+
378
+ it 'is okay printing a line of whitespace' do
379
+ array = [{ text: "hello\n \nworld" }]
380
+ options = { document: pdf }
381
+ text_box = described_class.new(array, options)
382
+ text_box.render
383
+ expect(text_box.text).to eq("hello\n\nworld")
384
+
385
+ array = [
386
+ { text: "hello#{' ' * 500}" },
387
+ { text: ' ' * 500 },
388
+ { text: "#{' ' * 500}\n" },
389
+ { text: 'world' }
390
+ ]
391
+ options = { document: pdf }
392
+ text_box = described_class.new(array, options)
393
+ text_box.render
394
+ expect(text_box.text).to eq("hello\n\nworld")
395
+ end
396
+
397
+ it 'enables fragment level direction setting' do
398
+ number_of_hellos = 18
399
+ array = [
400
+ { text: 'hello ' * number_of_hellos },
401
+ { text: 'world', direction: :ltr },
402
+ { text: ', how are you?' }
403
+ ]
404
+ options = { document: pdf, direction: :rtl }
405
+ text_box = described_class.new(array, options)
406
+ text_box.render
407
+ text = PDF::Inspector::Text.analyze(pdf.render)
408
+ expect(text.strings[0]).to eq('era woh ,')
409
+ expect(text.strings[1]).to eq('world')
410
+ expect(text.strings[2]).to eq(' olleh' * number_of_hellos)
411
+ expect(text.strings[3]).to eq('?uoy')
412
+ end
413
+
414
+ it 'is able to perform fragment callbacks' do
415
+ callback_object =
416
+ fragment_callback_class.new('something', 7, document: pdf)
417
+ allow(callback_object).to receive(:render_behind)
418
+ allow(callback_object).to receive(:render_in_front)
419
+ array = [
420
+ { text: 'hello world ' },
421
+ { text: 'callback now', callback: callback_object }
422
+ ]
423
+ text_box = described_class.new(array, document: pdf)
424
+ text_box.render
425
+
426
+ expect(callback_object).to have_received(:render_behind).with(
427
+ kind_of(Prawn::Text::Formatted::Fragment)
428
+ )
429
+ expect(callback_object).to have_received(:render_in_front).with(
430
+ kind_of(Prawn::Text::Formatted::Fragment)
431
+ )
432
+ end
433
+
434
+ it 'is able to perform fragment callbacks on multiple objects' do
435
+ callback_object =
436
+ fragment_callback_class.new('something', 7, document: pdf)
437
+ allow(callback_object).to receive(:render_behind)
438
+ allow(callback_object).to receive(:render_in_front)
439
+
440
+ callback_object2 = fragment_callback_class.new(
441
+ 'something else', 14, document: pdf
442
+ )
443
+ allow(callback_object2).to receive(:render_behind)
444
+ allow(callback_object2).to receive(:render_in_front)
445
+
446
+ array = [
447
+ { text: 'hello world ' },
448
+ { text: 'callback now', callback: [callback_object, callback_object2] }
449
+ ]
450
+ text_box = described_class.new(array, document: pdf)
451
+ text_box.render
452
+
453
+ expect(callback_object).to have_received(:render_behind).with(
454
+ kind_of(Prawn::Text::Formatted::Fragment)
455
+ )
456
+ expect(callback_object).to have_received(:render_in_front).with(
457
+ kind_of(Prawn::Text::Formatted::Fragment)
458
+ )
459
+ expect(callback_object2).to have_received(:render_behind).with(
460
+ kind_of(Prawn::Text::Formatted::Fragment)
461
+ )
462
+ expect(callback_object2).to have_received(:render_in_front).with(
463
+ kind_of(Prawn::Text::Formatted::Fragment)
464
+ )
465
+ end
466
+
467
+ it 'fragment callbacks is able to define only the callback they need' do
468
+ behind = (
469
+ Class.new do
470
+ def initialize(_string, _number, _options); end
471
+
472
+ def render_behind(fragment); end
473
+ end
474
+ ).new(
475
+ 'something',
476
+ 7,
477
+ document: pdf
478
+ )
479
+ in_front = (
480
+ Class.new do
481
+ def initialize(_string, _number, _options); end
482
+
483
+ def render_in_front(fragment); end
484
+ end
485
+ ).new(
486
+ 'something',
487
+ 7,
488
+ document: pdf
489
+ )
490
+ array = [
491
+ { text: 'hello world ' },
492
+ { text: 'callback now', callback: [behind, in_front] }
493
+ ]
494
+ text_box = described_class.new(array, document: pdf)
495
+
496
+ text_box.render # trigger callbacks
497
+ end
498
+
499
+ it 'is able to set the font' do
500
+ array = [
501
+ { text: 'this contains ' },
502
+ {
503
+ text: 'Times-Bold',
504
+ styles: [:bold],
505
+ font: 'Times-Roman'
506
+ },
507
+ { text: ' text' }
508
+ ]
509
+ text_box = described_class.new(array, document: pdf)
510
+ text_box.render
511
+ contents = PDF::Inspector::Text.analyze(pdf.render)
512
+ fonts = contents.font_settings.map { |e| e[:name] }
513
+ expect(fonts).to eq(%i[Helvetica Times-Bold Helvetica])
514
+ expect(contents.strings[0]).to eq('this contains ')
515
+ expect(contents.strings[1]).to eq('Times-Bold')
516
+ expect(contents.strings[2]).to eq(' text')
517
+ end
518
+
519
+ it 'is able to set bold' do
520
+ array = [
521
+ { text: 'this contains ' },
522
+ { text: 'bold', styles: [:bold] },
523
+ { text: ' text' }
524
+ ]
525
+ text_box = described_class.new(array, document: pdf)
526
+ text_box.render
527
+ contents = PDF::Inspector::Text.analyze(pdf.render)
528
+ fonts = contents.font_settings.map { |e| e[:name] }
529
+ expect(fonts).to eq(%i[Helvetica Helvetica-Bold Helvetica])
530
+ expect(contents.strings[0]).to eq('this contains ')
531
+ expect(contents.strings[1]).to eq('bold')
532
+ expect(contents.strings[2]).to eq(' text')
533
+ end
534
+
535
+ it 'is able to set italics' do
536
+ array = [
537
+ { text: 'this contains ' },
538
+ { text: 'italic', styles: [:italic] },
539
+ { text: ' text' }
540
+ ]
541
+ text_box = described_class.new(array, document: pdf)
542
+ text_box.render
543
+ contents = PDF::Inspector::Text.analyze(pdf.render)
544
+ fonts = contents.font_settings.map { |e| e[:name] }
545
+ expect(fonts).to eq(%i[Helvetica Helvetica-Oblique Helvetica])
546
+ end
547
+
548
+ it 'is able to set subscript' do
549
+ array = [
550
+ { text: 'this contains ' },
551
+ { text: 'subscript', size: 18, styles: [:subscript] },
552
+ { text: ' text' }
553
+ ]
554
+ text_box = described_class.new(array, document: pdf)
555
+ text_box.render
556
+ contents = PDF::Inspector::Text.analyze(pdf.render)
557
+ expect(contents.font_settings[0][:size]).to eq(12)
558
+ expect(contents.font_settings[1][:size])
559
+ .to be_within(0.0001).of(18 * 0.583)
560
+ end
561
+
562
+ it 'is able to set superscript' do
563
+ array = [
564
+ { text: 'this contains ' },
565
+ { text: 'superscript', size: 18, styles: [:superscript] },
566
+ { text: ' text' }
567
+ ]
568
+ text_box = described_class.new(array, document: pdf)
569
+ text_box.render
570
+ contents = PDF::Inspector::Text.analyze(pdf.render)
571
+ expect(contents.font_settings[0][:size]).to eq(12)
572
+ expect(contents.font_settings[1][:size])
573
+ .to be_within(0.0001).of(18 * 0.583)
574
+ end
575
+
576
+ it 'is able to set compound bold and italic text' do
577
+ array = [
578
+ { text: 'this contains ' },
579
+ { text: 'bold italic', styles: %i[bold italic] },
580
+ { text: ' text' }
581
+ ]
582
+ text_box = described_class.new(array, document: pdf)
583
+ text_box.render
584
+ contents = PDF::Inspector::Text.analyze(pdf.render)
585
+ fonts = contents.font_settings.map { |e| e[:name] }
586
+ expect(fonts).to eq(%i[Helvetica Helvetica-BoldOblique Helvetica])
587
+ end
588
+
589
+ it 'is able to underline' do
590
+ array = [
591
+ { text: 'this contains ' },
592
+ { text: 'underlined', styles: [:underline] },
593
+ { text: ' text' }
594
+ ]
595
+ text_box = described_class.new(array, document: pdf)
596
+ text_box.render
597
+ line_drawing = PDF::Inspector::Graphics::Line.analyze(pdf.render)
598
+ expect(line_drawing.points.length).to eq(2)
599
+ end
600
+
601
+ it 'is able to strikethrough' do
602
+ array = [
603
+ { text: 'this contains ' },
604
+ { text: 'struckthrough', styles: [:strikethrough] },
605
+ { text: ' text' }
606
+ ]
607
+ text_box = described_class.new(array, document: pdf)
608
+ text_box.render
609
+ line_drawing = PDF::Inspector::Graphics::Line.analyze(pdf.render)
610
+ expect(line_drawing.points.length).to eq(2)
611
+ end
612
+
613
+ it 'is able to add URL links' do
614
+ allow(pdf).to receive(:link_annotation)
615
+ array = [
616
+ { text: 'click ' },
617
+ { text: 'here', link: 'http://example.com' },
618
+ { text: ' to visit' }
619
+ ]
620
+ text_box = described_class.new(array, document: pdf)
621
+ text_box.render
622
+
623
+ expect(pdf).to have_received(:link_annotation).with(
624
+ kind_of(Array),
625
+ Border: [0, 0, 0],
626
+ A: {
627
+ Type: :Action,
628
+ S: :URI,
629
+ URI: 'http://example.com'
630
+ }
631
+ )
632
+ end
633
+
634
+ it 'is able to add destination links' do
635
+ allow(pdf).to receive(:link_annotation)
636
+ array = [
637
+ { text: 'Go to the ' },
638
+ { text: 'Table of Contents', anchor: 'ToC' }
639
+ ]
640
+ text_box = described_class.new(array, document: pdf)
641
+ text_box.render
642
+
643
+ expect(pdf).to have_received(:link_annotation).with(
644
+ kind_of(Array),
645
+ Border: [0, 0, 0],
646
+ Dest: 'ToC'
647
+ )
648
+ end
649
+
650
+ it 'is able to add local actions' do
651
+ allow(pdf).to receive(:link_annotation)
652
+ array = [
653
+ { text: 'click ' },
654
+ { text: 'here', local: '../example.pdf' },
655
+ { text: ' to open a local file' }
656
+ ]
657
+ text_box = described_class.new(array, document: pdf)
658
+ text_box.render
659
+
660
+ expect(pdf).to have_received(:link_annotation).with(
661
+ kind_of(Array),
662
+ Border: [0, 0, 0],
663
+ A: {
664
+ Type: :Action,
665
+ S: :Launch,
666
+ F: '../example.pdf',
667
+ NewWindow: true
668
+ }
669
+ )
670
+ end
671
+
672
+ it 'is able to set font size' do
673
+ array = [
674
+ { text: 'this contains ' },
675
+ { text: 'sized', size: 24 },
676
+ { text: ' text' }
677
+ ]
678
+ text_box = described_class.new(array, document: pdf)
679
+ text_box.render
680
+ contents = PDF::Inspector::Text.analyze(pdf.render)
681
+ expect(contents.font_settings[0][:size]).to eq(12)
682
+ expect(contents.font_settings[1][:size]).to eq(24)
683
+ end
684
+
685
+ it 'sets the baseline based on the tallest fragment on a given line' do
686
+ array = [
687
+ { text: 'this contains ' },
688
+ { text: 'sized', size: 24 },
689
+ { text: ' text' }
690
+ ]
691
+ text_box = described_class.new(array, document: pdf)
692
+ text_box.render
693
+ pdf.font_size(24) do
694
+ expect(text_box.height).to be_within(0.001)
695
+ .of(pdf.font.ascender + pdf.font.descender)
696
+ end
697
+ end
698
+
699
+ it 'is able to set color via an rgb hex string' do
700
+ array = [{
701
+ text: 'rgb',
702
+ color: 'ff0000'
703
+ }]
704
+ text_box = described_class.new(array, document: pdf)
705
+ text_box.render
706
+ colors = PDF::Inspector::Graphics::Color.analyze(pdf.render)
707
+ expect(colors.fill_color_count).to eq(2)
708
+ expect(colors.stroke_color_count).to eq(2)
709
+ end
710
+
711
+ it 'is able to set color using a cmyk array' do
712
+ array = [{
713
+ text: 'cmyk',
714
+ color: [100, 0, 0, 0]
715
+ }]
716
+ text_box = described_class.new(array, document: pdf)
717
+ text_box.render
718
+ colors = PDF::Inspector::Graphics::Color.analyze(pdf.render)
719
+ expect(colors.fill_color_count).to eq(2)
720
+ expect(colors.stroke_color_count).to eq(2)
721
+ end
722
+ end
723
+
724
+ describe 'Text::Formatted::Box#render(:dry_run => true)' do
725
+ it 'does not change the graphics state of the document' do
726
+ state_before = PDF::Inspector::Graphics::Color.analyze(pdf.render)
727
+ fill_color_count = state_before.fill_color_count
728
+ stroke_color_count = state_before.stroke_color_count
729
+ stroke_color_space_count = state_before.stroke_color_space_count
730
+
731
+ array = [{
732
+ text: 'Foo',
733
+ color: [0, 0, 0, 100]
734
+ }]
735
+ options = { document: pdf }
736
+ text_box = described_class.new(array, options)
737
+ text_box.render(dry_run: true)
738
+
739
+ state_after = PDF::Inspector::Graphics::Color.analyze(pdf.render)
740
+ expect(state_after.fill_color_count).to eq(fill_color_count)
741
+ expect(state_after.stroke_color_count).to eq(stroke_color_count)
742
+ expect(state_after.stroke_color_space_count)
743
+ .to eq(stroke_color_space_count)
744
+ end
745
+ end
746
+
747
+ describe 'Text::Formatted::Box#render with fragment level '\
748
+ ':character_spacing option' do
749
+ it 'draws the character spacing to the document' do
750
+ array = [{
751
+ text: 'hello world',
752
+ character_spacing: 7
753
+ }]
754
+ options = { document: pdf }
755
+ text_box = described_class.new(array, options)
756
+ text_box.render
757
+ contents = PDF::Inspector::Text.analyze(pdf.render)
758
+ expect(contents.character_spacing[0]).to eq(7)
759
+ end
760
+
761
+ it 'lays out text properly' do
762
+ array = [{
763
+ text: 'hello world',
764
+ font: 'Courier',
765
+ character_spacing: 10
766
+ }]
767
+ options = {
768
+ document: pdf,
769
+ width: 100,
770
+ overflow: :expand
771
+ }
772
+ text_box = described_class.new(array, options)
773
+ text_box.render
774
+ expect(text_box.text).to eq("hello\nworld")
775
+ end
776
+ end
777
+
778
+ describe 'Text::Formatted::Box#render with :align => :justify' do
779
+ it 'does not justify the last line of a paragraph' do
780
+ array = [
781
+ { text: 'hello world ' },
782
+ { text: "\n" },
783
+ { text: 'goodbye' }
784
+ ]
785
+ options = { document: pdf, align: :justify }
786
+ text_box = described_class.new(array, options)
787
+ text_box.render
788
+ contents = PDF::Inspector::Text.analyze(pdf.render)
789
+ expect(contents.word_spacing).to be_empty
790
+ end
791
+
792
+ it 'raise an exception when align value is not a symbol' do
793
+ array = [
794
+ { text: 'hello world ' },
795
+ { text: "\n" },
796
+ { text: 'goodbye' }
797
+ ]
798
+ options = { document: pdf, align: 'justify' }
799
+ text_box = described_class.new(array, options)
800
+ expect { text_box.render }.to raise_error(
801
+ ArgumentError,
802
+ 'align must be one of :left, :right, :center or :justify symbols'
803
+ )
804
+ end
805
+ end
806
+
807
+ describe 'Text::Formatted::Box#render with :valign => :center' do
808
+ it 'has a bottom gap equal to baseline and bottom of box' do
809
+ box_height = 100
810
+ y = 450
811
+ array = [{ text: 'Vertical Align' }]
812
+ options = {
813
+ document: pdf,
814
+ valign: :center,
815
+ at: [0, y],
816
+ width: 100,
817
+ height: box_height,
818
+ size: 16
819
+ }
820
+ text_box = described_class.new(array, options)
821
+ text_box.render
822
+ line_padding = (box_height - text_box.height + text_box.descender) * 0.5
823
+ baseline = y - line_padding
824
+
825
+ expect(text_box.at[1]).to be_within(0.01).of(baseline)
826
+ end
827
+ end
828
+
829
+ describe 'Text::Formatted::Box#render with :valign => :bottom' do
830
+ it 'does not render a gap between the text and bottom of box' do
831
+ box_height = 100
832
+ y = 450
833
+ array = [{ text: 'Vertical Align' }]
834
+ options = {
835
+ document: pdf,
836
+ valign: :bottom,
837
+ at: [0, y],
838
+ width: 100,
839
+ height: box_height,
840
+ size: 16
841
+ }
842
+ text_box = described_class.new(array, options)
843
+ text_box.render
844
+ top_padding = y - (box_height - text_box.height)
845
+
846
+ expect(text_box.at[1]).to be_within(0.01).of(top_padding)
847
+ end
848
+ end
849
+ end