prawn 2.4.0 → 2.5.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (203) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data/lib/prawn/document/bounding_box.rb +213 -141
  4. data/lib/prawn/document/column_box.rb +61 -26
  5. data/lib/prawn/document/internals.rb +25 -16
  6. data/lib/prawn/document/span.rb +20 -18
  7. data/lib/prawn/document.rb +257 -171
  8. data/lib/prawn/encoding.rb +2 -5
  9. data/lib/prawn/errors.rb +23 -34
  10. data/lib/prawn/font.rb +248 -135
  11. data/lib/prawn/font_metric_cache.rb +11 -10
  12. data/lib/prawn/fonts/afm.rb +85 -45
  13. data/lib/prawn/fonts/dfont.rb +7 -1
  14. data/lib/prawn/fonts/otf.rb +4 -1
  15. data/lib/prawn/fonts/to_unicode_cmap.rb +151 -0
  16. data/lib/prawn/fonts/ttc.rb +7 -2
  17. data/lib/prawn/fonts/ttf.rb +305 -93
  18. data/lib/prawn/fonts.rb +14 -0
  19. data/lib/prawn/graphics/blend_mode.rb +25 -28
  20. data/lib/prawn/graphics/cap_style.rb +9 -12
  21. data/lib/prawn/graphics/color.rb +57 -34
  22. data/lib/prawn/graphics/dash.rb +45 -42
  23. data/lib/prawn/graphics/join_style.rb +17 -11
  24. data/lib/prawn/graphics/patterns.rb +190 -69
  25. data/lib/prawn/graphics/transformation.rb +48 -41
  26. data/lib/prawn/graphics/transparency.rb +16 -40
  27. data/lib/prawn/graphics.rb +363 -253
  28. data/lib/prawn/grid.rb +184 -57
  29. data/lib/prawn/image_handler.rb +27 -10
  30. data/lib/prawn/images/image.rb +8 -10
  31. data/lib/prawn/images/jpg.rb +42 -19
  32. data/lib/prawn/images/png.rb +92 -41
  33. data/lib/prawn/images.rb +44 -57
  34. data/lib/prawn/measurement_extensions.rb +39 -8
  35. data/lib/prawn/measurements.rb +60 -5
  36. data/lib/prawn/outline.rb +114 -108
  37. data/lib/prawn/repeater.rb +51 -35
  38. data/lib/prawn/security/arcfour.rb +4 -4
  39. data/lib/prawn/security.rb +75 -70
  40. data/lib/prawn/soft_mask.rb +42 -30
  41. data/lib/prawn/stamp.rb +38 -42
  42. data/lib/prawn/text/box.rb +146 -96
  43. data/lib/prawn/text/formatted/arranger.rb +87 -26
  44. data/lib/prawn/text/formatted/box.rb +221 -150
  45. data/lib/prawn/text/formatted/fragment.rb +130 -14
  46. data/lib/prawn/text/formatted/line_wrap.rb +33 -24
  47. data/lib/prawn/text/formatted/parser.rb +112 -72
  48. data/lib/prawn/text/formatted/wrap.rb +12 -17
  49. data/lib/prawn/text/formatted.rb +75 -0
  50. data/lib/prawn/text.rb +441 -196
  51. data/lib/prawn/transformation_stack.rb +29 -10
  52. data/lib/prawn/utilities.rb +13 -13
  53. data/lib/prawn/version.rb +2 -1
  54. data/lib/prawn/view.rb +68 -53
  55. data/lib/prawn.rb +23 -18
  56. data.tar.gz.sig +0 -0
  57. metadata +54 -177
  58. metadata.gz.sig +0 -0
  59. data/.yardopts +0 -10
  60. data/Gemfile +0 -5
  61. data/Rakefile +0 -25
  62. data/manual/absolute_position.pdf +0 -0
  63. data/manual/basic_concepts/adding_pages.rb +0 -26
  64. data/manual/basic_concepts/basic_concepts.rb +0 -43
  65. data/manual/basic_concepts/creation.rb +0 -38
  66. data/manual/basic_concepts/cursor.rb +0 -32
  67. data/manual/basic_concepts/measurement.rb +0 -24
  68. data/manual/basic_concepts/origin.rb +0 -37
  69. data/manual/basic_concepts/other_cursor_helpers.rb +0 -39
  70. data/manual/basic_concepts/view.rb +0 -48
  71. data/manual/bounding_box/bounding_box.rb +0 -41
  72. data/manual/bounding_box/bounds.rb +0 -48
  73. data/manual/bounding_box/canvas.rb +0 -23
  74. data/manual/bounding_box/creation.rb +0 -22
  75. data/manual/bounding_box/indentation.rb +0 -45
  76. data/manual/bounding_box/nesting.rb +0 -52
  77. data/manual/bounding_box/russian_boxes.rb +0 -40
  78. data/manual/bounding_box/stretchy.rb +0 -29
  79. data/manual/contents.rb +0 -35
  80. data/manual/cover.rb +0 -43
  81. data/manual/document_and_page_options/background.rb +0 -29
  82. data/manual/document_and_page_options/document_and_page_options.rb +0 -34
  83. data/manual/document_and_page_options/metadata.rb +0 -25
  84. data/manual/document_and_page_options/page_margins.rb +0 -36
  85. data/manual/document_and_page_options/page_size.rb +0 -34
  86. data/manual/document_and_page_options/print_scaling.rb +0 -23
  87. data/manual/example_helper.rb +0 -8
  88. data/manual/graphics/blend_mode.rb +0 -52
  89. data/manual/graphics/circle_and_ellipse.rb +0 -21
  90. data/manual/graphics/color.rb +0 -22
  91. data/manual/graphics/common_lines.rb +0 -29
  92. data/manual/graphics/fill_and_stroke.rb +0 -41
  93. data/manual/graphics/fill_rules.rb +0 -38
  94. data/manual/graphics/gradients.rb +0 -43
  95. data/manual/graphics/graphics.rb +0 -64
  96. data/manual/graphics/helper.rb +0 -34
  97. data/manual/graphics/line_width.rb +0 -36
  98. data/manual/graphics/lines_and_curves.rb +0 -40
  99. data/manual/graphics/polygon.rb +0 -27
  100. data/manual/graphics/rectangle.rb +0 -20
  101. data/manual/graphics/rotate.rb +0 -25
  102. data/manual/graphics/scale.rb +0 -42
  103. data/manual/graphics/soft_masks.rb +0 -44
  104. data/manual/graphics/stroke_cap.rb +0 -30
  105. data/manual/graphics/stroke_dash.rb +0 -47
  106. data/manual/graphics/stroke_join.rb +0 -29
  107. data/manual/graphics/translate.rb +0 -29
  108. data/manual/graphics/transparency.rb +0 -33
  109. data/manual/how_to_read_this_manual.rb +0 -39
  110. data/manual/images/absolute_position.rb +0 -22
  111. data/manual/images/fit.rb +0 -20
  112. data/manual/images/horizontal.rb +0 -24
  113. data/manual/images/images.rb +0 -41
  114. data/manual/images/plain_image.rb +0 -17
  115. data/manual/images/scale.rb +0 -21
  116. data/manual/images/vertical.rb +0 -30
  117. data/manual/images/width_and_height.rb +0 -24
  118. data/manual/layout/boxes.rb +0 -26
  119. data/manual/layout/content.rb +0 -24
  120. data/manual/layout/layout.rb +0 -27
  121. data/manual/layout/simple_grid.rb +0 -22
  122. data/manual/outline/add_subsection_to.rb +0 -60
  123. data/manual/outline/insert_section_after.rb +0 -46
  124. data/manual/outline/outline.rb +0 -33
  125. data/manual/outline/sections_and_pages.rb +0 -66
  126. data/manual/repeatable_content/alternate_page_numbering.rb +0 -36
  127. data/manual/repeatable_content/page_numbering.rb +0 -55
  128. data/manual/repeatable_content/repeatable_content.rb +0 -35
  129. data/manual/repeatable_content/repeater.rb +0 -54
  130. data/manual/repeatable_content/stamp.rb +0 -40
  131. data/manual/security/encryption.rb +0 -28
  132. data/manual/security/permissions.rb +0 -43
  133. data/manual/security/security.rb +0 -28
  134. data/manual/table.rb +0 -16
  135. data/manual/text/alignment.rb +0 -43
  136. data/manual/text/color.rb +0 -24
  137. data/manual/text/column_box.rb +0 -30
  138. data/manual/text/fallback_fonts.rb +0 -41
  139. data/manual/text/font.rb +0 -40
  140. data/manual/text/font_size.rb +0 -44
  141. data/manual/text/font_style.rb +0 -25
  142. data/manual/text/formatted_callbacks.rb +0 -70
  143. data/manual/text/formatted_text.rb +0 -61
  144. data/manual/text/free_flowing_text.rb +0 -50
  145. data/manual/text/inline.rb +0 -40
  146. data/manual/text/kerning_and_character_spacing.rb +0 -38
  147. data/manual/text/leading.rb +0 -24
  148. data/manual/text/line_wrapping.rb +0 -60
  149. data/manual/text/paragraph_indentation.rb +0 -31
  150. data/manual/text/positioned_text.rb +0 -37
  151. data/manual/text/registering_families.rb +0 -51
  152. data/manual/text/rendering_and_color.rb +0 -36
  153. data/manual/text/right_to_left_text.rb +0 -54
  154. data/manual/text/rotation.rb +0 -52
  155. data/manual/text/single_usage.rb +0 -36
  156. data/manual/text/text.rb +0 -75
  157. data/manual/text/text_box_excess.rb +0 -35
  158. data/manual/text/text_box_extensions.rb +0 -48
  159. data/manual/text/text_box_overflow.rb +0 -51
  160. data/manual/text/utf8.rb +0 -27
  161. data/manual/text/win_ansi_charset.rb +0 -62
  162. data/prawn.gemspec +0 -51
  163. data/spec/data/curves.pdf +0 -66
  164. data/spec/extensions/encoding_helpers.rb +0 -11
  165. data/spec/prawn/document/bounding_box_spec.rb +0 -550
  166. data/spec/prawn/document/column_box_spec.rb +0 -75
  167. data/spec/prawn/document/security_spec.rb +0 -176
  168. data/spec/prawn/document_annotations_spec.rb +0 -76
  169. data/spec/prawn/document_destinations_spec.rb +0 -15
  170. data/spec/prawn/document_grid_spec.rb +0 -99
  171. data/spec/prawn/document_reference_spec.rb +0 -27
  172. data/spec/prawn/document_span_spec.rb +0 -44
  173. data/spec/prawn/document_spec.rb +0 -805
  174. data/spec/prawn/font_metric_cache_spec.rb +0 -54
  175. data/spec/prawn/font_spec.rb +0 -544
  176. data/spec/prawn/graphics/blend_mode_spec.rb +0 -63
  177. data/spec/prawn/graphics/transparency_spec.rb +0 -81
  178. data/spec/prawn/graphics_spec.rb +0 -872
  179. data/spec/prawn/graphics_stroke_styles_spec.rb +0 -229
  180. data/spec/prawn/image_handler_spec.rb +0 -53
  181. data/spec/prawn/images/jpg_spec.rb +0 -20
  182. data/spec/prawn/images/png_spec.rb +0 -283
  183. data/spec/prawn/images_spec.rb +0 -229
  184. data/spec/prawn/measurements_extensions_spec.rb +0 -24
  185. data/spec/prawn/outline_spec.rb +0 -512
  186. data/spec/prawn/repeater_spec.rb +0 -166
  187. data/spec/prawn/soft_mask_spec.rb +0 -74
  188. data/spec/prawn/stamp_spec.rb +0 -173
  189. data/spec/prawn/text/box_spec.rb +0 -1110
  190. data/spec/prawn/text/formatted/arranger_spec.rb +0 -466
  191. data/spec/prawn/text/formatted/box_spec.rb +0 -849
  192. data/spec/prawn/text/formatted/fragment_spec.rb +0 -343
  193. data/spec/prawn/text/formatted/line_wrap_spec.rb +0 -495
  194. data/spec/prawn/text/formatted/parser_spec.rb +0 -697
  195. data/spec/prawn/text_draw_text_spec.rb +0 -150
  196. data/spec/prawn/text_rendering_mode_spec.rb +0 -48
  197. data/spec/prawn/text_spacing_spec.rb +0 -95
  198. data/spec/prawn/text_spec.rb +0 -603
  199. data/spec/prawn/text_with_inline_formatting_spec.rb +0 -35
  200. data/spec/prawn/transformation_stack_spec.rb +0 -66
  201. data/spec/prawn/view_spec.rb +0 -63
  202. data/spec/prawn_manual_spec.rb +0 -35
  203. data/spec/spec_helper.rb +0 -48
@@ -1,24 +1,31 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # Implements AFM font support for Prawn
4
- #
5
- # Copyright May 2008, Gregory Brown / James Healy. All Rights Reserved.
6
- #
7
- # This is free software. Please see the LICENSE and COPYING files for details.
8
-
9
3
  require_relative '../encoding'
10
4
 
11
5
  module Prawn
12
6
  module Fonts
13
- # @private
14
-
7
+ # AFM font. AFM stands for Adobe Font Metrics. It's not a complete font, it
8
+ # doesn't provide actual glyph outlines. It only contains glyph metrics to
9
+ # make text layout possible. AFM is used for PDF built-in fonts. Those
10
+ # fonts are supposed to be present on the target system making it possible
11
+ # to save a little bit of space by not embedding the fonts. A file that uses
12
+ # these fonts can not be read on a system that doesn't have these fonts
13
+ # installed.
14
+ #
15
+ # @note You shouldn't use this class directly.
15
16
  class AFM < Font
16
17
  class << self
18
+ # Prawn would warn you if you're using non-ASCII glyphs with AFM fonts
19
+ # as not all implementations provide those glyphs. This attribute
20
+ # suppresses that warning.
21
+ #
22
+ # @return [Boolean] (false)
17
23
  attr_accessor :hide_m17n_warning
18
24
  end
19
25
 
20
26
  self.hide_m17n_warning = false
21
27
 
28
+ # List of PDF built-in fonts.
22
29
  BUILT_INS = %w[
23
30
  Courier Helvetica Times-Roman Symbol ZapfDingbats
24
31
  Courier-Bold Courier-Oblique Courier-BoldOblique
@@ -26,10 +33,16 @@ module Prawn
26
33
  Helvetica-Bold Helvetica-Oblique Helvetica-BoldOblique
27
34
  ].freeze
28
35
 
36
+ # Does this font support Unicode?
37
+ #
38
+ # @return [false]
29
39
  def unicode?
30
40
  false
31
41
  end
32
42
 
43
+ # Paths to look for AFM files at.
44
+ #
45
+ # @return [Array<String>]
33
46
  def self.metrics_path
34
47
  @metrics_path ||=
35
48
  if ENV['METRICS']
@@ -39,19 +52,27 @@ module Prawn
39
52
  '.', '/usr/lib/afm',
40
53
  '/usr/local/lib/afm',
41
54
  '/usr/openwin/lib/fonts/afm',
42
- "#{Prawn::DATADIR}/fonts"
55
+ "#{Prawn::DATADIR}/fonts",
43
56
  ]
44
57
  end
45
58
  end
46
59
 
47
- attr_reader :attributes #:nodoc:
60
+ # @private
61
+ attr_reader :attributes
48
62
 
49
- # parse each ATM font file once only
63
+ # Parsed AFM data cache.
64
+ #
65
+ # @return [SynchronizedCache]
50
66
  def self.font_data
51
67
  @font_data ||= SynchronizedCache.new
52
68
  end
53
69
 
54
- def initialize(document, name, options = {}) #:nodoc:
70
+ # @param document [Prawn::Document]
71
+ # @param name [String]
72
+ # @param options [Hash]
73
+ # @option options :family [String]
74
+ # @option options :style [Symbol]
75
+ def initialize(document, name, options = {})
55
76
  name ||= options[:family]
56
77
  unless BUILT_INS.include?(name)
57
78
  raise Prawn::Errors::UnknownFont,
@@ -62,7 +83,7 @@ module Prawn
62
83
 
63
84
  file_name = @name.dup
64
85
  file_name << '.afm' unless /\.afm$/.match?(file_name)
65
- file_name = file_name[0] == '/' ? file_name : find_font(file_name)
86
+ file_name = find_font(file_name) unless file_name[0] == '/'
66
87
 
67
88
  font_data = self.class.font_data[file_name] ||= parse_afm(file_name)
68
89
  @glyph_widths = font_data[:glyph_widths]
@@ -72,19 +93,27 @@ module Prawn
72
93
  @kern_pair_table = font_data[:kern_pair_table]
73
94
  @attributes = font_data[:attributes]
74
95
 
75
- @ascender = @attributes['ascender'].to_i
76
- @descender = @attributes['descender'].to_i
96
+ @ascender = Integer(@attributes.fetch('ascender', '0'), 10)
97
+ @descender = Integer(@attributes.fetch('descender', '0'), 10)
77
98
  @line_gap = Float(bbox[3] - bbox[1]) - (@ascender - @descender)
78
99
  end
79
100
 
80
- # The font bbox, as an array of integers
101
+ # The font bbox.
81
102
  #
103
+ # @return [Array(Number, Number, Number, Number)]
82
104
  def bbox
83
105
  @bbox ||= @attributes['fontbbox'].split(/\s+/).map { |e| Integer(e) }
84
106
  end
85
107
 
86
- # NOTE: String *must* be encoded as WinAnsi
87
- def compute_width_of(string, options = {}) #:nodoc:
108
+ # Compute width of a string at the specified size, optionally with kerning
109
+ # applied.
110
+ #
111
+ # @param string [String] *must* be encoded as WinAnsi
112
+ # @param options [Hash{Symbol => any}]
113
+ # @option options :size [Number]
114
+ # @option options :kerning [Boolean] (false)
115
+ # @return [Number]
116
+ def compute_width_of(string, options = {})
88
117
  scale = (options[:size] || size) / 1000.0
89
118
 
90
119
  if options[:kerning]
@@ -96,18 +125,19 @@ module Prawn
96
125
  end
97
126
  end
98
127
 
99
- # Returns true if the font has kerning data, false otherwise
128
+ # Does this font contain kerning data.
100
129
  #
101
- # rubocop: disable Naming/PredicateName
102
- def has_kerning_data?
130
+ # @return [Boolean]
131
+ def has_kerning_data? # rubocop: disable Naming/PredicateName
103
132
  @kern_pairs.any?
104
133
  end
105
- # rubocop: enable Naming/PredicateName
106
134
 
107
- # built-in fonts only work with winansi encoding, so translate the
135
+ # Built-in fonts only work with WinAnsi encoding, so translate the
108
136
  # string. Changes the encoding in-place, so the argument itself
109
137
  # is replaced with a string in WinAnsi encoding.
110
138
  #
139
+ # @param text [String]
140
+ # @return [String]
111
141
  def normalize_encoding(text)
112
142
  text.encode('windows-1252')
113
143
  rescue ::Encoding::InvalidByteSequenceError,
@@ -115,17 +145,23 @@ module Prawn
115
145
 
116
146
  raise Prawn::Errors::IncompatibleStringEncoding,
117
147
  "Your document includes text that's not compatible with the " \
118
- "Windows-1252 character set.\n" \
119
- 'If you need full UTF-8 support, use external fonts instead of ' \
120
- "PDF's built-in fonts.\n"
148
+ "Windows-1252 character set.\n" \
149
+ 'If you need full UTF-8 support, use external fonts instead of ' \
150
+ "PDF's built-in fonts.\n"
121
151
  end
122
152
 
153
+ # Encode text to UTF-8.
154
+ #
155
+ # @param text [String]
156
+ # @return [String]
123
157
  def to_utf8(text)
124
158
  text.encode('UTF-8')
125
159
  end
126
160
 
127
- # Returns the number of characters in +str+ (a WinAnsi-encoded string).
161
+ # Returns the number of characters in `str` (a WinAnsi-encoded string).
128
162
  #
163
+ # @param str [String]
164
+ # @return [Integer]
129
165
  def character_count(str)
130
166
  str.length
131
167
  end
@@ -137,15 +173,23 @@ module Prawn
137
173
  # is either a string or an array (for kerned text).
138
174
  #
139
175
  # For Adobe fonts, there is only ever a single subset, so
140
- # the first element of the array is "0", and the second is
176
+ # the first element of the array is `0`, and the second is
141
177
  # the string itself (or an array, if kerning is performed).
142
178
  #
143
- # The +text+ parameter must be in WinAnsi encoding (cp1252).
179
+ # The `text` argument must be in WinAnsi encoding (cp1252).
144
180
  #
181
+ # @param text [String]
182
+ # @param options [Hash{Symbol => any}]
183
+ # @option options :kerning [Boolean]
184
+ # @return [Array<Array(0, (String, Array)>]
145
185
  def encode_text(text, options = {})
146
186
  [[0, options[:kerning] ? kern(text) : text]]
147
187
  end
148
188
 
189
+ # Does this font has a glyph for the character?
190
+ #
191
+ # @param char [String]
192
+ # @return [Boolean]
149
193
  def glyph_present?(char)
150
194
  !normalize_encoding(char).nil?
151
195
  rescue Prawn::Errors::IncompatibleStringEncoding
@@ -158,7 +202,7 @@ module Prawn
158
202
  font_dict = {
159
203
  Type: :Font,
160
204
  Subtype: :Type1,
161
- BaseFont: name.to_sym
205
+ BaseFont: name.to_sym,
162
206
  }
163
207
 
164
208
  # Symbolic AFM fonts (Symbol, ZapfDingbats) have their own encodings
@@ -172,7 +216,7 @@ module Prawn
172
216
  end
173
217
 
174
218
  def find_font(file)
175
- self.class.metrics_path.find { |f| File.exist? "#{f}/#{file}" } +
219
+ self.class.metrics_path.find { |f| File.exist?("#{f}/#{file}") } +
176
220
  "/#{file}"
177
221
  rescue NoMethodError
178
222
  raise Prawn::Errors::UnknownFont,
@@ -185,14 +229,14 @@ module Prawn
185
229
  glyph_widths: {},
186
230
  bounding_boxes: {},
187
231
  kern_pairs: {},
188
- attributes: {}
232
+ attributes: {},
189
233
  }
190
234
  section = []
191
235
 
192
236
  File.foreach(file_name) do |line|
193
237
  case line
194
238
  when /^Start(\w+)/
195
- section.push Regexp.last_match(1)
239
+ section.push(Regexp.last_match(1))
196
240
  next
197
241
  when /^End(\w+)/
198
242
  section.pop
@@ -204,13 +248,13 @@ module Prawn
204
248
  next unless /^CH?\s/.match?(line)
205
249
 
206
250
  name = line[/\bN\s+(\.?\w+)\s*;/, 1]
207
- data[:glyph_widths][name] = line[/\bWX\s+(\d+)\s*;/, 1].to_i
251
+ data[:glyph_widths][name] = Integer(line[/\bWX\s+(\d+)\s*;/, 1], 10)
208
252
  data[:bounding_boxes][name] = line[/\bB\s+([^;]+);/, 1].to_s.rstrip
209
253
  when %w[FontMetrics KernData KernPairs]
210
254
  next unless line =~ /^KPX\s+(\.?\w+)\s+(\.?\w+)\s+(-?\d+)/
211
255
 
212
256
  data[:kern_pairs][[Regexp.last_match(1), Regexp.last_match(2)]] =
213
- Regexp.last_match(3).to_i
257
+ Integer(Regexp.last_match(3), 10)
214
258
  when %w[FontMetrics KernData TrackKern],
215
259
  %w[FontMetrics Composites]
216
260
  next
@@ -222,15 +266,11 @@ module Prawn
222
266
  # process data parsed from AFM file to build tables which
223
267
  # will be used when measuring and kerning text
224
268
  data[:glyph_table] =
225
- (0..255).map do |i|
226
- data[:glyph_widths][Encoding::WinAnsi::CHARACTERS[i]].to_i
227
- end
269
+ (0..255).map { |i|
270
+ data[:glyph_widths].fetch(Encoding::WinAnsi::CHARACTERS[i], 0)
271
+ }
228
272
 
229
- character_hash = Hash[
230
- Encoding::WinAnsi::CHARACTERS.zip(
231
- (0..Encoding::WinAnsi::CHARACTERS.size).to_a
232
- )
233
- ]
273
+ character_hash = Encoding::WinAnsi::CHARACTERS.zip((0..Encoding::WinAnsi::CHARACTERS.size).to_a).to_h
234
274
  data[:kern_pair_table] =
235
275
  data[:kern_pairs].each_with_object({}) do |p, h|
236
276
  h[p[0].map { |n| character_hash[n] }] = p[1]
@@ -254,7 +294,7 @@ module Prawn
254
294
  end
255
295
 
256
296
  # converts a string into an array with spacing offsets
257
- # bewteen characters that need to be kerned
297
+ # between characters that need to be kerned
258
298
  #
259
299
  # String *must* be encoded as WinAnsi
260
300
  #
@@ -273,7 +313,7 @@ module Prawn
273
313
  end
274
314
 
275
315
  kerned.map do |e|
276
- e = e.is_a?(Array) ? e.pack('C*') : e
316
+ e = e.pack('C*') if e.is_a?(Array)
277
317
  if e.respond_to?(:force_encoding)
278
318
  e.force_encoding(::Encoding::Windows_1252)
279
319
  else
@@ -4,7 +4,9 @@ require_relative 'ttf'
4
4
 
5
5
  module Prawn
6
6
  module Fonts
7
- # @private
7
+ # DFONT font. DFONT is a bunch of TrueType fonts in a single file.
8
+ #
9
+ # @note You shouldn't use this class directly.
8
10
  class DFont < TTF
9
11
  # Returns a list of the names of all named fonts in the given dfont file.
10
12
  # Note that fonts are not required to be named in a dfont file, so the
@@ -12,6 +14,8 @@ module Prawn
12
14
  # the list is returned in no particular order, so the first font in the
13
15
  # list is not necessarily the font at index 0 in the file.
14
16
  #
17
+ # @param file [String]
18
+ # @return [Array<String>]
15
19
  def self.named_fonts(file)
16
20
  TTFunk::ResourceFile.open(file) do |f|
17
21
  return f.resources_for('sfnt')
@@ -20,6 +24,8 @@ module Prawn
20
24
 
21
25
  # Returns the number of fonts contained in the dfont file.
22
26
  #
27
+ # @param file [String]
28
+ # @return [Integer]
23
29
  def self.font_count(file)
24
30
  TTFunk::ResourceFile.open(file) do |f|
25
31
  return f.map['sfnt'][:list].length
@@ -4,7 +4,10 @@ require_relative 'ttf'
4
4
 
5
5
  module Prawn
6
6
  module Fonts
7
- # @private
7
+ # OpenType font. This class is used mostly to distinguish OTF from TTF.
8
+ # All functionality is in the {Fonts::TTF} class.
9
+ #
10
+ # @note You shouldn't use this class directly.
8
11
  class OTF < TTF
9
12
  end
10
13
  end
@@ -0,0 +1,151 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Prawn
4
+ module Fonts
5
+ # This class generates ToUnicode CMap for embedde TrueType/OpenType fonts.
6
+ # It's a separate format and is somewhat complicated so it has its own
7
+ # place.
8
+ #
9
+ # @private
10
+ class ToUnicodeCMap
11
+ # mapping is expected to be a hash with keys being character codes (in
12
+ # broad sense, as used in the showing operation strings) and values being
13
+ # Unicode code points.
14
+ def initialize(mapping, code_space_size = nil)
15
+ @mapping = mapping
16
+ @code_space_size = code_space_size
17
+ end
18
+
19
+ # Generate CMap.
20
+ #
21
+ # @return [String]
22
+ def generate
23
+ chunks = []
24
+
25
+ # Header
26
+ chunks << <<~HEADER.chomp
27
+ /CIDInit /ProcSet findresource begin
28
+ 12 dict begin
29
+ begincmap
30
+ /CIDSystemInfo 3 dict dup begin
31
+ /Registry (Adobe) def
32
+ /Ordering (UCS) def
33
+ /Supplement 0 def
34
+ end def
35
+ /CMapName /Adobe-Identity-UCS def
36
+ /CMapType 2 def
37
+ HEADER
38
+
39
+ max_glyph_index = mapping.keys.max
40
+ # Range
41
+ code_space_size = (max_glyph_index.bit_length / 8.0).ceil
42
+
43
+ used_code_space_size = @code_space_size || code_space_size
44
+
45
+ # In CMap codespaces are not sequentional, they're ranges in
46
+ # a multi-dimentional space. Each byte is considered separately. So we
47
+ # have to maximally extend the lower bytes in order to allow for
48
+ # continuos mapping.
49
+ # We only keep the highest byte because usually it's lower than
50
+ # maximally allowed and we don't want to cover that unused space.
51
+ code_space_max = max_glyph_index | ('ff' * (code_space_size - 1)).to_i(16)
52
+
53
+ chunks << '1 begincodespacerange'
54
+ chunks << format("<%0#{used_code_space_size * 2}X><%0#{used_code_space_size * 2}X>", 0, code_space_max)
55
+ chunks << 'endcodespacerange'
56
+
57
+ # Mapping
58
+ all_spans = mapping_spans(mapping.reject { |gid, cid| gid.zero? || (0xd800..0xdfff).cover?(cid) })
59
+
60
+ short_spans, long_spans = all_spans.partition { |span| span[0] == :short }
61
+
62
+ long_spans
63
+ .each_slice(100) do |spans|
64
+ chunks << "#{spans.length} beginbfrange"
65
+
66
+ spans.each do |type, span|
67
+ # rubocop: disable Lint/FormatParameterMismatch # false positive
68
+ case type
69
+ when :fully_sorted
70
+ chunks << format(
71
+ "<%0#{code_space_size * 2}X><%0#{code_space_size * 2}X><%s>",
72
+ span.first[0],
73
+ span.last[0],
74
+ span.first[1].chr(::Encoding::UTF_16BE).unpack1('H*'),
75
+ )
76
+ when :index_sorted
77
+ chunks << format(
78
+ "<%0#{code_space_size * 2}X><%0#{code_space_size * 2}X>[%s]",
79
+ span.first[0],
80
+ span.last[0],
81
+ span.map { |_, cid| "<#{cid.chr(::Encoding::UTF_16BE).unpack1('H*')}>" }.join(''),
82
+ )
83
+ end
84
+ # rubocop: enable Lint/FormatParameterMismatch
85
+ end
86
+
87
+ chunks << 'endbfrange'
88
+ end
89
+
90
+ short_spans
91
+ .map { |_type, slice| slice.flatten(1) }
92
+ .each_slice(100) do |mapping|
93
+ chunks << "#{mapping.length} beginbfchar"
94
+ chunks.concat(
95
+ mapping.map { |(gid, cid)|
96
+ # rubocop: disable Lint/FormatParameterMismatch # false positive
97
+ format(
98
+ "<%0#{code_space_size * 2}X><%s>",
99
+ gid,
100
+ cid.chr(::Encoding::UTF_16BE).unpack1('H*'),
101
+ )
102
+ # rubocop: enable Lint/FormatParameterMismatch
103
+ },
104
+ )
105
+ chunks << 'endbfchar'
106
+ end
107
+
108
+ # Footer
109
+ chunks << <<~FOOTER.chomp
110
+ endcmap
111
+ CMapName currentdict /CMap defineresource pop
112
+ end
113
+ end
114
+ FOOTER
115
+
116
+ chunks.join("\n")
117
+ end
118
+
119
+ private
120
+
121
+ attr_reader :mapping
122
+
123
+ attr_reader :cmap
124
+ attr_reader :code_space_size
125
+ attr_reader :code_space_max
126
+
127
+ def mapping_spans(mapping)
128
+ mapping
129
+ .sort
130
+ .slice_when { |a, b| (b[0] - a[0]) != 1 } # Slice at key discontinuity
131
+ .flat_map { |slice|
132
+ if slice.length == 1
133
+ [[:short, slice]]
134
+ else
135
+ continuous_slices, discontinuous_slices =
136
+ slice
137
+ .slice_when { |a, b| b[1] - a[1] != 1 } # Slice at value discontinuity
138
+ .partition { |subslice| subslice.length > 1 }
139
+
140
+ discontinuous_slices
141
+ .flatten(1) # Join together
142
+ .slice_when { |a, b| (b[0] - a[0]) != 1 } # Slice at key discontinuity, again
143
+ .map { |span| span.length > 1 ? [:index_sorted, span] : [:short, slice] } +
144
+ continuous_slices.map { |span| [:fully_sorted, span] }
145
+ end
146
+ } # rubocop: disable Style/MultilineBlockChain
147
+ .sort_by { |span| span[1][0][0] } # Sort span start key
148
+ end
149
+ end
150
+ end
151
+ end
@@ -4,11 +4,16 @@ require_relative 'ttf'
4
4
 
5
5
  module Prawn
6
6
  module Fonts
7
- # @private
7
+ # TrueType Collection font. It's an SFNT-based format that contains a bunch
8
+ # of TrueType fonts in a single file.
9
+ #
10
+ # @note You shouldn't use this class directly.
8
11
  class TTC < TTF
9
12
  # Returns a list of the names of all named fonts in the given ttc file.
10
13
  # They are returned in order of their appearance in the file.
11
14
  #
15
+ # @param file [String]
16
+ # @return [Array<String>]
12
17
  def self.font_names(file)
13
18
  TTFunk::Collection.open(file) do |ttc|
14
19
  ttc.map { |font| font.name.font_name.first }
@@ -20,7 +25,7 @@ module Prawn
20
25
  def read_ttf_file
21
26
  TTFunk::File.from_ttc(
22
27
  @name,
23
- font_option_to_index(@name, @options[:font])
28
+ font_option_to_index(@name, @options[:font]),
24
29
  )
25
30
  end
26
31