prawn 2.1.0 → 2.2.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 (278) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +2 -0
  3. data.tar.gz.sig +0 -0
  4. data/Gemfile +1 -9
  5. data/Rakefile +12 -22
  6. data/lib/prawn.rb +29 -48
  7. data/lib/prawn/document.rb +148 -123
  8. data/lib/prawn/document/bounding_box.rb +33 -26
  9. data/lib/prawn/document/column_box.rb +5 -7
  10. data/lib/prawn/document/internals.rb +6 -6
  11. data/lib/prawn/document/span.rb +20 -17
  12. data/lib/prawn/encoding.rb +65 -67
  13. data/lib/prawn/errors.rb +10 -7
  14. data/lib/prawn/font.rb +78 -62
  15. data/lib/prawn/font/afm.rb +93 -66
  16. data/lib/prawn/font/dfont.rb +2 -10
  17. data/lib/prawn/font/ttc.rb +34 -0
  18. data/lib/prawn/font/ttf.rb +73 -65
  19. data/lib/prawn/font_metric_cache.rb +9 -8
  20. data/lib/prawn/graphics.rb +110 -70
  21. data/lib/prawn/graphics/blend_mode.rb +7 -8
  22. data/lib/prawn/graphics/cap_style.rb +2 -4
  23. data/lib/prawn/graphics/color.rb +23 -26
  24. data/lib/prawn/graphics/dash.rb +22 -12
  25. data/lib/prawn/graphics/join_style.rb +8 -4
  26. data/lib/prawn/graphics/patterns.rb +185 -96
  27. data/lib/prawn/graphics/transformation.rb +11 -9
  28. data/lib/prawn/graphics/transparency.rb +15 -13
  29. data/lib/prawn/grid.rb +20 -20
  30. data/lib/prawn/image_handler.rb +4 -6
  31. data/lib/prawn/images.rb +22 -15
  32. data/lib/prawn/images/image.rb +0 -1
  33. data/lib/prawn/images/jpg.rb +26 -22
  34. data/lib/prawn/images/png.rb +60 -57
  35. data/lib/prawn/measurement_extensions.rb +8 -9
  36. data/lib/prawn/measurements.rb +14 -15
  37. data/lib/prawn/outline.rb +96 -78
  38. data/lib/prawn/repeater.rb +12 -10
  39. data/lib/prawn/security.rb +66 -48
  40. data/lib/prawn/security/arcfour.rb +1 -3
  41. data/lib/prawn/soft_mask.rb +23 -25
  42. data/lib/prawn/stamp.rb +16 -12
  43. data/lib/prawn/text.rb +59 -45
  44. data/lib/prawn/text/box.rb +9 -8
  45. data/lib/prawn/text/formatted.rb +4 -6
  46. data/lib/prawn/text/formatted/arranger.rb +51 -30
  47. data/lib/prawn/text/formatted/box.rb +112 -88
  48. data/lib/prawn/text/formatted/fragment.rb +10 -15
  49. data/lib/prawn/text/formatted/line_wrap.rb +118 -61
  50. data/lib/prawn/text/formatted/parser.rb +134 -110
  51. data/lib/prawn/text/formatted/wrap.rb +42 -32
  52. data/lib/prawn/transformation_stack.rb +3 -4
  53. data/lib/prawn/utilities.rb +6 -21
  54. data/lib/prawn/version.rb +1 -3
  55. data/lib/prawn/view.rb +4 -2
  56. data/manual/basic_concepts/adding_pages.rb +4 -7
  57. data/manual/basic_concepts/basic_concepts.rb +29 -22
  58. data/manual/basic_concepts/creation.rb +8 -11
  59. data/manual/basic_concepts/cursor.rb +2 -5
  60. data/manual/basic_concepts/measurement.rb +3 -6
  61. data/manual/basic_concepts/origin.rb +3 -6
  62. data/manual/basic_concepts/other_cursor_helpers.rb +9 -12
  63. data/manual/basic_concepts/view.rb +20 -16
  64. data/manual/bounding_box/bounding_box.rb +27 -24
  65. data/manual/bounding_box/bounds.rb +9 -12
  66. data/manual/bounding_box/canvas.rb +2 -5
  67. data/manual/bounding_box/creation.rb +4 -7
  68. data/manual/bounding_box/indentation.rb +12 -15
  69. data/manual/bounding_box/nesting.rb +22 -17
  70. data/manual/bounding_box/russian_boxes.rb +8 -9
  71. data/manual/bounding_box/stretchy.rb +10 -13
  72. data/manual/contents.rb +26 -22
  73. data/manual/cover.rb +22 -20
  74. data/manual/document_and_page_options/background.rb +9 -13
  75. data/manual/document_and_page_options/document_and_page_options.rb +23 -20
  76. data/manual/document_and_page_options/metadata.rb +16 -16
  77. data/manual/document_and_page_options/page_margins.rb +16 -20
  78. data/manual/document_and_page_options/page_size.rb +11 -12
  79. data/manual/document_and_page_options/print_scaling.rb +15 -15
  80. data/manual/example_helper.rb +2 -4
  81. data/manual/graphics/blend_mode.rb +10 -9
  82. data/manual/graphics/circle_and_ellipse.rb +2 -5
  83. data/manual/graphics/color.rb +5 -9
  84. data/manual/graphics/common_lines.rb +5 -8
  85. data/manual/graphics/fill_and_stroke.rb +2 -5
  86. data/manual/graphics/fill_rules.rb +7 -10
  87. data/manual/graphics/gradients.rb +25 -21
  88. data/manual/graphics/graphics.rb +49 -43
  89. data/manual/graphics/helper.rb +10 -9
  90. data/manual/graphics/line_width.rb +5 -7
  91. data/manual/graphics/lines_and_curves.rb +5 -8
  92. data/manual/graphics/polygon.rb +4 -8
  93. data/manual/graphics/rectangle.rb +2 -5
  94. data/manual/graphics/rotate.rb +4 -7
  95. data/manual/graphics/scale.rb +12 -15
  96. data/manual/graphics/soft_masks.rb +1 -4
  97. data/manual/graphics/stroke_cap.rb +3 -6
  98. data/manual/graphics/stroke_dash.rb +9 -12
  99. data/manual/graphics/stroke_join.rb +2 -5
  100. data/manual/graphics/translate.rb +7 -10
  101. data/manual/graphics/transparency.rb +5 -8
  102. data/manual/how_to_read_this_manual.rb +4 -6
  103. data/manual/images/absolute_position.rb +4 -7
  104. data/manual/images/fit.rb +5 -8
  105. data/manual/images/horizontal.rb +6 -9
  106. data/manual/images/images.rb +25 -23
  107. data/manual/images/plain_image.rb +3 -6
  108. data/manual/images/scale.rb +7 -10
  109. data/manual/images/vertical.rb +10 -13
  110. data/manual/images/width_and_height.rb +8 -11
  111. data/manual/layout/boxes.rb +3 -6
  112. data/manual/layout/content.rb +5 -8
  113. data/manual/layout/layout.rb +16 -16
  114. data/manual/layout/simple_grid.rb +4 -7
  115. data/manual/outline/add_subsection_to.rb +18 -21
  116. data/manual/outline/insert_section_after.rb +13 -16
  117. data/manual/outline/outline.rb +19 -17
  118. data/manual/outline/sections_and_pages.rb +15 -18
  119. data/manual/repeatable_content/alternate_page_numbering.rb +19 -17
  120. data/manual/repeatable_content/page_numbering.rb +15 -16
  121. data/manual/repeatable_content/repeatable_content.rb +23 -19
  122. data/manual/repeatable_content/repeater.rb +12 -15
  123. data/manual/repeatable_content/stamp.rb +12 -15
  124. data/manual/security/encryption.rb +7 -10
  125. data/manual/security/permissions.rb +17 -14
  126. data/manual/security/security.rb +17 -16
  127. data/manual/table.rb +2 -4
  128. data/manual/text/alignment.rb +14 -17
  129. data/manual/text/color.rb +10 -11
  130. data/manual/text/column_box.rb +5 -8
  131. data/manual/text/fallback_fonts.rb +23 -21
  132. data/manual/text/font.rb +9 -12
  133. data/manual/text/font_size.rb +11 -14
  134. data/manual/text/font_style.rb +4 -7
  135. data/manual/text/formatted_callbacks.rb +23 -21
  136. data/manual/text/formatted_text.rb +31 -25
  137. data/manual/text/free_flowing_text.rb +18 -21
  138. data/manual/text/inline.rb +16 -19
  139. data/manual/text/kerning_and_character_spacing.rb +12 -15
  140. data/manual/text/leading.rb +5 -8
  141. data/manual/text/line_wrapping.rb +33 -17
  142. data/manual/text/paragraph_indentation.rb +11 -14
  143. data/manual/text/positioned_text.rb +13 -16
  144. data/manual/text/registering_families.rb +16 -19
  145. data/manual/text/rendering_and_color.rb +7 -10
  146. data/manual/text/right_to_left_text.rb +24 -19
  147. data/manual/text/rotation.rb +26 -23
  148. data/manual/text/single_usage.rb +6 -9
  149. data/manual/text/text.rb +56 -52
  150. data/manual/text/text_box_excess.rb +18 -17
  151. data/manual/text/text_box_extensions.rb +16 -15
  152. data/manual/text/text_box_overflow.rb +15 -18
  153. data/manual/text/utf8.rb +9 -12
  154. data/manual/text/win_ansi_charset.rb +18 -19
  155. data/prawn.gemspec +37 -27
  156. data/spec/extensions/encoding_helpers.rb +0 -2
  157. data/spec/manual_spec.rb +33 -0
  158. data/spec/prawn/document/bounding_box_spec.rb +546 -0
  159. data/spec/prawn/document/column_box_spec.rb +73 -0
  160. data/spec/prawn/document/security_spec.rb +173 -0
  161. data/spec/prawn/document_annotations_spec.rb +74 -0
  162. data/spec/prawn/document_destinations_spec.rb +13 -0
  163. data/spec/prawn/document_grid_spec.rb +96 -0
  164. data/spec/prawn/document_reference_spec.rb +25 -0
  165. data/spec/prawn/document_span_spec.rb +34 -0
  166. data/spec/prawn/document_spec.rb +751 -0
  167. data/spec/prawn/font_metric_cache_spec.rb +52 -0
  168. data/spec/prawn/font_spec.rb +513 -0
  169. data/spec/prawn/graphics/blend_mode_spec.rb +61 -0
  170. data/spec/prawn/graphics/transparency_spec.rb +79 -0
  171. data/spec/prawn/graphics_spec.rb +817 -0
  172. data/spec/prawn/graphics_stroke_styles_spec.rb +227 -0
  173. data/spec/{image_handler_spec.rb → prawn/image_handler_spec.rb} +13 -15
  174. data/spec/prawn/images/jpg_spec.rb +18 -0
  175. data/spec/prawn/images/png_spec.rb +281 -0
  176. data/spec/prawn/images_spec.rb +170 -0
  177. data/spec/prawn/measurements_extensions_spec.rb +22 -0
  178. data/spec/prawn/outline_spec.rb +408 -0
  179. data/spec/prawn/repeater_spec.rb +163 -0
  180. data/spec/prawn/soft_mask_spec.rb +72 -0
  181. data/spec/prawn/stamp_spec.rb +168 -0
  182. data/spec/prawn/text/box_spec.rb +1113 -0
  183. data/spec/prawn/text/formatted/arranger_spec.rb +464 -0
  184. data/spec/prawn/text/formatted/box_spec.rb +825 -0
  185. data/spec/prawn/text/formatted/fragment_spec.rb +341 -0
  186. data/spec/prawn/text/formatted/line_wrap_spec.rb +491 -0
  187. data/spec/prawn/text/formatted/parser_spec.rb +667 -0
  188. data/spec/prawn/text_draw_text_spec.rb +147 -0
  189. data/spec/prawn/text_rendering_mode_spec.rb +42 -0
  190. data/spec/prawn/text_spacing_spec.rb +93 -0
  191. data/spec/prawn/text_spec.rb +601 -0
  192. data/spec/prawn/text_with_inline_formatting_spec.rb +33 -0
  193. data/spec/{transformation_stack_spec.rb → prawn/transformation_stack_spec.rb} +21 -20
  194. data/spec/prawn/view_spec.rb +45 -0
  195. data/spec/spec_helper.rb +16 -16
  196. metadata +96 -151
  197. metadata.gz.sig +1 -0
  198. data/data/images/16bit.alpha +0 -0
  199. data/data/images/16bit.color +0 -0
  200. data/data/images/16bit.png +0 -0
  201. data/data/images/arrow.png +0 -0
  202. data/data/images/arrow2.png +0 -0
  203. data/data/images/blend_modes_bottom_layer.jpg +0 -0
  204. data/data/images/blend_modes_top_layer.jpg +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/indexed_transparency.png +0 -0
  213. data/data/images/indexed_transparency_alpha.dat +0 -0
  214. data/data/images/indexed_transparency_color.dat +0 -0
  215. data/data/images/letterhead.jpg +0 -0
  216. data/data/images/license.md +0 -8
  217. data/data/images/page_white_text.alpha +0 -0
  218. data/data/images/page_white_text.color +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/spec/acceptance/png_spec.rb +0 -35
  244. data/spec/annotations_spec.rb +0 -67
  245. data/spec/blend_mode_spec.rb +0 -71
  246. data/spec/bounding_box_spec.rb +0 -501
  247. data/spec/column_box_spec.rb +0 -59
  248. data/spec/destinations_spec.rb +0 -13
  249. data/spec/document_spec.rb +0 -738
  250. data/spec/font_metric_cache_spec.rb +0 -52
  251. data/spec/font_spec.rb +0 -475
  252. data/spec/formatted_text_arranger_spec.rb +0 -452
  253. data/spec/formatted_text_box_spec.rb +0 -716
  254. data/spec/formatted_text_fragment_spec.rb +0 -299
  255. data/spec/graphics_spec.rb +0 -705
  256. data/spec/grid_spec.rb +0 -95
  257. data/spec/images_spec.rb +0 -167
  258. data/spec/inline_formatted_text_parser_spec.rb +0 -568
  259. data/spec/jpg_spec.rb +0 -23
  260. data/spec/line_wrap_spec.rb +0 -366
  261. data/spec/measurement_units_spec.rb +0 -22
  262. data/spec/outline_spec.rb +0 -409
  263. data/spec/png_spec.rb +0 -257
  264. data/spec/reference_spec.rb +0 -25
  265. data/spec/repeater_spec.rb +0 -154
  266. data/spec/security_spec.rb +0 -151
  267. data/spec/soft_mask_spec.rb +0 -78
  268. data/spec/span_spec.rb +0 -43
  269. data/spec/stamp_spec.rb +0 -179
  270. data/spec/stroke_styles_spec.rb +0 -208
  271. data/spec/text_at_spec.rb +0 -142
  272. data/spec/text_box_spec.rb +0 -1042
  273. data/spec/text_rendering_mode_spec.rb +0 -45
  274. data/spec/text_spacing_spec.rb +0 -93
  275. data/spec/text_spec.rb +0 -543
  276. data/spec/text_with_inline_formatting_spec.rb +0 -35
  277. data/spec/transparency_spec.rb +0 -91
  278. data/spec/view_spec.rb +0 -42
@@ -0,0 +1,52 @@
1
+ require 'spec_helper'
2
+ require 'pathname'
3
+
4
+ describe Prawn::FontMetricCache do
5
+ let(:document) { Prawn::Document.new }
6
+ let(:font_metric_cache) { described_class.new(document) }
7
+
8
+ it 'starts with an empty cache' do
9
+ expect(font_metric_cache.instance_variable_get(:@cache)).to be_empty
10
+ end
11
+
12
+ it 'caches the width of the provided string' do
13
+ font_metric_cache.width_of('M', {})
14
+
15
+ expect(font_metric_cache.instance_variable_get(:@cache).size).to eq(1)
16
+ end
17
+
18
+ it 'onlies cache a single copy of the same string' do
19
+ font_metric_cache.width_of('M', {})
20
+ font_metric_cache.width_of('M', {})
21
+
22
+ expect(font_metric_cache.instance_variable_get(:@cache).size).to eq(1)
23
+ end
24
+
25
+ it 'caches different copies for different strings' do
26
+ font_metric_cache.width_of('M', {})
27
+ font_metric_cache.width_of('W', {})
28
+
29
+ expect(font_metric_cache.instance_variable_get(:@cache).entries.size)
30
+ .to eq 2
31
+ end
32
+
33
+ it 'caches different copies of the same string with different font sizes' do
34
+ font_metric_cache.width_of('M', {})
35
+
36
+ document.font_size 24
37
+ font_metric_cache.width_of('M', {})
38
+
39
+ expect(font_metric_cache.instance_variable_get(:@cache).entries.size)
40
+ .to eq 2
41
+ end
42
+
43
+ it 'caches different copies of the same string with different fonts' do
44
+ font_metric_cache.width_of('M', {})
45
+
46
+ document.font 'Courier'
47
+ font_metric_cache.width_of('M', {})
48
+
49
+ expect(font_metric_cache.instance_variable_get(:@cache).entries.size)
50
+ .to eq 2
51
+ end
52
+ end
@@ -0,0 +1,513 @@
1
+ require 'spec_helper'
2
+ require 'pathname'
3
+
4
+ describe Prawn::Font do
5
+ let(:pdf) { create_pdf }
6
+
7
+ describe 'Font behavior' do
8
+ it 'defaults to Helvetica if no font is specified' do
9
+ pdf = Prawn::Document.new
10
+ expect(pdf.font.name).to eq('Helvetica')
11
+ end
12
+ end
13
+
14
+ describe 'Font objects' do
15
+ it 'is equal' do
16
+ font1 = Prawn::Document.new.font
17
+ font2 = Prawn::Document.new.font
18
+
19
+ expect(font1).to eql(font2)
20
+ end
21
+
22
+ it 'alwayses be the same key' do
23
+ font1 = Prawn::Document.new.font
24
+ font2 = Prawn::Document.new.font
25
+
26
+ hash = {}
27
+
28
+ hash[font1] = 'Original'
29
+ hash[font2] = 'Overwritten'
30
+
31
+ expect(hash.size).to eq(1)
32
+
33
+ expect(hash[font1]).to eq('Overwritten')
34
+ expect(hash[font2]).to eq('Overwritten')
35
+ end
36
+ end
37
+
38
+ describe '#width_of' do
39
+ it 'takes character spacing into account' do
40
+ original_width = pdf.width_of('hello world')
41
+ pdf.character_spacing(7) do
42
+ expect(pdf.width_of('hello world')).to eq(original_width + 11 * 7)
43
+ end
44
+ end
45
+
46
+ it 'excludes newlines' do
47
+ # Use a TTF font that has a non-zero width for \n
48
+ pdf.font("#{Prawn::DATADIR}/fonts/gkai00mp.ttf")
49
+
50
+ expect(pdf.width_of("\nhello world\n")).to eq(
51
+ pdf.width_of('hello world')
52
+ )
53
+ end
54
+
55
+ it 'takes formatting into account' do
56
+ normal_hello = pdf.width_of('hello')
57
+ inline_bold_hello = pdf.width_of('<b>hello</b>', inline_format: true)
58
+ expect(inline_bold_hello).to be > normal_hello
59
+
60
+ pdf.font('Helvetica', style: :bold) do
61
+ expect(inline_bold_hello).to eq(pdf.width_of('hello'))
62
+ end
63
+ end
64
+
65
+ it 'accepts :style as an argument' do
66
+ styled_bold_hello = pdf.width_of('hello', style: :bold)
67
+ pdf.font('Helvetica', style: :bold) do
68
+ expect(styled_bold_hello).to eq(pdf.width_of('hello'))
69
+ end
70
+ end
71
+
72
+ it 'calculates styled widths correctly using TTFs' do
73
+ pdf.font_families.update(
74
+ 'DejaVu Sans' => {
75
+ normal: "#{Prawn::DATADIR}/fonts/DejaVuSans.ttf",
76
+ bold: "#{Prawn::DATADIR}/fonts/DejaVuSans-Bold.ttf"
77
+ }
78
+ )
79
+ styled_bold_hello = nil
80
+ bold_hello = nil
81
+ plain_hello = nil
82
+
83
+ pdf.font('DejaVu Sans') do
84
+ styled_bold_hello = pdf.width_of('hello', style: :bold)
85
+ end
86
+ pdf.font('DejaVu Sans', style: :bold) do
87
+ bold_hello = pdf.width_of('hello')
88
+ end
89
+
90
+ pdf.font('DejaVu Sans') do
91
+ plain_hello = pdf.width_of('hello')
92
+ end
93
+
94
+ expect(plain_hello).to_not eq(bold_hello)
95
+
96
+ expect(styled_bold_hello).to eq(bold_hello)
97
+ end
98
+
99
+ it 'does not treat minus as if it were a hyphen', issue: 578 do
100
+ expect(pdf.width_of('-0.75')).to be < pdf.width_of('25.00')
101
+ end
102
+ end
103
+
104
+ describe '#font_size' do
105
+ it 'allows setting font size in DSL style' do
106
+ pdf.font_size 20
107
+ expect(pdf.font_size).to eq(20)
108
+ end
109
+ end
110
+
111
+ describe 'font style support' do
112
+ it 'complains if there is no @current_page' do
113
+ pdf_without_page = Prawn::Document.new(skip_page_creation: true)
114
+
115
+ expect { pdf_without_page.font 'Helvetica' }
116
+ .to raise_error(Prawn::Errors::NotOnPage)
117
+ end
118
+
119
+ it 'allows specifying font style by style name and font family' do
120
+ pdf.font 'Courier', style: :bold
121
+ pdf.text 'In Courier bold'
122
+
123
+ pdf.font 'Courier', style: :bold_italic
124
+ pdf.text 'In Courier bold-italic'
125
+
126
+ pdf.font 'Courier', style: :italic
127
+ pdf.text 'In Courier italic'
128
+
129
+ pdf.font 'Courier', style: :normal
130
+ pdf.text 'In Normal Courier'
131
+
132
+ pdf.font 'Helvetica'
133
+ pdf.text 'In Normal Helvetica'
134
+
135
+ text = PDF::Inspector::Text.analyze(pdf.render)
136
+ expect(text.font_settings.map { |e| e[:name] }).to eq(
137
+ [
138
+ :"Courier-Bold", :"Courier-BoldOblique", :"Courier-Oblique",
139
+ :Courier, :Helvetica
140
+ ]
141
+ )
142
+ end
143
+
144
+ it 'allows font familes to be defined in a single dfont' do
145
+ file = "#{Prawn::DATADIR}/fonts/Panic+Sans.dfont"
146
+ pdf.font_families['Panic Sans'] = {
147
+ normal: { file: file, font: 'PanicSans' },
148
+ italic: { file: file, font: 'PanicSans-Italic' },
149
+ bold: { file: file, font: 'PanicSans-Bold' },
150
+ bold_italic: { file: file, font: 'PanicSans-BoldItalic' }
151
+ }
152
+
153
+ pdf.font 'Panic Sans', style: :italic
154
+ pdf.text 'In PanicSans-Italic'
155
+
156
+ text = PDF::Inspector::Text.analyze(pdf.render)
157
+ name = text.font_settings.map { |e| e[:name] }.first.to_s
158
+ name = name.sub(/\w+\+/, 'subset+')
159
+ expect(name).to eq('subset+PanicSans-Italic')
160
+ end
161
+
162
+ it 'allows font familes to be defined in a single ttc' do
163
+ file = "#{Prawn::DATADIR}/fonts/DejaVuSans.ttc"
164
+ pdf.font_families['DejaVu Sans'] = {
165
+ normal: { file: file, font: 0 },
166
+ bold: { file: file, font: 1 }
167
+ }
168
+
169
+ pdf.font 'DejaVu Sans', style: :bold
170
+ pdf.text 'In PanicSans-Bold'
171
+
172
+ text = PDF::Inspector::Text.analyze(pdf.render)
173
+ name = text.font_settings.map { |e| e[:name] }.first.to_s
174
+ name = name.sub(/\w+\+/, 'subset+')
175
+ expect(name).to eq('subset+DejaVuSans-Bold')
176
+ end
177
+
178
+ it 'allows fonts to be indexed by name in a ttc file' do
179
+ file = "#{Prawn::DATADIR}/fonts/DejaVuSans.ttc"
180
+ pdf.font_families['DejaVu Sans'] = {
181
+ normal: { file: file, font: 'DejaVu Sans' },
182
+ bold: { file: file, font: 'DejaVu Sans Bold' }
183
+ }
184
+
185
+ pdf.font 'DejaVu Sans', style: :bold
186
+ pdf.text 'In PanicSans-Bold'
187
+
188
+ text = PDF::Inspector::Text.analyze(pdf.render)
189
+ name = text.font_settings.map { |e| e[:name] }.first.to_s
190
+ name = name.sub(/\w+\+/, 'subset+')
191
+ expect(name).to eq('subset+DejaVuSans-Bold')
192
+ end
193
+
194
+ it 'accepts Pathname objects for font files' do
195
+ file = Pathname.new("#{Prawn::DATADIR}/fonts/DejaVuSans.ttf")
196
+ pdf.font_families['DejaVu Sans'] = {
197
+ normal: file
198
+ }
199
+
200
+ pdf.font 'DejaVu Sans'
201
+ pdf.text 'In DejaVu Sans'
202
+
203
+ text = PDF::Inspector::Text.analyze(pdf.render)
204
+ name = text.font_settings.map { |e| e[:name] }.first.to_s
205
+ name = name.sub(/\w+\+/, 'subset+')
206
+ expect(name).to eq('subset+DejaVuSans')
207
+ end
208
+
209
+ it 'accepts IO objects for font files' do
210
+ io = File.open "#{Prawn::DATADIR}/fonts/DejaVuSans.ttf"
211
+ pdf.font_families['DejaVu Sans'] = {
212
+ normal: described_class.load(pdf, io)
213
+ }
214
+
215
+ pdf.font 'DejaVu Sans'
216
+ pdf.text 'In DejaVu Sans'
217
+
218
+ text = PDF::Inspector::Text.analyze(pdf.render)
219
+ name = text.font_settings.map { |e| e[:name] }.first.to_s
220
+ name = name.sub(/\w+\+/, 'subset+')
221
+ expect(name).to eq('subset+DejaVuSans')
222
+ end
223
+ end
224
+
225
+ describe 'Transactional font handling' do
226
+ it 'allows setting of size directly when font is created' do
227
+ pdf.font 'Courier', size: 16
228
+ expect(pdf.font_size).to eq(16)
229
+ end
230
+
231
+ it 'allows temporary setting of a new font using a transaction' do
232
+ pdf.font 'Helvetica', size: 12
233
+
234
+ pdf.font 'Courier', size: 16 do
235
+ expect(pdf.font.name).to eq('Courier')
236
+ expect(pdf.font_size).to eq(16)
237
+ end
238
+
239
+ expect(pdf.font.name).to eq('Helvetica')
240
+ expect(pdf.font_size).to eq(12)
241
+ end
242
+
243
+ it 'masks font size when using a transacation' do
244
+ pdf.font 'Courier', size: 16 do
245
+ expect(pdf.font_size).to eq(16)
246
+ end
247
+
248
+ pdf.font 'Times-Roman'
249
+ pdf.font 'Courier'
250
+
251
+ expect(pdf.font_size).to eq(12)
252
+ end
253
+ end
254
+
255
+ describe 'Document#page_fonts' do
256
+ it 'registers fonts properly by page' do
257
+ pdf.font 'Courier'
258
+ pdf.text('hello')
259
+
260
+ pdf.font 'Helvetica'
261
+ pdf.text('hello')
262
+
263
+ pdf.font 'Times-Roman'
264
+ pdf.text('hello')
265
+
266
+ ['Courier', 'Helvetica', 'Times-Roman'].each do |f|
267
+ page_should_include_font(f)
268
+ end
269
+
270
+ pdf.start_new_page
271
+
272
+ pdf.font 'Helvetica'
273
+ pdf.text('hello')
274
+
275
+ page_should_include_font('Helvetica')
276
+ page_should_not_include_font('Courier')
277
+ page_should_not_include_font('Times-Roman')
278
+ end
279
+
280
+ def page_includes_font?(font)
281
+ pdf.page.fonts.values.map { |e| e.data[:BaseFont] }.include?(font.to_sym)
282
+ end
283
+
284
+ def page_should_include_font(font)
285
+ expect(page_includes_font?(font)).to eq true
286
+ end
287
+
288
+ def page_should_not_include_font(font)
289
+ expect(page_includes_font?(font)).to eq false
290
+ end
291
+ end
292
+
293
+ describe 'AFM fonts' do
294
+ let(:times) { pdf.find_font 'Times-Roman' }
295
+
296
+ it 'calculates string width taking into account accented characters' do
297
+ input = win1252_string("\xE9") # é in win-1252
298
+ expect(times.compute_width_of(input, size: 12))
299
+ .to eq(times.compute_width_of('e', size: 12))
300
+ end
301
+
302
+ it 'calculates string width taking into account kerning pairs' do
303
+ expect(times.compute_width_of(win1252_string('To'), size: 12))
304
+ .to eq(13.332)
305
+ expect(times.compute_width_of(
306
+ win1252_string('To'),
307
+ size: 12, kerning: true
308
+ )).to eq(12.372)
309
+
310
+ input = win1252_string("T\xF6") # Tö in win-1252
311
+ expect(times.compute_width_of(input, size: 12, kerning: true))
312
+ .to eq(12.372)
313
+ end
314
+
315
+ it 'encodes text without kerning by default' do
316
+ expect(times.encode_text(win1252_string('To'))).to eq([[0, 'To']])
317
+ input = win1252_string("T\xE9l\xE9") # Télé in win-1252
318
+ expect(times.encode_text(input)).to eq([[0, input]])
319
+ expect(times.encode_text(win1252_string('Technology')))
320
+ .to eq([[0, 'Technology']])
321
+ expect(times.encode_text(win1252_string('Technology...')))
322
+ .to eq([[0, 'Technology...']])
323
+ end
324
+
325
+ it 'encodes text with kerning if requested' do
326
+ expect(times.encode_text(win1252_string('To'), kerning: true))
327
+ .to eq([[0, ['T', 80, 'o']]])
328
+ input = win1252_string("T\xE9l\xE9") # Télé in win-1252
329
+ output = win1252_string("\xE9l\xE9") # élé in win-1252
330
+ expect(times.encode_text(input, kerning: true))
331
+ .to eq([[0, ['T', 70, output]]])
332
+ expect(times.encode_text(win1252_string('Technology'), kerning: true))
333
+ .to eq([[0, ['T', 70, 'echnology']]])
334
+ expect(times.encode_text(win1252_string('Technology...'), kerning: true))
335
+ .to eq([[0, ['T', 70, 'echnology', 65, '...']]])
336
+ end
337
+
338
+ describe 'when normalizing encoding' do
339
+ it 'does not modify the original string when normalize_encoding() '\
340
+ 'is used' do
341
+ original = 'Foo'
342
+ normalized = times.normalize_encoding(original)
343
+ expect(original.equal?(normalized)).to eq false
344
+ end
345
+
346
+ it 'modifies the original string when normalize_encoding!() is used' do
347
+ original = 'Foo'
348
+ normalized = times.normalize_encoding!(original)
349
+ expect(original.equal?(normalized)).to eq true
350
+ end
351
+ end
352
+
353
+ it 'omits /Encoding for symbolic fonts' do
354
+ zapf = pdf.find_font 'ZapfDingbats'
355
+ font_dict = zapf.send(:register, nil)
356
+ expect(font_dict.data[:Encoding]).to be_nil
357
+ end
358
+ end
359
+
360
+ describe '#glyph_present' do
361
+ it 'returns true when present in an AFM font' do
362
+ font = pdf.find_font('Helvetica')
363
+ expect(font.glyph_present?('H')).to eq true
364
+ end
365
+
366
+ it 'returns false when absent in an AFM font' do
367
+ font = pdf.find_font('Helvetica')
368
+ expect(font.glyph_present?('再')).to eq false
369
+ end
370
+
371
+ it 'returns true when present in a TTF font' do
372
+ font = pdf.find_font("#{Prawn::DATADIR}/fonts/DejaVuSans.ttf")
373
+ expect(font.glyph_present?('H')).to eq true
374
+ end
375
+
376
+ it 'returns false when absent in a TTF font' do
377
+ font = pdf.find_font("#{Prawn::DATADIR}/fonts/DejaVuSans.ttf")
378
+ expect(font.glyph_present?('再')).to eq false
379
+
380
+ font = pdf.find_font("#{Prawn::DATADIR}/fonts/gkai00mp.ttf")
381
+ expect(font.glyph_present?('€')).to eq false
382
+ end
383
+ end
384
+
385
+ describe 'TTF fonts' do
386
+ let(:font) { pdf.find_font "#{Prawn::DATADIR}/fonts/DejaVuSans.ttf" }
387
+
388
+ it 'calculates string width taking into account accented characters' do
389
+ expect(font.compute_width_of('é', size: 12)).to eq(
390
+ font.compute_width_of('e', size: 12)
391
+ )
392
+ end
393
+
394
+ it 'calculates string width taking into account kerning pairs' do
395
+ expect(font.compute_width_of('To', size: 12)).to be_within(0.01).of(14.65)
396
+ expect(font.compute_width_of('To', size: 12, kerning: true))
397
+ .to be_within(0.01).of(12.61)
398
+ end
399
+
400
+ it 'encodes text without kerning by default' do
401
+ expect(font.encode_text('To')).to eq([[0, 'To']])
402
+
403
+ tele = "T\216l\216"
404
+ result = font.encode_text('Télé')
405
+ expect(result.length).to eq(1)
406
+ expect(result[0][0]).to eq(0)
407
+ expect(result[0][1].bytes.to_a).to eq(tele.bytes.to_a)
408
+
409
+ expect(font.encode_text('Technology')).to eq([[0, 'Technology']])
410
+ expect(font.encode_text('Technology...')).to eq([[0, 'Technology...']])
411
+ expect(font.encode_text('Teχnology...')).to eq([
412
+ [0, 'Te'],
413
+ [1, '!'], [0, 'nology...']
414
+ ])
415
+ end
416
+
417
+ it 'encodes text with kerning if requested' do
418
+ expect(font.encode_text('To', kerning: true)).to eq([
419
+ [0, ['T', 169.921875, 'o']]
420
+ ])
421
+ expect(font.encode_text('Technology', kerning: true)).to eq([
422
+ [0, ['T', 169.921875, 'echnology']]
423
+ ])
424
+ expect(font.encode_text('Technology...', kerning: true)).to eq([
425
+ [0, ['T', 169.921875, 'echnology', 142.578125, '...']]
426
+ ])
427
+ expect(font.encode_text('Teχnology...', kerning: true)).to eq([
428
+ [0, ['T', 169.921875, 'e']],
429
+ [1, '!'],
430
+ [0, ['nology', 142.578125, '...']]
431
+ ])
432
+ end
433
+
434
+ it 'uses the ascender, descender, and cap height from the TTF verbatim' do
435
+ # These metrics are relative to the font's own bbox. They should not be
436
+ # scaled with font size.
437
+ ref = pdf.ref!({})
438
+ font.send :embed, ref, 0
439
+
440
+ # Pull out the embedded font descriptor
441
+ descriptor = ref.data[:FontDescriptor].data
442
+ expect(descriptor[:Ascent]).to eq(759)
443
+ expect(descriptor[:Descent]).to eq(-240)
444
+ expect(descriptor[:CapHeight]).to eq(759)
445
+ end
446
+
447
+ describe 'when normalizing encoding' do
448
+ it 'does not modify the original string with normalize_encoding()' do
449
+ original = 'Foo'
450
+ normalized = font.normalize_encoding(original)
451
+ expect(original.equal?(normalized)).to eq false
452
+ end
453
+
454
+ it 'modifies the original string when normalize_encoding!() is used' do
455
+ original = 'Foo'
456
+ normalized = font.normalize_encoding!(original)
457
+ expect(original.equal?(normalized)).to eq true
458
+ end
459
+ end
460
+ end
461
+
462
+ describe 'DFont fonts' do
463
+ let(:file) { "#{Prawn::DATADIR}/fonts/Panic+Sans.dfont" }
464
+
465
+ it 'lists all named fonts' do
466
+ list = Prawn::Font::DFont.named_fonts(file)
467
+ expect(list).to match_array(%w[
468
+ PanicSans PanicSans-Bold PanicSans-BoldItalic PanicSans-Italic
469
+ ])
470
+ end
471
+
472
+ it 'counts the number of fonts in the file' do
473
+ expect(Prawn::Font::DFont.font_count(file)).to eq(4)
474
+ end
475
+
476
+ it 'defaults selected font to the first one if not specified' do
477
+ font = pdf.find_font(file)
478
+ expect(font.basename).to eq('PanicSans')
479
+ end
480
+
481
+ it 'allows font to be selected by index' do
482
+ font = pdf.find_font(file, font: 2)
483
+ expect(font.basename).to eq('PanicSans-Italic')
484
+ end
485
+
486
+ it 'allows font to be selected by name' do
487
+ font = pdf.find_font(file, font: 'PanicSans-BoldItalic')
488
+ expect(font.basename).to eq('PanicSans-BoldItalic')
489
+ end
490
+
491
+ it 'caches font object based on selected font' do
492
+ f1 = pdf.find_font(file, font: 'PanicSans')
493
+ f2 = pdf.find_font(file, font: 'PanicSans-Bold')
494
+ expect(f2.object_id).to_not eq(f1.object_id)
495
+ expect(pdf.find_font(file, font: 'PanicSans').object_id)
496
+ .to eq(f1.object_id)
497
+ expect(pdf.find_font(file, font: 'PanicSans-Bold').object_id)
498
+ .to eq(f2.object_id)
499
+ end
500
+ end
501
+
502
+ describe '#character_count(text)' do
503
+ it 'works on TTF fonts' do
504
+ pdf.font("#{Prawn::DATADIR}/fonts/gkai00mp.ttf")
505
+ expect(pdf.font.character_count('こんにちは世界')).to eq(7)
506
+ expect(pdf.font.character_count('Hello, world!')).to eq(13)
507
+ end
508
+
509
+ it 'works on AFM fonts' do
510
+ expect(pdf.font.character_count('Hello, world!')).to eq(13)
511
+ end
512
+ end
513
+ end