prawn 2.3.0 → 2.5.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 (203) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data/lib/prawn/document/bounding_box.rb +223 -143
  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 +21 -18
  7. data/lib/prawn/document.rb +273 -182
  8. data/lib/prawn/encoding.rb +2 -5
  9. data/lib/prawn/errors.rb +23 -34
  10. data/lib/prawn/font.rb +254 -139
  11. data/lib/prawn/font_metric_cache.rb +18 -16
  12. data/lib/prawn/fonts/afm.rb +99 -57
  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 +345 -107
  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 +75 -50
  22. data/lib/prawn/graphics/dash.rb +45 -42
  23. data/lib/prawn/graphics/join_style.rb +18 -12
  24. data/lib/prawn/graphics/patterns.rb +239 -110
  25. data/lib/prawn/graphics/transformation.rb +51 -44
  26. data/lib/prawn/graphics/transparency.rb +16 -40
  27. data/lib/prawn/graphics.rb +370 -260
  28. data/lib/prawn/grid.rb +219 -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 +46 -20
  32. data/lib/prawn/images/png.rb +94 -42
  33. data/lib/prawn/images.rb +70 -81
  34. data/lib/prawn/measurement_extensions.rb +39 -8
  35. data/lib/prawn/measurements.rb +60 -5
  36. data/lib/prawn/outline.rb +120 -113
  37. data/lib/prawn/repeater.rb +52 -36
  38. data/lib/prawn/security/arcfour.rb +4 -4
  39. data/lib/prawn/security.rb +106 -98
  40. data/lib/prawn/soft_mask.rb +42 -30
  41. data/lib/prawn/stamp.rb +38 -42
  42. data/lib/prawn/text/box.rb +156 -105
  43. data/lib/prawn/text/formatted/arranger.rb +121 -41
  44. data/lib/prawn/text/formatted/box.rb +239 -163
  45. data/lib/prawn/text/formatted/fragment.rb +130 -14
  46. data/lib/prawn/text/formatted/line_wrap.rb +49 -38
  47. data/lib/prawn/text/formatted/parser.rb +116 -74
  48. data/lib/prawn/text/formatted/wrap.rb +25 -26
  49. data/lib/prawn/text/formatted.rb +75 -0
  50. data/lib/prawn/text.rb +456 -211
  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 +69 -54
  55. data/lib/prawn.rb +24 -18
  56. data.tar.gz.sig +0 -0
  57. metadata +55 -262
  58. metadata.gz.sig +3 -4
  59. data/.yardopts +0 -10
  60. data/Gemfile +0 -5
  61. data/Rakefile +0 -54
  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 -25
  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 -22
  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 -37
  94. data/manual/graphics/gradients.rb +0 -43
  95. data/manual/graphics/graphics.rb +0 -64
  96. data/manual/graphics/helper.rb +0 -27
  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 -28
  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 -27
  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 -41
  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 -22
  142. data/manual/text/formatted_callbacks.rb +0 -65
  143. data/manual/text/formatted_text.rb +0 -58
  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 -32
  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 -47
  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 -49
  160. data/manual/text/utf8.rb +0 -27
  161. data/manual/text/win_ansi_charset.rb +0 -62
  162. data/prawn.gemspec +0 -57
  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 -546
  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 -36
  173. data/spec/prawn/document_spec.rb +0 -802
  174. data/spec/prawn/font_metric_cache_spec.rb +0 -54
  175. data/spec/prawn/font_spec.rb +0 -542
  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 -837
  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 -224
  184. data/spec/prawn/measurements_extensions_spec.rb +0 -24
  185. data/spec/prawn/outline_spec.rb +0 -412
  186. data/spec/prawn/repeater_spec.rb +0 -165
  187. data/spec/prawn/soft_mask_spec.rb +0 -74
  188. data/spec/prawn/stamp_spec.rb +0 -172
  189. data/spec/prawn/text/box_spec.rb +0 -1112
  190. data/spec/prawn/text/formatted/arranger_spec.rb +0 -466
  191. data/spec/prawn/text/formatted/box_spec.rb +0 -846
  192. data/spec/prawn/text/formatted/fragment_spec.rb +0 -343
  193. data/spec/prawn/text/formatted/line_wrap_spec.rb +0 -494
  194. data/spec/prawn/text/formatted/parser_spec.rb +0 -697
  195. data/spec/prawn/text_draw_text_spec.rb +0 -149
  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,19 +1,12 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # font_metric_cache.rb : The Prawn font class
4
- #
5
- # Copyright Dec 2012, Kenneth Kalmer. All Rights Reserved.
6
- #
7
- # This is free software. Please see the LICENSE and COPYING files for details.
8
- #
9
-
10
3
  module Prawn
11
- # Cache used internally by Prawn::Document instances to calculate the width
4
+ # Cache used internally by {Prawn::Document} instances to calculate the width
12
5
  # of various strings for layout purposes.
13
6
  #
14
7
  # @private
15
8
  class FontMetricCache
16
- CacheEntry = Struct.new(:font, :options, :string)
9
+ CacheEntry = Struct.new(:font, :font_size, :options, :string)
17
10
 
18
11
  def initialize(document)
19
12
  @document = document
@@ -21,17 +14,26 @@ module Prawn
21
14
  @cache = {}
22
15
  end
23
16
 
17
+ # Get width of string.
18
+ #
19
+ # @param string [String]
20
+ # @param options [Hash{Symbol => any}]
21
+ # @option options :style [Symbol]
22
+ # @option options :size [Number]
23
+ # @option options :kerning [Boolean] (false)
24
+ # @return [Number]
24
25
  def width_of(string, options)
25
- f = if options[:style]
26
- # override style with :style => :bold
27
- @document.find_font(@document.font.family, style: options[:style])
28
- else
29
- @document.font
30
- end
26
+ f =
27
+ if options[:style]
28
+ # override style with :style => :bold
29
+ @document.find_font(@document.font.family, style: options[:style])
30
+ else
31
+ @document.font
32
+ end
31
33
 
32
34
  encoded_string = f.normalize_encoding(string)
33
35
 
34
- key = CacheEntry.new(f, options, encoded_string)
36
+ key = CacheEntry.new(f, @document.font_size, options, encoded_string)
35
37
 
36
38
  @cache[key] ||= f.compute_width_of(encoded_string, options)
37
39
 
@@ -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,31 +33,46 @@ 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
- @metrics_path ||= if ENV['METRICS']
35
- ENV['METRICS'].split(':')
36
- else
37
- [
38
- '.', '/usr/lib/afm',
39
- '/usr/local/lib/afm',
40
- '/usr/openwin/lib/fonts/afm',
41
- Prawn::DATADIR + '/fonts'
42
- ]
43
- end
47
+ @metrics_path ||=
48
+ if ENV['METRICS']
49
+ ENV['METRICS'].split(':')
50
+ else
51
+ [
52
+ '.', '/usr/lib/afm',
53
+ '/usr/local/lib/afm',
54
+ '/usr/openwin/lib/fonts/afm',
55
+ "#{Prawn::DATADIR}/fonts",
56
+ ]
57
+ end
44
58
  end
45
59
 
46
- attr_reader :attributes #:nodoc:
60
+ # @private
61
+ attr_reader :attributes
47
62
 
48
- # parse each ATM font file once only
63
+ # Parsed AFM data cache.
64
+ #
65
+ # @return [SynchronizedCache]
49
66
  def self.font_data
50
67
  @font_data ||= SynchronizedCache.new
51
68
  end
52
69
 
53
- 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 = {})
54
76
  name ||= options[:family]
55
77
  unless BUILT_INS.include?(name)
56
78
  raise Prawn::Errors::UnknownFont,
@@ -61,7 +83,7 @@ module Prawn
61
83
 
62
84
  file_name = @name.dup
63
85
  file_name << '.afm' unless /\.afm$/.match?(file_name)
64
- file_name = file_name[0] == '/' ? file_name : find_font(file_name)
86
+ file_name = find_font(file_name) unless file_name[0] == '/'
65
87
 
66
88
  font_data = self.class.font_data[file_name] ||= parse_afm(file_name)
67
89
  @glyph_widths = font_data[:glyph_widths]
@@ -71,42 +93,51 @@ module Prawn
71
93
  @kern_pair_table = font_data[:kern_pair_table]
72
94
  @attributes = font_data[:attributes]
73
95
 
74
- @ascender = @attributes['ascender'].to_i
75
- @descender = @attributes['descender'].to_i
96
+ @ascender = Integer(@attributes.fetch('ascender', '0'), 10)
97
+ @descender = Integer(@attributes.fetch('descender', '0'), 10)
76
98
  @line_gap = Float(bbox[3] - bbox[1]) - (@ascender - @descender)
77
99
  end
78
100
 
79
- # The font bbox, as an array of integers
101
+ # The font bbox.
80
102
  #
103
+ # @return [Array(Number, Number, Number, Number)]
81
104
  def bbox
82
105
  @bbox ||= @attributes['fontbbox'].split(/\s+/).map { |e| Integer(e) }
83
106
  end
84
107
 
85
- # NOTE: String *must* be encoded as WinAnsi
86
- 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 = {})
87
117
  scale = (options[:size] || size) / 1000.0
88
118
 
89
119
  if options[:kerning]
90
120
  strings, numbers = kern(string).partition { |e| e.is_a?(String) }
91
- total_kerning_offset = numbers.inject(0.0) { |a, e| a + e }
121
+ total_kerning_offset = numbers.sum
92
122
  (unscaled_width_of(strings.join) - total_kerning_offset) * scale
93
123
  else
94
124
  unscaled_width_of(string) * scale
95
125
  end
96
126
  end
97
127
 
98
- # Returns true if the font has kerning data, false otherwise
128
+ # Does this font contain kerning data.
99
129
  #
100
- # rubocop: disable Naming/PredicateName
101
- def has_kerning_data?
130
+ # @return [Boolean]
131
+ def has_kerning_data? # rubocop: disable Naming/PredicateName
102
132
  @kern_pairs.any?
103
133
  end
104
- # rubocop: enable Naming/PredicateName
105
134
 
106
- # built-in fonts only work with winansi encoding, so translate the
135
+ # Built-in fonts only work with WinAnsi encoding, so translate the
107
136
  # string. Changes the encoding in-place, so the argument itself
108
137
  # is replaced with a string in WinAnsi encoding.
109
138
  #
139
+ # @param text [String]
140
+ # @return [String]
110
141
  def normalize_encoding(text)
111
142
  text.encode('windows-1252')
112
143
  rescue ::Encoding::InvalidByteSequenceError,
@@ -114,17 +145,23 @@ module Prawn
114
145
 
115
146
  raise Prawn::Errors::IncompatibleStringEncoding,
116
147
  "Your document includes text that's not compatible with the " \
117
- "Windows-1252 character set.\n" \
118
- 'If you need full UTF-8 support, use external fonts instead of ' \
119
- "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"
120
151
  end
121
152
 
153
+ # Encode text to UTF-8.
154
+ #
155
+ # @param text [String]
156
+ # @return [String]
122
157
  def to_utf8(text)
123
158
  text.encode('UTF-8')
124
159
  end
125
160
 
126
- # Returns the number of characters in +str+ (a WinAnsi-encoded string).
161
+ # Returns the number of characters in `str` (a WinAnsi-encoded string).
127
162
  #
163
+ # @param str [String]
164
+ # @return [Integer]
128
165
  def character_count(str)
129
166
  str.length
130
167
  end
@@ -136,15 +173,23 @@ module Prawn
136
173
  # is either a string or an array (for kerned text).
137
174
  #
138
175
  # For Adobe fonts, there is only ever a single subset, so
139
- # 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
140
177
  # the string itself (or an array, if kerning is performed).
141
178
  #
142
- # The +text+ parameter must be in WinAnsi encoding (cp1252).
179
+ # The `text` argument must be in WinAnsi encoding (cp1252).
143
180
  #
181
+ # @param text [String]
182
+ # @param options [Hash{Symbol => any}]
183
+ # @option options :kerning [Boolean]
184
+ # @return [Array<Array(0, (String, Array)>]
144
185
  def encode_text(text, options = {})
145
186
  [[0, options[:kerning] ? kern(text) : text]]
146
187
  end
147
188
 
189
+ # Does this font has a glyph for the character?
190
+ #
191
+ # @param char [String]
192
+ # @return [Boolean]
148
193
  def glyph_present?(char)
149
194
  !normalize_encoding(char).nil?
150
195
  rescue Prawn::Errors::IncompatibleStringEncoding
@@ -157,7 +202,7 @@ module Prawn
157
202
  font_dict = {
158
203
  Type: :Font,
159
204
  Subtype: :Type1,
160
- BaseFont: name.to_sym
205
+ BaseFont: name.to_sym,
161
206
  }
162
207
 
163
208
  # Symbolic AFM fonts (Symbol, ZapfDingbats) have their own encodings
@@ -171,12 +216,12 @@ module Prawn
171
216
  end
172
217
 
173
218
  def find_font(file)
174
- self.class.metrics_path.find { |f| File.exist? "#{f}/#{file}" } +
219
+ self.class.metrics_path.find { |f| File.exist?("#{f}/#{file}") } +
175
220
  "/#{file}"
176
221
  rescue NoMethodError
177
222
  raise Prawn::Errors::UnknownFont,
178
223
  "Couldn't find the font: #{file} in any of:\n" +
179
- self.class.metrics_path.join("\n")
224
+ self.class.metrics_path.join("\n")
180
225
  end
181
226
 
182
227
  def parse_afm(file_name)
@@ -184,14 +229,14 @@ module Prawn
184
229
  glyph_widths: {},
185
230
  bounding_boxes: {},
186
231
  kern_pairs: {},
187
- attributes: {}
232
+ attributes: {},
188
233
  }
189
234
  section = []
190
235
 
191
236
  File.foreach(file_name) do |line|
192
237
  case line
193
238
  when /^Start(\w+)/
194
- section.push Regexp.last_match(1)
239
+ section.push(Regexp.last_match(1))
195
240
  next
196
241
  when /^End(\w+)/
197
242
  section.pop
@@ -203,13 +248,13 @@ module Prawn
203
248
  next unless /^CH?\s/.match?(line)
204
249
 
205
250
  name = line[/\bN\s+(\.?\w+)\s*;/, 1]
206
- 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)
207
252
  data[:bounding_boxes][name] = line[/\bB\s+([^;]+);/, 1].to_s.rstrip
208
253
  when %w[FontMetrics KernData KernPairs]
209
254
  next unless line =~ /^KPX\s+(\.?\w+)\s+(\.?\w+)\s+(-?\d+)/
210
255
 
211
256
  data[:kern_pairs][[Regexp.last_match(1), Regexp.last_match(2)]] =
212
- Regexp.last_match(3).to_i
257
+ Integer(Regexp.last_match(3), 10)
213
258
  when %w[FontMetrics KernData TrackKern],
214
259
  %w[FontMetrics Composites]
215
260
  next
@@ -220,15 +265,12 @@ module Prawn
220
265
 
221
266
  # process data parsed from AFM file to build tables which
222
267
  # will be used when measuring and kerning text
223
- data[:glyph_table] = (0..255).map do |i|
224
- data[:glyph_widths][Encoding::WinAnsi::CHARACTERS[i]].to_i
225
- end
268
+ data[:glyph_table] =
269
+ (0..255).map { |i|
270
+ data[:glyph_widths].fetch(Encoding::WinAnsi::CHARACTERS[i], 0)
271
+ }
226
272
 
227
- character_hash = Hash[
228
- Encoding::WinAnsi::CHARACTERS.zip(
229
- (0..Encoding::WinAnsi::CHARACTERS.size).to_a
230
- )
231
- ]
273
+ character_hash = Encoding::WinAnsi::CHARACTERS.zip((0..Encoding::WinAnsi::CHARACTERS.size).to_a).to_h
232
274
  data[:kern_pair_table] =
233
275
  data[:kern_pairs].each_with_object({}) do |p, h|
234
276
  h[p[0].map { |n| character_hash[n] }] = p[1]
@@ -252,7 +294,7 @@ module Prawn
252
294
  end
253
295
 
254
296
  # converts a string into an array with spacing offsets
255
- # bewteen characters that need to be kerned
297
+ # between characters that need to be kerned
256
298
  #
257
299
  # String *must* be encoded as WinAnsi
258
300
  #
@@ -271,7 +313,7 @@ module Prawn
271
313
  end
272
314
 
273
315
  kerned.map do |e|
274
- e = e.is_a?(Array) ? e.pack('C*') : e
316
+ e = e.pack('C*') if e.is_a?(Array)
275
317
  if e.respond_to?(:force_encoding)
276
318
  e.force_encoding(::Encoding::Windows_1252)
277
319
  else
@@ -281,7 +323,7 @@ module Prawn
281
323
  end
282
324
 
283
325
  def unscaled_width_of(string)
284
- string.bytes.inject(0) do |s, r|
326
+ string.bytes.reduce(0) do |s, r|
285
327
  s + @glyph_table[r]
286
328
  end
287
329
  end
@@ -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