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