prawn 0.13.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 (348) hide show
  1. checksums.yaml +5 -5
  2. checksums.yaml.gz.sig +0 -0
  3. data/.yardopts +10 -0
  4. data/GPLv2 +20 -21
  5. data/Gemfile +3 -16
  6. data/Rakefile +17 -39
  7. data/lib/prawn/document/bounding_box.rb +85 -42
  8. data/lib/prawn/document/column_box.rb +21 -11
  9. data/lib/prawn/document/internals.rb +40 -147
  10. data/lib/prawn/document/span.rb +25 -17
  11. data/lib/prawn/document.rb +286 -245
  12. data/lib/prawn/encoding.rb +68 -101
  13. data/lib/prawn/errors.rb +47 -43
  14. data/lib/prawn/font.rb +204 -155
  15. data/lib/prawn/font_metric_cache.rb +25 -21
  16. data/lib/prawn/fonts/afm.rb +292 -0
  17. data/lib/prawn/{font → fonts}/dfont.rb +7 -13
  18. data/lib/prawn/fonts/otf.rb +11 -0
  19. data/lib/prawn/fonts/ttc.rb +36 -0
  20. data/lib/prawn/{font → fonts}/ttf.rb +142 -80
  21. data/lib/prawn/graphics/blend_mode.rb +65 -0
  22. data/lib/prawn/graphics/cap_style.rb +6 -5
  23. data/lib/prawn/graphics/color.rb +47 -44
  24. data/lib/prawn/graphics/dash.rb +30 -13
  25. data/lib/prawn/graphics/join_style.rb +13 -6
  26. data/lib/prawn/graphics/patterns.rb +221 -90
  27. data/lib/prawn/graphics/transformation.rb +21 -12
  28. data/lib/prawn/graphics/transparency.rb +21 -17
  29. data/lib/prawn/graphics.rb +155 -128
  30. data/lib/prawn/{layout/grid.rb → grid.rb} +110 -47
  31. data/lib/prawn/image_handler.rb +16 -2
  32. data/lib/prawn/images/image.rb +4 -2
  33. data/lib/prawn/images/jpg.rb +39 -30
  34. data/lib/prawn/images/png.rb +132 -169
  35. data/lib/prawn/images.rb +70 -62
  36. data/lib/prawn/measurement_extensions.rb +15 -10
  37. data/lib/prawn/measurements.rb +22 -23
  38. data/lib/prawn/outline.rb +301 -13
  39. data/lib/prawn/repeater.rb +19 -17
  40. data/lib/prawn/security/arcfour.rb +54 -0
  41. data/lib/prawn/security.rb +108 -86
  42. data/lib/prawn/soft_mask.rb +40 -41
  43. data/lib/prawn/stamp.rb +29 -12
  44. data/lib/prawn/text/box.rb +27 -29
  45. data/lib/prawn/text/formatted/arranger.rb +110 -67
  46. data/lib/prawn/text/formatted/box.rb +233 -165
  47. data/lib/prawn/text/formatted/fragment.rb +27 -27
  48. data/lib/prawn/text/formatted/line_wrap.rb +137 -97
  49. data/lib/prawn/text/formatted/parser.rb +149 -127
  50. data/lib/prawn/text/formatted/wrap.rb +57 -37
  51. data/lib/prawn/text/formatted.rb +6 -4
  52. data/lib/prawn/text.rb +105 -73
  53. data/lib/prawn/transformation_stack.rb +44 -0
  54. data/lib/prawn/utilities.rb +11 -21
  55. data/lib/prawn/version.rb +5 -0
  56. data/lib/prawn/view.rb +101 -0
  57. data/lib/prawn.rb +42 -68
  58. data/{data/images/fractal.jpg → manual/absolute_position.pdf} +0 -0
  59. data/manual/basic_concepts/adding_pages.rb +9 -10
  60. data/manual/basic_concepts/basic_concepts.rb +33 -24
  61. data/manual/basic_concepts/creation.rb +10 -11
  62. data/manual/basic_concepts/cursor.rb +9 -10
  63. data/manual/basic_concepts/measurement.rb +10 -11
  64. data/manual/basic_concepts/origin.rb +8 -9
  65. data/manual/basic_concepts/other_cursor_helpers.rb +17 -18
  66. data/manual/basic_concepts/view.rb +48 -0
  67. data/manual/bounding_box/bounding_box.rb +31 -29
  68. data/manual/bounding_box/bounds.rb +17 -18
  69. data/manual/bounding_box/canvas.rb +8 -9
  70. data/manual/bounding_box/creation.rb +8 -9
  71. data/manual/bounding_box/indentation.rb +22 -23
  72. data/manual/bounding_box/nesting.rb +32 -25
  73. data/manual/bounding_box/russian_boxes.rb +19 -19
  74. data/manual/bounding_box/stretchy.rb +18 -20
  75. data/manual/contents.rb +35 -0
  76. data/manual/cover.rb +43 -0
  77. data/manual/document_and_page_options/background.rb +16 -14
  78. data/manual/document_and_page_options/document_and_page_options.rb +26 -23
  79. data/manual/document_and_page_options/metadata.rb +21 -19
  80. data/manual/document_and_page_options/page_margins.rb +20 -22
  81. data/manual/document_and_page_options/page_size.rb +15 -15
  82. data/manual/document_and_page_options/print_scaling.rb +23 -0
  83. data/manual/example_helper.rb +5 -408
  84. data/manual/graphics/blend_mode.rb +52 -0
  85. data/manual/graphics/circle_and_ellipse.rb +8 -9
  86. data/manual/graphics/color.rb +11 -13
  87. data/manual/graphics/common_lines.rb +13 -12
  88. data/manual/graphics/fill_and_stroke.rb +10 -11
  89. data/manual/graphics/fill_rules.rb +13 -12
  90. data/manual/graphics/gradients.rb +28 -22
  91. data/manual/graphics/graphics.rb +52 -46
  92. data/manual/graphics/helper.rb +20 -10
  93. data/manual/graphics/line_width.rb +13 -12
  94. data/manual/graphics/lines_and_curves.rb +13 -14
  95. data/manual/graphics/polygon.rb +10 -12
  96. data/manual/graphics/rectangle.rb +7 -8
  97. data/manual/graphics/rotate.rb +9 -12
  98. data/manual/graphics/scale.rb +19 -18
  99. data/manual/graphics/soft_masks.rb +5 -7
  100. data/manual/graphics/stroke_cap.rb +10 -11
  101. data/manual/graphics/stroke_dash.rb +16 -17
  102. data/manual/graphics/stroke_join.rb +10 -11
  103. data/manual/graphics/translate.rb +13 -13
  104. data/manual/graphics/transparency.rb +11 -13
  105. data/manual/{manual/how_to_read_this_manual.rb → how_to_read_this_manual.rb} +23 -25
  106. data/manual/images/absolute_position.rb +9 -10
  107. data/manual/images/fit.rb +9 -10
  108. data/manual/images/horizontal.rb +13 -14
  109. data/manual/images/images.rb +31 -30
  110. data/manual/images/plain_image.rb +6 -7
  111. data/manual/images/scale.rb +12 -13
  112. data/manual/images/vertical.rb +19 -17
  113. data/manual/images/width_and_height.rb +13 -14
  114. data/manual/layout/boxes.rb +14 -15
  115. data/manual/layout/content.rb +12 -13
  116. data/manual/layout/layout.rb +19 -20
  117. data/manual/layout/simple_grid.rb +8 -9
  118. data/manual/outline/add_subsection_to.rb +26 -27
  119. data/manual/outline/insert_section_after.rb +19 -20
  120. data/manual/outline/outline.rb +23 -22
  121. data/manual/outline/sections_and_pages.rb +24 -25
  122. data/manual/repeatable_content/alternate_page_numbering.rb +36 -0
  123. data/manual/repeatable_content/page_numbering.rb +20 -19
  124. data/manual/repeatable_content/repeatable_content.rb +26 -22
  125. data/manual/repeatable_content/repeater.rb +18 -19
  126. data/manual/repeatable_content/stamp.rb +18 -19
  127. data/manual/security/encryption.rb +8 -11
  128. data/manual/security/permissions.rb +20 -15
  129. data/manual/security/security.rb +20 -20
  130. data/manual/table.rb +16 -0
  131. data/manual/text/alignment.rb +17 -18
  132. data/manual/text/color.rb +13 -13
  133. data/manual/text/column_box.rb +10 -12
  134. data/manual/text/fallback_fonts.rb +29 -25
  135. data/manual/text/font.rb +17 -18
  136. data/manual/text/font_size.rb +21 -22
  137. data/manual/text/font_style.rb +12 -10
  138. data/manual/text/formatted_callbacks.rb +36 -26
  139. data/manual/text/formatted_text.rb +41 -34
  140. data/manual/text/free_flowing_text.rb +28 -29
  141. data/manual/text/inline.rb +23 -26
  142. data/manual/text/kerning_and_character_spacing.rb +20 -21
  143. data/manual/text/leading.rb +10 -11
  144. data/manual/text/line_wrapping.rb +40 -21
  145. data/manual/text/paragraph_indentation.rb +17 -12
  146. data/manual/text/positioned_text.rb +19 -20
  147. data/manual/text/registering_families.rb +33 -30
  148. data/manual/text/rendering_and_color.rb +11 -12
  149. data/manual/text/right_to_left_text.rb +31 -20
  150. data/manual/text/rotation.rb +36 -27
  151. data/manual/text/single_usage.rb +13 -14
  152. data/manual/text/text.rb +62 -62
  153. data/manual/text/text_box_excess.rb +22 -19
  154. data/manual/text/text_box_extensions.rb +21 -18
  155. data/manual/text/text_box_overflow.rb +28 -21
  156. data/manual/text/utf8.rb +16 -17
  157. data/manual/text/win_ansi_charset.rb +29 -26
  158. data/prawn.gemspec +45 -43
  159. data/spec/extensions/encoding_helpers.rb +4 -3
  160. data/spec/prawn/document/bounding_box_spec.rb +550 -0
  161. data/spec/prawn/document/column_box_spec.rb +75 -0
  162. data/spec/prawn/document/security_spec.rb +176 -0
  163. data/spec/prawn/document_annotations_spec.rb +76 -0
  164. data/spec/prawn/document_destinations_spec.rb +15 -0
  165. data/spec/prawn/document_grid_spec.rb +99 -0
  166. data/spec/prawn/document_reference_spec.rb +27 -0
  167. data/spec/prawn/document_span_spec.rb +44 -0
  168. data/spec/prawn/document_spec.rb +805 -0
  169. data/spec/prawn/font_metric_cache_spec.rb +54 -0
  170. data/spec/prawn/font_spec.rb +544 -0
  171. data/spec/prawn/graphics/blend_mode_spec.rb +63 -0
  172. data/spec/prawn/graphics/transparency_spec.rb +81 -0
  173. data/spec/prawn/graphics_spec.rb +872 -0
  174. data/spec/prawn/graphics_stroke_styles_spec.rb +229 -0
  175. data/spec/prawn/image_handler_spec.rb +53 -0
  176. data/spec/prawn/images/jpg_spec.rb +20 -0
  177. data/spec/prawn/images/png_spec.rb +283 -0
  178. data/spec/prawn/images_spec.rb +229 -0
  179. data/spec/prawn/measurements_extensions_spec.rb +24 -0
  180. data/spec/prawn/outline_spec.rb +512 -0
  181. data/spec/prawn/repeater_spec.rb +166 -0
  182. data/spec/prawn/soft_mask_spec.rb +74 -0
  183. data/spec/prawn/stamp_spec.rb +173 -0
  184. data/spec/prawn/text/box_spec.rb +1110 -0
  185. data/spec/prawn/text/formatted/arranger_spec.rb +466 -0
  186. data/spec/prawn/text/formatted/box_spec.rb +849 -0
  187. data/spec/prawn/text/formatted/fragment_spec.rb +343 -0
  188. data/spec/prawn/text/formatted/line_wrap_spec.rb +495 -0
  189. data/spec/prawn/text/formatted/parser_spec.rb +697 -0
  190. data/spec/prawn/text_draw_text_spec.rb +150 -0
  191. data/spec/prawn/text_rendering_mode_spec.rb +48 -0
  192. data/spec/prawn/text_spacing_spec.rb +95 -0
  193. data/spec/prawn/text_spec.rb +603 -0
  194. data/spec/prawn/text_with_inline_formatting_spec.rb +35 -0
  195. data/spec/prawn/transformation_stack_spec.rb +66 -0
  196. data/spec/prawn/view_spec.rb +63 -0
  197. data/spec/prawn_manual_spec.rb +35 -0
  198. data/spec/spec_helper.rb +22 -21
  199. data.tar.gz.sig +0 -0
  200. metadata +168 -307
  201. metadata.gz.sig +0 -0
  202. data/README.md +0 -109
  203. data/data/encodings/win_ansi.txt +0 -29
  204. data/data/images/16bit.alpha +0 -0
  205. data/data/images/16bit.dat +0 -0
  206. data/data/images/16bit.png +0 -0
  207. data/data/images/arrow.png +0 -0
  208. data/data/images/arrow2.png +0 -0
  209. data/data/images/barcode_issue.png +0 -0
  210. data/data/images/dice.alpha +0 -0
  211. data/data/images/dice.dat +0 -0
  212. data/data/images/dice.png +0 -0
  213. data/data/images/dice_interlaced.png +0 -0
  214. data/data/images/indexed_color.dat +0 -0
  215. data/data/images/indexed_color.png +0 -0
  216. data/data/images/letterhead.jpg +0 -0
  217. data/data/images/page_white_text.alpha +0 -0
  218. data/data/images/page_white_text.dat +0 -0
  219. data/data/images/page_white_text.png +0 -0
  220. data/data/images/pigs.jpg +0 -0
  221. data/data/images/prawn.png +0 -0
  222. data/data/images/ruport.png +0 -0
  223. data/data/images/ruport_data.dat +0 -0
  224. data/data/images/ruport_transparent.png +0 -0
  225. data/data/images/ruport_type0.png +0 -0
  226. data/data/images/stef.jpg +0 -0
  227. data/data/images/tru256.bmp +0 -0
  228. data/data/images/web-links.dat +0 -1
  229. data/data/images/web-links.png +0 -0
  230. data/data/pdfs/complex_template.pdf +0 -0
  231. data/data/pdfs/contains_ttf_font.pdf +0 -0
  232. data/data/pdfs/encrypted.pdf +0 -0
  233. data/data/pdfs/form.pdf +1 -819
  234. data/data/pdfs/hexagon.pdf +0 -61
  235. data/data/pdfs/indirect_reference.pdf +0 -86
  236. data/data/pdfs/multipage_template.pdf +0 -127
  237. data/data/pdfs/nested_pages.pdf +0 -118
  238. data/data/pdfs/page_without_mediabox.pdf +0 -193
  239. data/data/pdfs/resources_as_indirect_object.pdf +0 -83
  240. data/data/pdfs/two_hexagons.pdf +0 -90
  241. data/data/pdfs/version_1_6.pdf +0 -61
  242. data/data/shift_jis_text.txt +0 -1
  243. data/lib/pdf/core/annotations.rb +0 -60
  244. data/lib/pdf/core/byte_string.rb +0 -9
  245. data/lib/pdf/core/destinations.rb +0 -90
  246. data/lib/pdf/core/document_state.rb +0 -78
  247. data/lib/pdf/core/filter_list.rb +0 -51
  248. data/lib/pdf/core/filters.rb +0 -36
  249. data/lib/pdf/core/graphics_state.rb +0 -68
  250. data/lib/pdf/core/literal_string.rb +0 -16
  251. data/lib/pdf/core/name_tree.rb +0 -177
  252. data/lib/pdf/core/object_store.rb +0 -320
  253. data/lib/pdf/core/outline.rb +0 -315
  254. data/lib/pdf/core/page.rb +0 -212
  255. data/lib/pdf/core/page_geometry.rb +0 -126
  256. data/lib/pdf/core/pdf_object.rb +0 -124
  257. data/lib/pdf/core/reference.rb +0 -103
  258. data/lib/pdf/core/stream.rb +0 -98
  259. data/lib/pdf/core/text.rb +0 -275
  260. data/lib/pdf/core.rb +0 -35
  261. data/lib/prawn/compatibility.rb +0 -91
  262. data/lib/prawn/document/graphics_state.rb +0 -73
  263. data/lib/prawn/document/snapshot.rb +0 -89
  264. data/lib/prawn/font/afm.rb +0 -203
  265. data/lib/prawn/layout.rb +0 -20
  266. data/lib/prawn/table/cell/image.rb +0 -70
  267. data/lib/prawn/table/cell/in_table.rb +0 -27
  268. data/lib/prawn/table/cell/span_dummy.rb +0 -92
  269. data/lib/prawn/table/cell/subtable.rb +0 -65
  270. data/lib/prawn/table/cell/text.rb +0 -153
  271. data/lib/prawn/table/cell.rb +0 -770
  272. data/lib/prawn/table/cells.rb +0 -295
  273. data/lib/prawn/table.rb +0 -643
  274. data/manual/example_file.rb +0 -116
  275. data/manual/example_package.rb +0 -53
  276. data/manual/example_section.rb +0 -46
  277. data/manual/manual/cover.rb +0 -35
  278. data/manual/manual/foreword.rb +0 -85
  279. data/manual/manual/manual.rb +0 -35
  280. data/manual/syntax_highlight.rb +0 -52
  281. data/manual/table/basic_block.rb +0 -53
  282. data/manual/table/before_rendering_page.rb +0 -26
  283. data/manual/table/cell_border_lines.rb +0 -24
  284. data/manual/table/cell_borders_and_bg.rb +0 -31
  285. data/manual/table/cell_dimensions.rb +0 -30
  286. data/manual/table/cell_text.rb +0 -38
  287. data/manual/table/column_widths.rb +0 -30
  288. data/manual/table/content_and_subtables.rb +0 -39
  289. data/manual/table/creation.rb +0 -27
  290. data/manual/table/filtering.rb +0 -36
  291. data/manual/table/flow_and_header.rb +0 -17
  292. data/manual/table/image_cells.rb +0 -33
  293. data/manual/table/position.rb +0 -29
  294. data/manual/table/row_colors.rb +0 -20
  295. data/manual/table/span.rb +0 -30
  296. data/manual/table/style.rb +0 -22
  297. data/manual/table/table.rb +0 -52
  298. data/manual/table/width.rb +0 -27
  299. data/manual/templates/full_template.rb +0 -25
  300. data/manual/templates/page_template.rb +0 -48
  301. data/manual/templates/templates.rb +0 -27
  302. data/manual/text/group.rb +0 -29
  303. data/spec/acceptance/png.rb +0 -23
  304. data/spec/annotations_spec.rb +0 -74
  305. data/spec/bounding_box_spec.rb +0 -493
  306. data/spec/cell_spec.rb +0 -628
  307. data/spec/column_box_spec.rb +0 -33
  308. data/spec/destinations_spec.rb +0 -15
  309. data/spec/document_spec.rb +0 -761
  310. data/spec/extensions/mocha.rb +0 -44
  311. data/spec/filters_spec.rb +0 -34
  312. data/spec/font_metric_cache_spec.rb +0 -52
  313. data/spec/font_spec.rb +0 -464
  314. data/spec/formatted_text_arranger_spec.rb +0 -421
  315. data/spec/formatted_text_box_spec.rb +0 -650
  316. data/spec/formatted_text_fragment_spec.rb +0 -298
  317. data/spec/graphics_spec.rb +0 -651
  318. data/spec/grid_spec.rb +0 -85
  319. data/spec/image_handler_spec.rb +0 -42
  320. data/spec/images_spec.rb +0 -157
  321. data/spec/inline_formatted_text_parser_spec.rb +0 -564
  322. data/spec/jpg_spec.rb +0 -25
  323. data/spec/line_wrap_spec.rb +0 -333
  324. data/spec/measurement_units_spec.rb +0 -23
  325. data/spec/name_tree_spec.rb +0 -112
  326. data/spec/object_store_spec.rb +0 -170
  327. data/spec/outline_spec.rb +0 -448
  328. data/spec/pdf_object_spec.rb +0 -172
  329. data/spec/png_spec.rb +0 -240
  330. data/spec/reference_spec.rb +0 -82
  331. data/spec/repeater_spec.rb +0 -158
  332. data/spec/security_spec.rb +0 -158
  333. data/spec/snapshot_spec.rb +0 -186
  334. data/spec/soft_mask_spec.rb +0 -117
  335. data/spec/span_spec.rb +0 -44
  336. data/spec/stamp_spec.rb +0 -158
  337. data/spec/stream_spec.rb +0 -58
  338. data/spec/stroke_styles_spec.rb +0 -211
  339. data/spec/table/span_dummy_spec.rb +0 -17
  340. data/spec/table_spec.rb +0 -1355
  341. data/spec/template_spec.rb +0 -351
  342. data/spec/text_at_spec.rb +0 -130
  343. data/spec/text_box_spec.rb +0 -1030
  344. data/spec/text_rendering_mode_spec.rb +0 -45
  345. data/spec/text_spacing_spec.rb +0 -93
  346. data/spec/text_spec.rb +0 -425
  347. data/spec/text_with_inline_formatting_spec.rb +0 -35
  348. data/spec/transparency_spec.rb +0 -89
@@ -0,0 +1,1110 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ describe Prawn::Text::Box do
6
+ let(:pdf) { create_pdf }
7
+
8
+ it 'is able to set leading document-wide' do
9
+ pdf.default_leading(7)
10
+ pdf.default_leading = 7
11
+ text_box = described_class.new('hello world', document: pdf)
12
+ expect(text_box.leading).to eq(7)
13
+ end
14
+
15
+ it 'option should be able to override document-wide leading' do
16
+ pdf.default_leading = 7
17
+ text_box = described_class.new(
18
+ 'hello world',
19
+ document: pdf,
20
+ leading: 20
21
+ )
22
+ expect(text_box.leading).to eq(20)
23
+ end
24
+
25
+ it 'is able to set text direction document-wide' do
26
+ pdf.text_direction(:rtl)
27
+ pdf.text_direction = :rtl
28
+ string = "Hello world, how are you?\nI'm fine, thank you."
29
+ text_box = described_class.new(string, document: pdf)
30
+ text_box.render
31
+ text = PDF::Inspector::Text.analyze(pdf.render)
32
+ expect(text.strings[0]).to eq('?uoy era woh ,dlrow olleH')
33
+ expect(text.strings[1]).to eq(".uoy knaht ,enif m'I")
34
+ end
35
+
36
+ it 'is able to reverse multi-byte text' do
37
+ pdf.text_direction(:rtl)
38
+ pdf.text_direction = :rtl
39
+ pdf.text_direction = :rtl
40
+ pdf.font("#{Prawn::DATADIR}/fonts/gkai00mp.ttf", size: 16) do
41
+ pdf.text '写个小'
42
+ end
43
+ text = PDF::Inspector::Text.analyze(pdf.render)
44
+ expect(text.strings[0]).to eq('小个写')
45
+ end
46
+
47
+ it 'option should be able to override document-wide text direction' do
48
+ pdf.text_direction = :rtl
49
+ string = "Hello world, how are you?\nI'm fine, thank you."
50
+ text_box = described_class.new(
51
+ string,
52
+ document: pdf,
53
+ direction: :ltr
54
+ )
55
+ text_box.render
56
+ text = PDF::Inspector::Text.analyze(pdf.render)
57
+ expect(text.strings[0]).to eq('Hello world, how are you?')
58
+ expect(text.strings[1]).to eq("I'm fine, thank you.")
59
+ end
60
+
61
+ it 'only requires enough space for the descender and the ascender '\
62
+ 'when determining whether a line can fit' do
63
+ text = 'Oh hai text rect'
64
+ options = {
65
+ document: pdf,
66
+ height: pdf.font.ascender + pdf.font.descender
67
+ }
68
+ text_box = described_class.new(text, options)
69
+ text_box.render
70
+ expect(text_box.text).to eq('Oh hai text rect')
71
+
72
+ text = "Oh hai text rect\nOh hai text rect"
73
+ options = {
74
+ document: pdf,
75
+ height: pdf.font.height + pdf.font.ascender + pdf.font.descender
76
+ }
77
+ text_box = described_class.new(text, options)
78
+ text_box.render
79
+ expect(text_box.text).to eq("Oh hai text rect\nOh hai text rect")
80
+ end
81
+
82
+ describe '#nothing_printed?' do
83
+ it 'returns true when nothing printed' do
84
+ string = "Hello world, how are you?\nI'm fine, thank you."
85
+ text_box = described_class.new(string, height: 2, document: pdf)
86
+ text_box.render
87
+ expect(text_box.nothing_printed?).to eq true
88
+ end
89
+
90
+ it 'returns false when something printed' do
91
+ string = "Hello world, how are you?\nI'm fine, thank you."
92
+ text_box = described_class.new(string, height: 14, document: pdf)
93
+ text_box.render
94
+ expect(text_box.nothing_printed?).to eq false
95
+ end
96
+ end
97
+
98
+ describe '#everything_printed?' do
99
+ it 'returns false when not everything printed' do
100
+ string = "Hello world, how are you?\nI'm fine, thank you."
101
+ text_box = described_class.new(string, height: 14, document: pdf)
102
+ text_box.render
103
+ expect(text_box.everything_printed?).to eq false
104
+ end
105
+
106
+ it 'returns true when everything printed' do
107
+ string = "Hello world, how are you?\nI'm fine, thank you."
108
+ text_box = described_class.new(string, document: pdf)
109
+ text_box.render
110
+ expect(text_box.everything_printed?).to eq true
111
+ end
112
+ end
113
+
114
+ describe '#line_gap' do
115
+ it '==S the line gap of the font when using a single font and font size' do
116
+ string = "Hello world, how are you?\nI'm fine, thank you."
117
+ text_box = described_class.new(string, document: pdf)
118
+ text_box.render
119
+ expect(text_box.line_gap).to be_within(0.0001).of(pdf.font.line_gap)
120
+ end
121
+ end
122
+
123
+ describe '#render with :align => :justify' do
124
+ it 'draws the word spacing to the document' do
125
+ string = 'hello world ' * 20
126
+ options = { document: pdf, align: :justify }
127
+ text_box = described_class.new(string, options)
128
+ text_box.render
129
+ contents = PDF::Inspector::Text.analyze(pdf.render)
130
+ expect(contents.word_spacing[0]).to be > 0
131
+ end
132
+
133
+ it 'does not justify the last line of a paragraph' do
134
+ string = 'hello world '
135
+ options = { document: pdf, align: :justify }
136
+ text_box = described_class.new(string, options)
137
+ text_box.render
138
+ contents = PDF::Inspector::Text.analyze(pdf.render)
139
+ expect(contents.word_spacing).to be_empty
140
+ end
141
+ end
142
+
143
+ describe '#height without leading' do
144
+ it 'is the sum of the height of each line, not including the space below '\
145
+ 'the last line' do
146
+ text = "Oh hai text rect.\nOh hai text rect."
147
+ options = { document: pdf }
148
+ text_box = described_class.new(text, options)
149
+ text_box.render
150
+ expect(text_box.height).to be_within(0.001)
151
+ .of(pdf.font.height * 2 - pdf.font.line_gap)
152
+ end
153
+ end
154
+
155
+ describe '#height with leading' do
156
+ it 'is the sum of the height of each line plus leading, but not including '\
157
+ 'the space below the last line' do
158
+ text = "Oh hai text rect.\nOh hai text rect."
159
+ leading = 12
160
+ options = { document: pdf, leading: leading }
161
+ text_box = described_class.new(text, options)
162
+ text_box.render
163
+ expect(text_box.height).to be_within(0.001).of(
164
+ (pdf.font.height + leading) * 2 - pdf.font.line_gap - leading
165
+ )
166
+ end
167
+ end
168
+
169
+ context 'with :draw_text_callback' do
170
+ it 'hits the callback whenever text is drawn' do
171
+ draw_block = instance_spy('Draw block')
172
+
173
+ pdf.text_box 'this text is long enough to span two lines',
174
+ width: 150,
175
+ draw_text_callback: ->(text, _) { draw_block.kick(text) }
176
+
177
+ expect(draw_block).to have_received(:kick)
178
+ .with('this text is long enough to')
179
+ expect(draw_block).to have_received(:kick).with('span two lines')
180
+ end
181
+
182
+ it 'hits the callback once per fragment for :inline_format' do
183
+ draw_block = instance_spy('Draw block')
184
+
185
+ pdf.text_box 'this text has <b>fancy</b> formatting',
186
+ inline_format: true,
187
+ width: 500,
188
+ draw_text_callback: ->(text, _) { draw_block.kick(text) }
189
+
190
+ expect(draw_block).to have_received(:kick).with('this text has ')
191
+ expect(draw_block).to have_received(:kick).with('fancy')
192
+ expect(draw_block).to have_received(:kick).with(' formatting')
193
+ end
194
+
195
+ it 'does not call #draw_text!' do
196
+ allow(pdf).to receive(:draw_text!)
197
+ pdf.text_box 'some text',
198
+ width: 500,
199
+ draw_text_callback: ->(_, _) {}
200
+ expect(pdf).to_not have_received(:draw_text!)
201
+ end
202
+ end
203
+
204
+ describe '#valid_options' do
205
+ it 'returns an array' do
206
+ text_box = described_class.new('', document: pdf)
207
+ expect(text_box.valid_options).to be_a_kind_of(Array)
208
+ end
209
+ end
210
+
211
+ describe '#render' do
212
+ it 'does not fail if height is smaller than 1 line' do
213
+ text = 'Oh hai text rect. ' * 10
214
+ options = {
215
+ height: pdf.font.height * 0.5,
216
+ document: pdf
217
+ }
218
+ text_box = described_class.new(text, options)
219
+ text_box.render
220
+ expect(text_box.text).to eq('')
221
+ end
222
+
223
+ it 'draws content to the page' do
224
+ text = 'Oh hai text rect. ' * 10
225
+ options = { document: pdf }
226
+ text_box = described_class.new(text, options)
227
+ text_box.render
228
+ text = PDF::Inspector::Text.analyze(pdf.render)
229
+ expect(text.strings).to_not be_empty
230
+ end
231
+
232
+ it 'does not draw a transformation matrix' do
233
+ text = 'Oh hai text rect. ' * 10
234
+ options = { document: pdf }
235
+ text_box = described_class.new(text, options)
236
+ text_box.render
237
+ matrices = PDF::Inspector::Graphics::Matrix.analyze(pdf.render)
238
+ expect(matrices.matrices.length).to eq(0)
239
+ end
240
+ end
241
+
242
+ describe '#render(:single_line => true)' do
243
+ it 'draws only one line to the page' do
244
+ text = 'Oh hai text rect. ' * 10
245
+ options = {
246
+ document: pdf,
247
+ single_line: true
248
+ }
249
+ text_box = described_class.new(text, options)
250
+ text_box.render
251
+ text = PDF::Inspector::Text.analyze(pdf.render)
252
+ expect(text.strings.length).to eq(1)
253
+ end
254
+ end
255
+
256
+ describe '#render(:dry_run => true)' do
257
+ it 'does not draw any content to the page' do
258
+ text = 'Oh hai text rect. ' * 10
259
+ options = { document: pdf }
260
+ text_box = described_class.new(text, options)
261
+ text_box.render(dry_run: true)
262
+ text = PDF::Inspector::Text.analyze(pdf.render)
263
+ expect(text.strings).to be_empty
264
+ end
265
+
266
+ it 'subsequent calls to render do not raise an ArgumentError exception' do
267
+ text = '™©'
268
+ options = { document: pdf }
269
+ text_box = described_class.new(text, options)
270
+ text_box.render(dry_run: true)
271
+
272
+ expect do
273
+ text_box.render
274
+ end.to_not raise_exception
275
+ end
276
+ end
277
+
278
+ describe '#render(:valign => :bottom)' do
279
+ it '#at should be the same from one dry run to the next' do
280
+ text = 'this is center text ' * 12
281
+ options = {
282
+ width: 162,
283
+ valign: :bottom,
284
+ document: pdf
285
+ }
286
+ text_box = described_class.new(text, options)
287
+
288
+ text_box.render(dry_run: true)
289
+ original_at = text_box.at.dup
290
+
291
+ text_box.render(dry_run: true)
292
+ expect(text_box.at).to eq(original_at)
293
+ end
294
+ end
295
+
296
+ describe '#render(:valign => :center)' do
297
+ it '#at should be the same from one dry run to the next' do
298
+ text = 'this is center text ' * 12
299
+ options = {
300
+ width: 162,
301
+ valign: :center,
302
+ document: pdf
303
+ }
304
+ text_box = described_class.new(text, options)
305
+
306
+ text_box.render(dry_run: true)
307
+ original_at = text_box.at.dup
308
+
309
+ text_box.render(dry_run: true)
310
+ expect(text_box.at).to eq(original_at)
311
+ end
312
+ end
313
+
314
+ describe '#render with :rotate option of 30)' do
315
+ let(:cos) { Math.cos(30 * Math::PI / 180) }
316
+ let(:sin) { Math.sin(30 * Math::PI / 180) }
317
+ let(:text) { 'Oh hai text rect. ' * 10 }
318
+ let(:options) do
319
+ {
320
+ document: pdf,
321
+ rotate: 30,
322
+ at: [300, 70],
323
+ width: 100,
324
+ height: 50
325
+ }
326
+ end
327
+
328
+ context 'with :rotate_around option of :center' do
329
+ it 'draws content to the page rotated about the center of the text' do
330
+ options[:rotate_around] = :center
331
+ text_box = described_class.new(text, options)
332
+ text_box.render
333
+
334
+ matrices = PDF::Inspector::Graphics::Matrix.analyze(pdf.render)
335
+ x = 350
336
+ y = 45
337
+ x_prime = x * cos - y * sin
338
+ y_prime = x * sin + y * cos
339
+ expect(matrices.matrices[0]).to eq([
340
+ 1, 0, 0, 1,
341
+ reduce_precision(x - x_prime),
342
+ reduce_precision(y - y_prime)
343
+ ])
344
+ expect(matrices.matrices[1]).to eq([
345
+ reduce_precision(cos),
346
+ reduce_precision(sin),
347
+ reduce_precision(-sin),
348
+ reduce_precision(cos),
349
+ 0, 0
350
+ ])
351
+
352
+ text = PDF::Inspector::Text.analyze(pdf.render)
353
+ expect(text.strings).to_not be_empty
354
+ end
355
+ end
356
+
357
+ context 'with :rotate_around option of :upper_left' do
358
+ it 'draws content to the page rotated about the upper left corner of '\
359
+ 'the text' do
360
+ options[:rotate_around] = :upper_left
361
+ text_box = described_class.new(text, options)
362
+ text_box.render
363
+
364
+ matrices = PDF::Inspector::Graphics::Matrix.analyze(pdf.render)
365
+ x_prime = 300 * cos - 70 * sin
366
+ y_prime = 300 * sin + 70 * cos
367
+ expect(matrices.matrices[0]).to eq([
368
+ 1, 0, 0, 1,
369
+ reduce_precision(300 - x_prime),
370
+ reduce_precision(70 - y_prime)
371
+ ])
372
+ expect(matrices.matrices[1]).to eq([
373
+ reduce_precision(cos),
374
+ reduce_precision(sin),
375
+ reduce_precision(-sin),
376
+ reduce_precision(cos),
377
+ 0, 0
378
+ ])
379
+
380
+ text = PDF::Inspector::Text.analyze(pdf.render)
381
+ expect(text.strings).to_not be_empty
382
+ end
383
+ end
384
+
385
+ context 'with default :rotate_around' do
386
+ it 'draws content to the page rotated about the upper left corner of '\
387
+ 'the text' do
388
+ text_box = described_class.new(text, options)
389
+ text_box.render
390
+
391
+ matrices = PDF::Inspector::Graphics::Matrix.analyze(pdf.render)
392
+ x_prime = 300 * cos - 70 * sin
393
+ y_prime = 300 * sin + 70 * cos
394
+ expect(matrices.matrices[0]).to eq([
395
+ 1, 0, 0, 1,
396
+ reduce_precision(300 - x_prime),
397
+ reduce_precision(70 - y_prime)
398
+ ])
399
+ expect(matrices.matrices[1]).to eq([
400
+ reduce_precision(cos),
401
+ reduce_precision(sin),
402
+ reduce_precision(-sin),
403
+ reduce_precision(cos),
404
+ 0, 0
405
+ ])
406
+
407
+ text = PDF::Inspector::Text.analyze(pdf.render)
408
+ expect(text.strings).to_not be_empty
409
+ end
410
+ end
411
+
412
+ context 'with :rotate_around option of :upper_right' do
413
+ it 'draws content to the page rotated about the upper right corner of '\
414
+ 'the text' do
415
+ options[:rotate_around] = :upper_right
416
+ text_box = described_class.new(text, options)
417
+ text_box.render
418
+
419
+ matrices = PDF::Inspector::Graphics::Matrix.analyze(pdf.render)
420
+ x = 400
421
+ y = 70
422
+ x_prime = x * cos - y * sin
423
+ y_prime = x * sin + y * cos
424
+ expect(matrices.matrices[0]).to eq([
425
+ 1, 0, 0, 1,
426
+ reduce_precision(x - x_prime),
427
+ reduce_precision(y - y_prime)
428
+ ])
429
+ expect(matrices.matrices[1]).to eq([
430
+ reduce_precision(cos),
431
+ reduce_precision(sin),
432
+ reduce_precision(-sin),
433
+ reduce_precision(cos),
434
+ 0, 0
435
+ ])
436
+
437
+ text = PDF::Inspector::Text.analyze(pdf.render)
438
+ expect(text.strings).to_not be_empty
439
+ end
440
+ end
441
+
442
+ context 'with :rotate_around option of :lower_right' do
443
+ it 'draws content to the page rotated about the lower right corner of '\
444
+ 'the text' do
445
+ options[:rotate_around] = :lower_right
446
+ text_box = described_class.new(text, options)
447
+ text_box.render
448
+
449
+ matrices = PDF::Inspector::Graphics::Matrix.analyze(pdf.render)
450
+ x = 400
451
+ y = 20
452
+ x_prime = x * cos - y * sin
453
+ y_prime = x * sin + y * cos
454
+ expect(matrices.matrices[0]).to eq([
455
+ 1, 0, 0, 1,
456
+ reduce_precision(x - x_prime),
457
+ reduce_precision(y - y_prime)
458
+ ])
459
+ expect(matrices.matrices[1]).to eq([
460
+ reduce_precision(cos),
461
+ reduce_precision(sin),
462
+ reduce_precision(-sin),
463
+ reduce_precision(cos),
464
+ 0, 0
465
+ ])
466
+
467
+ text = PDF::Inspector::Text.analyze(pdf.render)
468
+ expect(text.strings).to_not be_empty
469
+ end
470
+ end
471
+
472
+ context 'with :rotate_around option of :lower_left' do
473
+ it 'draws content to the page rotated about the lower left corner of '\
474
+ 'the text' do
475
+ options[:rotate_around] = :lower_left
476
+ text_box = described_class.new(text, options)
477
+ text_box.render
478
+
479
+ matrices = PDF::Inspector::Graphics::Matrix.analyze(pdf.render)
480
+ x = 300
481
+ y = 20
482
+ x_prime = x * cos - y * sin
483
+ y_prime = x * sin + y * cos
484
+ expect(matrices.matrices[0]).to eq([
485
+ 1, 0, 0, 1,
486
+ reduce_precision(x - x_prime),
487
+ reduce_precision(y - y_prime)
488
+ ])
489
+ expect(matrices.matrices[1]).to eq([
490
+ reduce_precision(cos),
491
+ reduce_precision(sin),
492
+ reduce_precision(-sin),
493
+ reduce_precision(cos),
494
+ 0, 0
495
+ ])
496
+
497
+ text = PDF::Inspector::Text.analyze(pdf.render)
498
+ expect(text.strings).to_not be_empty
499
+ end
500
+ end
501
+ end
502
+
503
+ describe 'default height' do
504
+ it 'is the height from the bottom bound to document.y' do
505
+ target_height = pdf.y - pdf.bounds.bottom
506
+ text = "Oh hai\n" * 60
507
+ text_box = described_class.new(text, document: pdf)
508
+ text_box.render
509
+ expect(text_box.height).to be_within(pdf.font.height).of(target_height)
510
+ end
511
+
512
+ it 'uses the margin-box bottom if only in a stretchy bbox' do
513
+ pdf.bounding_box([0, pdf.cursor], width: pdf.bounds.width) do
514
+ target_height = pdf.y - pdf.bounds.bottom
515
+ text = "Oh hai\n" * 60
516
+ text_box = described_class.new(text, document: pdf)
517
+ text_box.render
518
+ expect(text_box.height).to be_within(pdf.font.height).of(target_height)
519
+ end
520
+ end
521
+
522
+ it 'uses the parent-box bottom if in a stretchy bbox and overflow is '\
523
+ ':expand, even with an explicit height' do
524
+ pdf.bounding_box([0, pdf.cursor], width: pdf.bounds.width) do
525
+ target_height = pdf.y - pdf.bounds.bottom
526
+ text = "Oh hai\n" * 60
527
+ text_box = described_class.new(
528
+ text,
529
+ document: pdf,
530
+ height: 100,
531
+ overflow: :expand
532
+ )
533
+ text_box.render
534
+ expect(text_box.height).to be_within(pdf.font.height).of(target_height)
535
+ end
536
+ end
537
+
538
+ it 'uses the innermost non-stretchy bbox, not the margin box' do
539
+ pdf.bounding_box(
540
+ [0, pdf.cursor],
541
+ width: pdf.bounds.width,
542
+ height: 200
543
+ ) do
544
+ pdf.bounding_box([0, pdf.cursor], width: pdf.bounds.width) do
545
+ text = "Oh hai\n" * 60
546
+ text_box = described_class.new(text, document: pdf)
547
+ text_box.render
548
+ expect(text_box.height).to be_within(pdf.font.height).of(200)
549
+ end
550
+ end
551
+ end
552
+ end
553
+
554
+ describe 'default at' do
555
+ it 'is the left corner of the bounds, and the current document.y' do
556
+ target_at = [pdf.bounds.left, pdf.y]
557
+ text = 'Oh hai text rect. ' * 100
558
+ options = { document: pdf }
559
+ text_box = described_class.new(text, options)
560
+ text_box.render
561
+ expect(text_box.at).to eq(target_at)
562
+ end
563
+ end
564
+
565
+ context 'with text than can fit in the box' do
566
+ let(:text) { 'Oh hai text rect. ' * 10 }
567
+ let(:options) do
568
+ {
569
+ width: 162.0,
570
+ height: 162.0,
571
+ document: pdf
572
+ }
573
+ end
574
+
575
+ it 'printed text should match requested text, except that preceding and ' \
576
+ 'trailing white space will be stripped from each line, and newlines ' \
577
+ 'may be inserted' do
578
+ text_box = described_class.new(" #{text}", options)
579
+ text_box.render
580
+ expect(text_box.text.tr("\n", ' ')).to eq(text.strip)
581
+ end
582
+
583
+ it 'render returns an empty string because no text remains unprinted' do
584
+ text_box = described_class.new(text, options)
585
+ expect(text_box.render).to eq('')
586
+ end
587
+
588
+ it 'is truncated when the leading is set high enough to prevent all the '\
589
+ 'lines from being printed' do
590
+ options[:leading] = 40
591
+ text_box = described_class.new(text, options)
592
+ text_box.render
593
+ expect(text_box.text.tr("\n", ' ')).to_not eq(text.strip)
594
+ end
595
+ end
596
+
597
+ context 'with text that fits exactly in the box' do
598
+ let(:lines) { 3 }
599
+ let(:interlines) { lines - 1 }
600
+ let(:text) { (1..lines).to_a.join("\n") }
601
+ let(:options) do
602
+ {
603
+ width: 162.0,
604
+ height: pdf.font.ascender + pdf.font.height * interlines +
605
+ pdf.font.descender,
606
+ document: pdf
607
+ }
608
+ end
609
+
610
+ it 'has the expected height' do
611
+ expected_height = options.delete(:height)
612
+ text_box = described_class.new(text, options)
613
+ text_box.render
614
+ expect(text_box.height).to be_within(0.0001).of(expected_height)
615
+ end
616
+
617
+ it 'prints everything' do
618
+ text_box = described_class.new(text, options)
619
+ text_box.render
620
+ expect(text_box.text).to eq(text)
621
+ end
622
+
623
+ describe 'with leading' do
624
+ before do
625
+ options[:leading] = 15
626
+ end
627
+
628
+ it 'does not overflow when enough height is added' do
629
+ options[:height] += options[:leading] * interlines
630
+ text_box = described_class.new(text, options)
631
+ text_box.render
632
+ expect(text_box.text).to eq(text)
633
+ end
634
+
635
+ it 'overflows when insufficient height is added' do
636
+ options[:height] += options[:leading] * interlines - 1
637
+ text_box = described_class.new(text, options)
638
+ text_box.render
639
+ expect(text_box.text).to_not eq(text)
640
+ end
641
+ end
642
+
643
+ context 'with negative leading' do
644
+ before do
645
+ options[:leading] = -4
646
+ end
647
+
648
+ it 'does not overflow when enough height is removed' do
649
+ options[:height] += options[:leading] * interlines
650
+ text_box = described_class.new(text, options)
651
+ text_box.render
652
+ expect(text_box.text).to eq(text)
653
+ end
654
+
655
+ it 'overflows when too much height is removed' do
656
+ options[:height] += options[:leading] * interlines - 1
657
+ text_box = described_class.new(text, options)
658
+ text_box.render
659
+ expect(text_box.text).to_not eq(text)
660
+ end
661
+ end
662
+ end
663
+
664
+ context 'when printing UTF-8 string with higher bit characters' do
665
+ let(:text) { '©' }
666
+
667
+ let(:text_box) do
668
+ # not enough height to print any text, so we can directly compare against
669
+ # the input string
670
+ bounding_height = 1.0
671
+ options = {
672
+ height: bounding_height,
673
+ document: pdf
674
+ }
675
+ described_class.new(text, options)
676
+ end
677
+
678
+ before do
679
+ file = "#{Prawn::DATADIR}/fonts/Panic+Sans.dfont"
680
+ pdf.font_families['Panic Sans'] = {
681
+ normal: { file: file, font: 'PanicSans' },
682
+ italic: { file: file, font: 'PanicSans-Italic' },
683
+ bold: { file: file, font: 'PanicSans-Bold' },
684
+ bold_italic: { file: file, font: 'PanicSans-BoldItalic' }
685
+ }
686
+ end
687
+
688
+ describe 'when using a TTF font' do
689
+ it 'unprinted text should be in UTF-8 encoding' do
690
+ pdf.font('Panic Sans')
691
+ remaining_text = text_box.render
692
+ expect(remaining_text).to eq(text)
693
+ end
694
+ end
695
+
696
+ describe 'when using an AFM font' do
697
+ it 'unprinted text should be in UTF-8 encoding' do
698
+ remaining_text = text_box.render
699
+ expect(remaining_text).to eq(text)
700
+ end
701
+ end
702
+ end
703
+
704
+ context 'with more text than can fit in the box' do
705
+ let(:text) { 'Oh hai text rect. ' * 30 }
706
+ let(:bounding_height) { 162.0 }
707
+ let(:options) do
708
+ {
709
+ width: 162.0,
710
+ height: bounding_height,
711
+ document: pdf
712
+ }
713
+ end
714
+
715
+ context 'when truncated overflow' do
716
+ let(:text_box) do
717
+ described_class.new(text, options.merge(overflow: :truncate))
718
+ end
719
+
720
+ it 'is truncated' do
721
+ text_box.render
722
+ expect(text_box.text.tr("\n", ' ')).to_not eq(text.strip)
723
+ end
724
+
725
+ it 'render does not return an empty string because some text remains '\
726
+ 'unprinted' do
727
+ expect(text_box.render).to_not be_empty
728
+ end
729
+
730
+ it '#height should be no taller than the specified height' do
731
+ text_box.render
732
+ expect(text_box.height).to be <= bounding_height
733
+ end
734
+
735
+ it '#height should be within one font height of the specified height' do
736
+ text_box.render
737
+ expect(bounding_height).to be_within(pdf.font.height)
738
+ .of(text_box.height)
739
+ end
740
+
741
+ context 'with :rotate option' do
742
+ it 'unrendered text should be the same as when not rotated' do
743
+ remaining_text = text_box.render
744
+
745
+ rotate = 30
746
+ options[:document] = pdf
747
+ options[:rotate] = rotate
748
+ options[:at] = [300, 70]
749
+ rotated_text_box = described_class.new(text, options)
750
+ expect(rotated_text_box.render).to eq(remaining_text)
751
+ end
752
+ end
753
+ end
754
+
755
+ context 'when truncated with text and size taken from the manual' do
756
+ it 'returns the right text' do
757
+ text = 'This is the beginning of the text. It will be cut somewhere ' \
758
+ 'and the rest of the text will procede to be rendered this time by '\
759
+ 'calling another method.' + ' . ' * 50
760
+ options[:width] = 300
761
+ options[:height] = 50
762
+ options[:size] = 18
763
+ text_box = described_class.new(text, options)
764
+ remaining_text = text_box.render
765
+ expect(remaining_text).to eq(
766
+ 'text will procede to be rendered this time by calling another ' \
767
+ 'method. . . . . . . . . . . . . . . . . . . . ' \
768
+ '. . . . . . . . . . . . . . . . . . . . . . ' \
769
+ '. . . . . . . . . '
770
+ )
771
+ end
772
+ end
773
+
774
+ context 'when expand overflow' do
775
+ let(:text_box) do
776
+ described_class.new(text, options.merge(overflow: :expand))
777
+ end
778
+
779
+ it 'height expands to encompass all the text '\
780
+ '(but not exceed the height of the page)' do
781
+ text_box.render
782
+ expect(text_box.height).to be > bounding_height
783
+ end
784
+
785
+ it 'displays the entire string (as long as there was space remaining on '\
786
+ 'the page to print all the text)' do
787
+ text_box.render
788
+ expect(text_box.text.tr("\n", ' ')).to eq(text.strip)
789
+ end
790
+
791
+ it 'render returns an empty string because no text remains unprinted '\
792
+ '(as long as there was space remaining on the page to print all '\
793
+ 'the text)' do
794
+ expect(text_box.render).to eq('')
795
+ end
796
+ end
797
+
798
+ context 'when shrink_to_fit overflow' do
799
+ let(:text_box) do
800
+ described_class.new(
801
+ text,
802
+ options.merge(
803
+ overflow: :shrink_to_fit,
804
+ min_font_size: 2
805
+ )
806
+ )
807
+ end
808
+
809
+ it 'displays the entire text' do
810
+ text_box.render
811
+ expect(text_box.text.tr("\n", ' ')).to eq(text.strip)
812
+ end
813
+
814
+ it 'render returns an empty string because no text remains unprinted' do
815
+ expect(text_box.render).to eq('')
816
+ end
817
+
818
+ it 'does not drop below the minimum font size' do
819
+ options[:overflow] = :shrink_to_fit
820
+ options[:min_font_size] = 10.1
821
+ text_box = described_class.new(text, options)
822
+ text_box.render
823
+
824
+ actual_text = PDF::Inspector::Text.analyze(pdf.render)
825
+ expect(actual_text.font_settings[0][:size]).to eq(10.1)
826
+ end
827
+ end
828
+ end
829
+
830
+ context 'with enough space to fit the text but using the ' \
831
+ 'shrink_to_fit overflow' do
832
+ it 'does not shrink the text when there is no need to' do
833
+ bounding_height = 162.0
834
+ options = {
835
+ width: 162.0,
836
+ height: bounding_height,
837
+ overflow: :shrink_to_fit,
838
+ min_font_size: 5,
839
+ document: pdf
840
+ }
841
+ text_box = described_class.new("hello\nworld", options)
842
+ text_box.render
843
+
844
+ text = PDF::Inspector::Text.analyze(pdf.render)
845
+ expect(text.font_settings[0][:size]).to eq(12)
846
+ end
847
+ end
848
+
849
+ context 'with a solid block of Chinese characters' do
850
+ it 'printed text should match requested text, except for newlines' do
851
+ text = '写中国字' * 10
852
+ options = {
853
+ width: 162.0,
854
+ height: 162.0,
855
+ document: pdf,
856
+ overflow: :truncate
857
+ }
858
+ pdf.font "#{Prawn::DATADIR}/fonts/gkai00mp.ttf"
859
+ text_box = described_class.new(text, options)
860
+ text_box.render
861
+ expect(text_box.text.delete("\n")).to eq(text)
862
+ end
863
+ end
864
+
865
+ describe 'drawing bounding boxes' do
866
+ it 'restores the margin box when bounding box exits' do
867
+ margin_box = pdf.bounds
868
+
869
+ pdf.text_box 'Oh hai text box. ' * 11, height: pdf.font.height * 10
870
+
871
+ expect(pdf.bounds).to eq(margin_box)
872
+ end
873
+ end
874
+
875
+ describe '#render with :character_spacing option' do
876
+ it 'draws the character spacing to the document' do
877
+ string = 'hello world'
878
+ options = { document: pdf, character_spacing: 10 }
879
+ text_box = described_class.new(string, options)
880
+ text_box.render
881
+ contents = PDF::Inspector::Text.analyze(pdf.render)
882
+ expect(contents.character_spacing[0]).to eq(10)
883
+ end
884
+
885
+ it 'takes character spacing into account when wrapping' do
886
+ pdf.font 'Courier'
887
+ text_box = described_class.new(
888
+ 'hello world',
889
+ width: 100,
890
+ overflow: :expand,
891
+ character_spacing: 10,
892
+ document: pdf
893
+ )
894
+ text_box.render
895
+ expect(text_box.text).to eq("hello\nworld")
896
+ end
897
+ end
898
+
899
+ describe 'wrapping' do
900
+ it 'wraps text' do
901
+ text = 'Please wrap this text about HERE. ' \
902
+ 'More text that should be wrapped'
903
+ expect = "Please wrap this text about\n"\
904
+ "HERE. More text that should be\nwrapped"
905
+
906
+ pdf.font 'Courier'
907
+ text_box = described_class.new(
908
+ text,
909
+ width: 220,
910
+ overflow: :expand,
911
+ document: pdf
912
+ )
913
+ text_box.render
914
+ expect(text_box.text).to eq(expect)
915
+ end
916
+
917
+ # white space was being stripped after the entire line was generated,
918
+ # meaning that leading white space characters reduced the amount of space on
919
+ # the line for other characters, so wrapping "hello hello" resulted in
920
+ # "hello\n\nhello", rather than "hello\nhello"
921
+ #
922
+ it 'white space at beginning of line should not be taken into account ' \
923
+ 'when computing line width' do
924
+ text = 'hello hello'
925
+ expect = "hello\nhello"
926
+
927
+ pdf.font 'Courier'
928
+ text_box = described_class.new(
929
+ text,
930
+ width: 40,
931
+ overflow: :expand,
932
+ document: pdf
933
+ )
934
+ text_box.render
935
+ expect(text_box.text).to eq(expect)
936
+ end
937
+
938
+ it 'respects end of line when wrapping text' do
939
+ text = "Please wrap only before\nTHIS word. Don't wrap this"
940
+ expect = text
941
+
942
+ pdf.font 'Courier'
943
+ text_box = described_class.new(
944
+ text,
945
+ width: 220,
946
+ overflow: :expand,
947
+ document: pdf
948
+ )
949
+ text_box.render
950
+ expect(text_box.text).to eq(expect)
951
+ end
952
+
953
+ it 'respects multiple newlines when wrapping text' do
954
+ text = "Please wrap only before THIS\n\nword. Don't wrap this"
955
+ expect = "Please wrap only before\nTHIS\n\nword. Don't wrap this"
956
+
957
+ pdf.font 'Courier'
958
+ text_box = described_class.new(
959
+ text,
960
+ width: 200,
961
+ overflow: :expand,
962
+ document: pdf
963
+ )
964
+ text_box.render
965
+ expect(text_box.text).to eq(expect)
966
+ end
967
+
968
+ it 'respects multiple newlines when wrapping text when those newlines '\
969
+ 'coincide with a line break' do
970
+ text = "Please wrap only before\n\nTHIS word. Don't wrap this"
971
+ expect = text
972
+
973
+ pdf.font 'Courier'
974
+ text_box = described_class.new(
975
+ text,
976
+ width: 220,
977
+ overflow: :expand,
978
+ document: pdf
979
+ )
980
+ text_box.render
981
+ expect(text_box.text).to eq(expect)
982
+ end
983
+
984
+ it 'respects initial newlines' do
985
+ text = "\nThis should be on line 2"
986
+ expect = text
987
+
988
+ pdf.font 'Courier'
989
+ text_box = described_class.new(
990
+ text,
991
+ width: 220,
992
+ overflow: :expand,
993
+ document: pdf
994
+ )
995
+ text_box.render
996
+ expect(text_box.text).to eq(expect)
997
+ end
998
+
999
+ it 'wraps lines comprised of a single word of the bounds when '\
1000
+ 'wrapping text' do
1001
+ text = 'You_can_wrap_this_text_HERE'
1002
+ expect = "You_can_wrap_this_text_HE\nRE"
1003
+
1004
+ pdf.font 'Courier'
1005
+ text_box = described_class.new(
1006
+ text,
1007
+ width: 180,
1008
+ overflow: :expand,
1009
+ document: pdf
1010
+ )
1011
+ text_box.render
1012
+ expect(text_box.text).to eq(expect)
1013
+ end
1014
+
1015
+ it 'wraps lines comprised of a single non-alpha word of the bounds when '\
1016
+ 'wrapping text' do
1017
+ text = '©' * 30
1018
+
1019
+ pdf.font 'Courier'
1020
+ text_box = described_class.new(
1021
+ text,
1022
+ width: 180,
1023
+ overflow: :expand,
1024
+ document: pdf
1025
+ )
1026
+
1027
+ text_box.render
1028
+
1029
+ expected = "#{'©' * 25}\n#{'©' * 5}"
1030
+ expected = pdf.font.normalize_encoding(expected)
1031
+ expected = expected.force_encoding(Encoding::UTF_8)
1032
+ expect(text_box.text).to eq(expected)
1033
+ end
1034
+
1035
+ it 'wraps non-unicode strings using single-byte word-wrapping' do
1036
+ text = 'continúa esforzandote ' * 5
1037
+ text_box = described_class.new(
1038
+ text,
1039
+ width: 180,
1040
+ document: pdf
1041
+ )
1042
+ text_box.render
1043
+ results_with_accent = text_box.text
1044
+
1045
+ text = 'continua esforzandote ' * 5
1046
+ text_box = described_class.new(
1047
+ text,
1048
+ width: 180,
1049
+ document: pdf
1050
+ )
1051
+ text_box.render
1052
+ results_without_accent = text_box.text
1053
+
1054
+ expect(first_line(results_with_accent).length)
1055
+ .to eq(first_line(results_without_accent).length)
1056
+ end
1057
+
1058
+ it 'allows you to disable wrapping by char' do
1059
+ text = 'You_cannot_wrap_this_text_at_all_because_we_are_disabling_' \
1060
+ 'wrapping_by_char_and_there_are_no_word_breaks'
1061
+
1062
+ pdf.font 'Courier'
1063
+ text_box = described_class.new(
1064
+ text,
1065
+ width: 180,
1066
+ overflow: :shrink_to_fit,
1067
+ disable_wrap_by_char: true,
1068
+ document: pdf
1069
+ )
1070
+ expect { text_box.render }.to raise_error(Prawn::Errors::CannotFit)
1071
+ end
1072
+
1073
+ it 'retains full words with :shrink_to_fit if char wrapping is disabled' do
1074
+ text = 'Wrapped_words'
1075
+ expect = 'Wrapped_words'
1076
+
1077
+ pdf.font 'Courier'
1078
+ text_box = described_class.new(
1079
+ text,
1080
+ width: 50,
1081
+ height: 50,
1082
+ size: 50,
1083
+ overflow: :shrink_to_fit,
1084
+ disable_wrap_by_char: true,
1085
+ document: pdf
1086
+ )
1087
+ text_box.render
1088
+ expect(text_box.text).to eq(expect)
1089
+ end
1090
+ end
1091
+
1092
+ describe 'Text::Box#render with :mode option' do
1093
+ it 'alters the text rendering mode of the document' do
1094
+ string = 'hello world'
1095
+ options = { document: pdf, mode: :fill_stroke }
1096
+ text_box = described_class.new(string, options)
1097
+ text_box.render
1098
+ contents = PDF::Inspector::Text.analyze(pdf.render)
1099
+ expect(contents.text_rendering_mode).to eq([2, 0])
1100
+ end
1101
+ end
1102
+
1103
+ def reduce_precision(float)
1104
+ float.round(5)
1105
+ end
1106
+
1107
+ def first_line(str)
1108
+ str.lines.first
1109
+ end
1110
+ end