prawn 2.0.2 → 2.3.0

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