prawn 2.4.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 +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