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,54 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+ require 'pathname'
5
+
6
+ describe Prawn::FontMetricCache do
7
+ let(:document) { Prawn::Document.new }
8
+ let(:font_metric_cache) { described_class.new(document) }
9
+
10
+ it 'starts with an empty cache' do
11
+ expect(font_metric_cache.instance_variable_get(:@cache)).to be_empty
12
+ end
13
+
14
+ it 'caches the width of the provided string' do
15
+ font_metric_cache.width_of('M', {})
16
+
17
+ expect(font_metric_cache.instance_variable_get(:@cache).size).to eq(1)
18
+ end
19
+
20
+ it 'onlies cache a single copy of the same string' do
21
+ font_metric_cache.width_of('M', {})
22
+ font_metric_cache.width_of('M', {})
23
+
24
+ expect(font_metric_cache.instance_variable_get(:@cache).size).to eq(1)
25
+ end
26
+
27
+ it 'caches different copies for different strings' do
28
+ font_metric_cache.width_of('M', {})
29
+ font_metric_cache.width_of('W', {})
30
+
31
+ expect(font_metric_cache.instance_variable_get(:@cache).entries.size)
32
+ .to eq 2
33
+ end
34
+
35
+ it 'caches different copies of the same string with different font sizes' do
36
+ font_metric_cache.width_of('M', {})
37
+
38
+ document.font_size 24
39
+ font_metric_cache.width_of('M', {})
40
+
41
+ expect(font_metric_cache.instance_variable_get(:@cache).entries.size)
42
+ .to eq 2
43
+ end
44
+
45
+ it 'caches different copies of the same string with different fonts' do
46
+ font_metric_cache.width_of('M', {})
47
+
48
+ document.font 'Courier'
49
+ font_metric_cache.width_of('M', {})
50
+
51
+ expect(font_metric_cache.instance_variable_get(:@cache).entries.size)
52
+ .to eq 2
53
+ end
54
+ end
@@ -0,0 +1,544 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+ require 'pathname'
5
+
6
+ describe Prawn::Font do
7
+ let(:pdf) { create_pdf }
8
+
9
+ describe 'Font behavior' do
10
+ it 'defaults to Helvetica if no font is specified' do
11
+ pdf = Prawn::Document.new
12
+ expect(pdf.font.name).to eq('Helvetica')
13
+ end
14
+ end
15
+
16
+ describe 'Font objects' do
17
+ it 'is equal' do
18
+ font1 = Prawn::Document.new.font
19
+ font2 = Prawn::Document.new.font
20
+
21
+ expect(font1).to eql(font2)
22
+ end
23
+
24
+ it 'alwayses be the same key' do
25
+ font1 = Prawn::Document.new.font
26
+ font2 = Prawn::Document.new.font
27
+
28
+ hash = {}
29
+
30
+ hash[font1] = 'Original'
31
+ hash[font2] = 'Overwritten'
32
+
33
+ expect(hash.size).to eq(1)
34
+
35
+ expect(hash[font1]).to eq('Overwritten')
36
+ expect(hash[font2]).to eq('Overwritten')
37
+ end
38
+ end
39
+
40
+ describe '#width_of' do
41
+ it 'takes character spacing into account' do
42
+ original_width = pdf.width_of('hello world')
43
+ pdf.character_spacing(7) do
44
+ expect(pdf.width_of('hello world')).to eq(original_width + 10 * 7)
45
+ end
46
+ end
47
+
48
+ it 'excludes newlines' do
49
+ # Use a TTF font that has a non-zero width for \n
50
+ pdf.font("#{Prawn::DATADIR}/fonts/gkai00mp.ttf")
51
+
52
+ expect(pdf.width_of("\nhello world\n")).to eq(
53
+ pdf.width_of('hello world')
54
+ )
55
+ end
56
+
57
+ it 'takes formatting into account' do
58
+ normal_hello = pdf.width_of('hello')
59
+ inline_bold_hello = pdf.width_of('<b>hello</b>', inline_format: true)
60
+ expect(inline_bold_hello).to be > normal_hello
61
+
62
+ pdf.font('Helvetica', style: :bold) do
63
+ expect(inline_bold_hello).to eq(pdf.width_of('hello'))
64
+ end
65
+ end
66
+
67
+ it 'accepts :style as an argument' do
68
+ styled_bold_hello = pdf.width_of('hello', style: :bold)
69
+ pdf.font('Helvetica', style: :bold) do
70
+ expect(styled_bold_hello).to eq(pdf.width_of('hello'))
71
+ end
72
+ end
73
+
74
+ it 'reports missing font with style' do
75
+ expect do
76
+ pdf.font('Nada', style: :bold) do
77
+ pdf.width_of('hello')
78
+ end
79
+ end.to raise_error(Prawn::Errors::UnknownFont, /Nada \(bold\)/)
80
+ end
81
+
82
+ it 'calculates styled widths correctly using TTFs' do
83
+ pdf.font_families.update(
84
+ 'DejaVu Sans' => {
85
+ normal: "#{Prawn::DATADIR}/fonts/DejaVuSans.ttf",
86
+ bold: "#{Prawn::DATADIR}/fonts/DejaVuSans-Bold.ttf"
87
+ }
88
+ )
89
+ styled_bold_hello = nil
90
+ bold_hello = nil
91
+ plain_hello = nil
92
+
93
+ pdf.font('DejaVu Sans') do
94
+ styled_bold_hello = pdf.width_of('hello', style: :bold)
95
+ end
96
+ pdf.font('DejaVu Sans', style: :bold) do
97
+ bold_hello = pdf.width_of('hello')
98
+ end
99
+
100
+ pdf.font('DejaVu Sans') do
101
+ plain_hello = pdf.width_of('hello')
102
+ end
103
+
104
+ expect(plain_hello).to_not eq(bold_hello)
105
+
106
+ expect(styled_bold_hello).to eq(bold_hello)
107
+ end
108
+
109
+ it 'does not treat minus as if it were a hyphen', issue: 578 do
110
+ expect(pdf.width_of('-0.75')).to be < pdf.width_of('25.00')
111
+ end
112
+ end
113
+
114
+ describe '#font_size' do
115
+ it 'allows setting font size in DSL style' do
116
+ pdf.font_size 20
117
+ expect(pdf.font_size).to eq(20)
118
+ end
119
+ end
120
+
121
+ describe 'font style support' do
122
+ it 'complains if there is no @current_page' do
123
+ pdf_without_page = Prawn::Document.new(skip_page_creation: true)
124
+
125
+ expect { pdf_without_page.font 'Helvetica' }
126
+ .to raise_error(Prawn::Errors::NotOnPage)
127
+ end
128
+
129
+ it 'allows specifying font style by style name and font family' do
130
+ pdf.font 'Courier', style: :bold
131
+ pdf.text 'In Courier bold'
132
+
133
+ pdf.font 'Courier', style: :bold_italic
134
+ pdf.text 'In Courier bold-italic'
135
+
136
+ pdf.font 'Courier', style: :italic
137
+ pdf.text 'In Courier italic'
138
+
139
+ pdf.font 'Courier', style: :normal
140
+ pdf.text 'In Normal Courier'
141
+
142
+ pdf.font 'Helvetica'
143
+ pdf.text 'In Normal Helvetica'
144
+
145
+ text = PDF::Inspector::Text.analyze(pdf.render)
146
+ expect(text.font_settings.map { |e| e[:name] }).to eq(
147
+ %i[
148
+ Courier-Bold Courier-BoldOblique Courier-Oblique
149
+ Courier Helvetica
150
+ ]
151
+ )
152
+ end
153
+
154
+ it 'allows font familes to be defined in a single dfont' do
155
+ file = "#{Prawn::DATADIR}/fonts/Panic+Sans.dfont"
156
+ pdf.font_families['Panic Sans'] = {
157
+ normal: { file: file, font: 'PanicSans' },
158
+ italic: { file: file, font: 'PanicSans-Italic' },
159
+ bold: { file: file, font: 'PanicSans-Bold' },
160
+ bold_italic: { file: file, font: 'PanicSans-BoldItalic' }
161
+ }
162
+
163
+ pdf.font 'Panic Sans', style: :italic
164
+ pdf.text 'In PanicSans-Italic'
165
+
166
+ text = PDF::Inspector::Text.analyze(pdf.render)
167
+ name = text.font_settings.map { |e| e[:name] }.first.to_s
168
+ name = name.sub(/\w+\+/, 'subset+')
169
+ expect(name).to eq('subset+PanicSans-Italic')
170
+ end
171
+
172
+ it 'allows font familes to be defined in a single ttc' do
173
+ file = "#{Prawn::DATADIR}/fonts/DejaVuSans.ttc"
174
+ pdf.font_families['DejaVu Sans'] = {
175
+ normal: { file: file, font: 0 },
176
+ bold: { file: file, font: 1 }
177
+ }
178
+
179
+ pdf.font 'DejaVu Sans', style: :bold
180
+ pdf.text 'In PanicSans-Bold'
181
+
182
+ text = PDF::Inspector::Text.analyze(pdf.render)
183
+ name = text.font_settings.map { |e| e[:name] }.first.to_s
184
+ name = name.sub(/\w+\+/, 'subset+')
185
+ expect(name).to eq('subset+DejaVuSans-Bold')
186
+ end
187
+
188
+ it 'allows fonts to be indexed by name in a ttc file' do
189
+ file = "#{Prawn::DATADIR}/fonts/DejaVuSans.ttc"
190
+ pdf.font_families['DejaVu Sans'] = {
191
+ normal: { file: file, font: 'DejaVu Sans' },
192
+ bold: { file: file, font: 'DejaVu Sans Bold' }
193
+ }
194
+
195
+ pdf.font 'DejaVu Sans', style: :bold
196
+ pdf.text 'In PanicSans-Bold'
197
+
198
+ text = PDF::Inspector::Text.analyze(pdf.render)
199
+ name = text.font_settings.map { |e| e[:name] }.first.to_s
200
+ name = name.sub(/\w+\+/, 'subset+')
201
+ expect(name).to eq('subset+DejaVuSans-Bold')
202
+ end
203
+
204
+ it 'accepts Pathname objects for font files' do
205
+ file = Pathname.new("#{Prawn::DATADIR}/fonts/DejaVuSans.ttf")
206
+ pdf.font_families['DejaVu Sans'] = {
207
+ normal: file
208
+ }
209
+
210
+ pdf.font 'DejaVu Sans'
211
+ pdf.text 'In DejaVu Sans'
212
+
213
+ text = PDF::Inspector::Text.analyze(pdf.render)
214
+ name = text.font_settings.map { |e| e[:name] }.first.to_s
215
+ name = name.sub(/\w+\+/, 'subset+')
216
+ expect(name).to eq('subset+DejaVuSans')
217
+ end
218
+
219
+ it 'accepts IO objects for font files' do
220
+ File.open "#{Prawn::DATADIR}/fonts/DejaVuSans.ttf" do |io|
221
+ pdf.font_families['DejaVu Sans'] = {
222
+ normal: described_class.load(pdf, io)
223
+ }
224
+
225
+ pdf.font 'DejaVu Sans'
226
+ pdf.text 'In DejaVu Sans'
227
+
228
+ text = PDF::Inspector::Text.analyze(pdf.render)
229
+ name = text.font_settings.map { |e| e[:name] }.first.to_s
230
+ name = name.sub(/\w+\+/, 'subset+')
231
+ expect(name).to eq('subset+DejaVuSans')
232
+ end
233
+ end
234
+ end
235
+
236
+ describe 'Transactional font handling' do
237
+ it 'allows setting of size directly when font is created' do
238
+ pdf.font 'Courier', size: 16
239
+ expect(pdf.font_size).to eq(16)
240
+ end
241
+
242
+ it 'allows temporary setting of a new font using a transaction' do
243
+ pdf.font 'Helvetica', size: 12
244
+
245
+ pdf.font 'Courier', size: 16 do
246
+ expect(pdf.font.name).to eq('Courier')
247
+ expect(pdf.font_size).to eq(16)
248
+ end
249
+
250
+ expect(pdf.font.name).to eq('Helvetica')
251
+ expect(pdf.font_size).to eq(12)
252
+ end
253
+
254
+ it 'masks font size when using a transacation' do
255
+ pdf.font 'Courier', size: 16 do
256
+ expect(pdf.font_size).to eq(16)
257
+ end
258
+
259
+ pdf.font 'Times-Roman'
260
+ pdf.font 'Courier'
261
+
262
+ expect(pdf.font_size).to eq(12)
263
+ end
264
+ end
265
+
266
+ describe 'Document#page_fonts' do
267
+ it 'registers fonts properly by page' do
268
+ pdf.font 'Courier'
269
+ pdf.text('hello')
270
+
271
+ pdf.font 'Helvetica'
272
+ pdf.text('hello')
273
+
274
+ pdf.font 'Times-Roman'
275
+ pdf.text('hello')
276
+
277
+ %w[Courier Helvetica Times-Roman].each do |f|
278
+ page_should_include_font(f)
279
+ end
280
+
281
+ pdf.start_new_page
282
+
283
+ pdf.font 'Helvetica'
284
+ pdf.text('hello')
285
+
286
+ page_should_include_font('Helvetica')
287
+ page_should_not_include_font('Courier')
288
+ page_should_not_include_font('Times-Roman')
289
+ end
290
+
291
+ def page_includes_font?(font)
292
+ pdf.page.fonts.values.map { |e| e.data[:BaseFont] }.include?(font.to_sym)
293
+ end
294
+
295
+ def page_should_include_font(font)
296
+ expect(page_includes_font?(font)).to eq true
297
+ end
298
+
299
+ def page_should_not_include_font(font)
300
+ expect(page_includes_font?(font)).to eq false
301
+ end
302
+ end
303
+
304
+ describe 'AFM fonts' do
305
+ let(:times) { pdf.find_font 'Times-Roman' }
306
+
307
+ it 'calculates string width taking into account accented characters' do
308
+ input = win1252_string("\xE9") # é in win-1252
309
+ expect(times.compute_width_of(input, size: 12))
310
+ .to eq(times.compute_width_of('e', size: 12))
311
+ end
312
+
313
+ it 'calculates string width taking into account kerning pairs' do
314
+ expect(times.compute_width_of(win1252_string('To'), size: 12))
315
+ .to eq(13.332)
316
+ expect(times.compute_width_of(
317
+ win1252_string('To'),
318
+ size: 12,
319
+ kerning: true
320
+ )).to eq(12.372)
321
+
322
+ input = win1252_string("T\xF6") # Tö in win-1252
323
+ expect(times.compute_width_of(input, size: 12, kerning: true))
324
+ .to eq(12.372)
325
+ end
326
+
327
+ it 'encodes text without kerning by default' do
328
+ expect(times.encode_text(win1252_string('To'))).to eq([[0, 'To']])
329
+ input = win1252_string("T\xE9l\xE9") # Télé in win-1252
330
+ expect(times.encode_text(input)).to eq([[0, input]])
331
+ expect(times.encode_text(win1252_string('Technology')))
332
+ .to eq([[0, 'Technology']])
333
+ expect(times.encode_text(win1252_string('Technology...')))
334
+ .to eq([[0, 'Technology...']])
335
+ end
336
+
337
+ it 'encodes text with kerning if requested' do
338
+ expect(times.encode_text(win1252_string('To'), kerning: true))
339
+ .to eq([[0, ['T', 80, 'o']]])
340
+ input = win1252_string("T\xE9l\xE9") # Télé in win-1252
341
+ output = win1252_string("\xE9l\xE9") # élé in win-1252
342
+ expect(times.encode_text(input, kerning: true))
343
+ .to eq([[0, ['T', 70, output]]])
344
+ expect(times.encode_text(win1252_string('Technology'), kerning: true))
345
+ .to eq([[0, ['T', 70, 'echnology']]])
346
+ expect(times.encode_text(win1252_string('Technology...'), kerning: true))
347
+ .to eq([[0, ['T', 70, 'echnology', 65, '...']]])
348
+ end
349
+
350
+ describe 'when normalizing encoding' do
351
+ it 'does not modify the original string when normalize_encoding() '\
352
+ 'is used' do
353
+ original = 'Foo'
354
+ normalized = times.normalize_encoding(original)
355
+ expect(original.equal?(normalized)).to eq false
356
+ end
357
+ end
358
+
359
+ it 'omits /Encoding for symbolic fonts' do
360
+ zapf = pdf.find_font 'ZapfDingbats'
361
+ font_dict = zapf.__send__(:register, nil)
362
+ expect(font_dict.data[:Encoding]).to be_nil
363
+ end
364
+ end
365
+
366
+ describe '#glyph_present' do
367
+ it 'returns true when present in an AFM font' do
368
+ font = pdf.find_font('Helvetica')
369
+ expect(font.glyph_present?('H')).to eq true
370
+ end
371
+
372
+ it 'returns false when absent in an AFM font' do
373
+ font = pdf.find_font('Helvetica')
374
+ expect(font.glyph_present?('再')).to eq false
375
+ end
376
+
377
+ it 'returns true when present in a TTF font' do
378
+ font = pdf.find_font("#{Prawn::DATADIR}/fonts/DejaVuSans.ttf")
379
+ expect(font.glyph_present?('H')).to eq true
380
+ end
381
+
382
+ it 'returns false when absent in a TTF font' do
383
+ font = pdf.find_font("#{Prawn::DATADIR}/fonts/DejaVuSans.ttf")
384
+ expect(font.glyph_present?('再')).to eq false
385
+
386
+ font = pdf.find_font("#{Prawn::DATADIR}/fonts/gkai00mp.ttf")
387
+ expect(font.glyph_present?('€')).to eq false
388
+ end
389
+ end
390
+
391
+ describe 'TTF fonts' do
392
+ let(:font) { pdf.find_font "#{Prawn::DATADIR}/fonts/DejaVuSans.ttf" }
393
+
394
+ it 'calculates string width taking into account accented characters' do
395
+ expect(font.compute_width_of('é', size: 12)).to eq(
396
+ font.compute_width_of('e', size: 12)
397
+ )
398
+ end
399
+
400
+ it 'calculates string width taking into account kerning pairs' do
401
+ expect(font.compute_width_of('To', size: 12)).to be_within(0.01).of(14.65)
402
+ expect(font.compute_width_of('To', size: 12, kerning: true))
403
+ .to be_within(0.01).of(12.61)
404
+ end
405
+
406
+ it 'encodes text without kerning by default' do
407
+ expect(font.encode_text('To')).to eq([[0, 'To']])
408
+
409
+ tele = "T\216l\216"
410
+ result = font.encode_text('Télé')
411
+ expect(result.length).to eq(1)
412
+ expect(result[0][0]).to eq(0)
413
+ expect(result[0][1].bytes.to_a).to eq(tele.bytes.to_a)
414
+
415
+ expect(font.encode_text('Technology')).to eq([[0, 'Technology']])
416
+ expect(font.encode_text('Technology...')).to eq([[0, 'Technology...']])
417
+ expect(font.encode_text('Teχnology...')).to eq([
418
+ [0, 'Te'],
419
+ [1, '!'], [0, 'nology...']
420
+ ])
421
+ end
422
+
423
+ it 'encodes text with kerning if requested' do
424
+ expect(font.encode_text('To', kerning: true)).to eq([
425
+ [0, ['T', 169.921875, 'o']]
426
+ ])
427
+ expect(font.encode_text('Technology', kerning: true)).to eq([
428
+ [0, ['T', 169.921875, 'echnology']]
429
+ ])
430
+ expect(font.encode_text('Technology...', kerning: true)).to eq([
431
+ [0, ['T', 169.921875, 'echnology', 142.578125, '...']]
432
+ ])
433
+ expect(font.encode_text('Teχnology...', kerning: true)).to eq([
434
+ [0, ['T', 169.921875, 'e']],
435
+ [1, '!'],
436
+ [0, ['nology', 142.578125, '...']]
437
+ ])
438
+ end
439
+
440
+ it 'uses the ascender, descender, and cap height from the TTF verbatim' do
441
+ # These metrics are relative to the font's own bbox. They should not be
442
+ # scaled with font size.
443
+ ref = pdf.ref!({})
444
+ font.__send__(:embed, ref, 0)
445
+
446
+ # Pull out the embedded font descriptor
447
+ descriptor = ref.data[:FontDescriptor].data
448
+ expect(descriptor[:Ascent]).to eq(759)
449
+ expect(descriptor[:Descent]).to eq(-240)
450
+ expect(descriptor[:CapHeight]).to eq(759)
451
+ end
452
+
453
+ describe 'when normalizing encoding' do
454
+ it 'does not modify the original string with normalize_encoding()' do
455
+ original = 'Foo'
456
+ normalized = font.normalize_encoding(original)
457
+ expect(original.equal?(normalized)).to eq false
458
+ end
459
+ end
460
+ end
461
+
462
+ describe 'OTF fonts' do
463
+ let(:font) { pdf.find_font "#{Prawn::DATADIR}/fonts/Bodoni-Book.otf" }
464
+
465
+ it 'calculates string width taking into account accented characters' do
466
+ expect(font.compute_width_of('é', size: 12)).to eq(
467
+ font.compute_width_of('e', size: 12)
468
+ )
469
+ end
470
+
471
+ it 'uses the ascender, descender, and cap height from the OTF verbatim' do
472
+ # These metrics are relative to the font's own bbox. They should not be
473
+ # scaled with font size.
474
+ ref = pdf.ref!({})
475
+ font.__send__(:embed, ref, 0)
476
+
477
+ # Pull out the embedded font descriptor
478
+ descriptor = ref.data[:FontDescriptor].data
479
+ expect(descriptor[:Ascent]).to eq(1023)
480
+ expect(descriptor[:Descent]).to eq(-200)
481
+ expect(descriptor[:CapHeight]).to eq(3072)
482
+ end
483
+
484
+ describe 'when normalizing encoding' do
485
+ it 'does not modify the original string with normalize_encoding()' do
486
+ original = 'Foo'
487
+ normalized = font.normalize_encoding(original)
488
+ expect(original).to_not be_equal(normalized)
489
+ end
490
+ end
491
+ end
492
+
493
+ describe 'DFont fonts' do
494
+ let(:file) { "#{Prawn::DATADIR}/fonts/Panic+Sans.dfont" }
495
+
496
+ it 'lists all named fonts' do
497
+ list = Prawn::Fonts::DFont.named_fonts(file)
498
+ expect(list).to match_array(%w[
499
+ PanicSans PanicSans-Bold PanicSans-BoldItalic PanicSans-Italic
500
+ ])
501
+ end
502
+
503
+ it 'counts the number of fonts in the file' do
504
+ expect(Prawn::Fonts::DFont.font_count(file)).to eq(4)
505
+ end
506
+
507
+ it 'defaults selected font to the first one if not specified' do
508
+ font = pdf.find_font(file)
509
+ expect(font.basename).to eq('PanicSans')
510
+ end
511
+
512
+ it 'allows font to be selected by index' do
513
+ font = pdf.find_font(file, font: 2)
514
+ expect(font.basename).to eq('PanicSans-Italic')
515
+ end
516
+
517
+ it 'allows font to be selected by name' do
518
+ font = pdf.find_font(file, font: 'PanicSans-BoldItalic')
519
+ expect(font.basename).to eq('PanicSans-BoldItalic')
520
+ end
521
+
522
+ it 'caches font object based on selected font' do
523
+ f1 = pdf.find_font(file, font: 'PanicSans')
524
+ f2 = pdf.find_font(file, font: 'PanicSans-Bold')
525
+ expect(f2.object_id).to_not eq(f1.object_id)
526
+ expect(pdf.find_font(file, font: 'PanicSans').object_id)
527
+ .to eq(f1.object_id)
528
+ expect(pdf.find_font(file, font: 'PanicSans-Bold').object_id)
529
+ .to eq(f2.object_id)
530
+ end
531
+ end
532
+
533
+ describe '#character_count(text)' do
534
+ it 'works on TTF fonts' do
535
+ pdf.font("#{Prawn::DATADIR}/fonts/gkai00mp.ttf")
536
+ expect(pdf.font.character_count('こんにちは世界')).to eq(7)
537
+ expect(pdf.font.character_count('Hello, world!')).to eq(13)
538
+ end
539
+
540
+ it 'works on AFM fonts' do
541
+ expect(pdf.font.character_count('Hello, world!')).to eq(13)
542
+ end
543
+ end
544
+ end