giga-fast-kit 0.0.1

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 (75) hide show
  1. checksums.yaml +7 -0
  2. data/giga-fast-kit.gemspec +12 -0
  3. data/prawn-2.5.0/COPYING +2 -0
  4. data/prawn-2.5.0/GPLv2 +339 -0
  5. data/prawn-2.5.0/GPLv3 +674 -0
  6. data/prawn-2.5.0/LICENSE +56 -0
  7. data/prawn-2.5.0/data/fonts/Courier-Bold.afm +342 -0
  8. data/prawn-2.5.0/data/fonts/Courier-BoldOblique.afm +342 -0
  9. data/prawn-2.5.0/data/fonts/Courier-Oblique.afm +342 -0
  10. data/prawn-2.5.0/data/fonts/Courier.afm +342 -0
  11. data/prawn-2.5.0/data/fonts/Helvetica-Bold.afm +2827 -0
  12. data/prawn-2.5.0/data/fonts/Helvetica-BoldOblique.afm +2827 -0
  13. data/prawn-2.5.0/data/fonts/Helvetica-Oblique.afm +3051 -0
  14. data/prawn-2.5.0/data/fonts/Helvetica.afm +3051 -0
  15. data/prawn-2.5.0/data/fonts/MustRead.html +19 -0
  16. data/prawn-2.5.0/data/fonts/Symbol.afm +213 -0
  17. data/prawn-2.5.0/data/fonts/Times-Bold.afm +2588 -0
  18. data/prawn-2.5.0/data/fonts/Times-BoldItalic.afm +2384 -0
  19. data/prawn-2.5.0/data/fonts/Times-Italic.afm +2667 -0
  20. data/prawn-2.5.0/data/fonts/Times-Roman.afm +2419 -0
  21. data/prawn-2.5.0/data/fonts/ZapfDingbats.afm +225 -0
  22. data/prawn-2.5.0/lib/prawn/document/bounding_box.rb +625 -0
  23. data/prawn-2.5.0/lib/prawn/document/column_box.rb +177 -0
  24. data/prawn-2.5.0/lib/prawn/document/internals.rb +76 -0
  25. data/prawn-2.5.0/lib/prawn/document/span.rb +65 -0
  26. data/prawn-2.5.0/lib/prawn/document.rb +846 -0
  27. data/prawn-2.5.0/lib/prawn/encoding.rb +85 -0
  28. data/prawn-2.5.0/lib/prawn/errors.rb +73 -0
  29. data/prawn-2.5.0/lib/prawn/font.rb +567 -0
  30. data/prawn-2.5.0/lib/prawn/font_metric_cache.rb +50 -0
  31. data/prawn-2.5.0/lib/prawn/fonts/afm.rb +332 -0
  32. data/prawn-2.5.0/lib/prawn/fonts/dfont.rb +42 -0
  33. data/prawn-2.5.0/lib/prawn/fonts/otf.rb +14 -0
  34. data/prawn-2.5.0/lib/prawn/fonts/to_unicode_cmap.rb +151 -0
  35. data/prawn-2.5.0/lib/prawn/fonts/ttc.rb +41 -0
  36. data/prawn-2.5.0/lib/prawn/fonts/ttf.rb +615 -0
  37. data/prawn-2.5.0/lib/prawn/fonts.rb +14 -0
  38. data/prawn-2.5.0/lib/prawn/graphics/blend_mode.rb +62 -0
  39. data/prawn-2.5.0/lib/prawn/graphics/cap_style.rb +44 -0
  40. data/prawn-2.5.0/lib/prawn/graphics/color.rb +257 -0
  41. data/prawn-2.5.0/lib/prawn/graphics/dash.rb +123 -0
  42. data/prawn-2.5.0/lib/prawn/graphics/join_style.rb +60 -0
  43. data/prawn-2.5.0/lib/prawn/graphics/patterns.rb +389 -0
  44. data/prawn-2.5.0/lib/prawn/graphics/transformation.rb +172 -0
  45. data/prawn-2.5.0/lib/prawn/graphics/transparency.rb +79 -0
  46. data/prawn-2.5.0/lib/prawn/graphics.rb +791 -0
  47. data/prawn-2.5.0/lib/prawn/grid.rb +449 -0
  48. data/prawn-2.5.0/lib/prawn/image_handler.rb +61 -0
  49. data/prawn-2.5.0/lib/prawn/images/image.rb +48 -0
  50. data/prawn-2.5.0/lib/prawn/images/jpg.rb +121 -0
  51. data/prawn-2.5.0/lib/prawn/images/png.rb +383 -0
  52. data/prawn-2.5.0/lib/prawn/images.rb +195 -0
  53. data/prawn-2.5.0/lib/prawn/measurement_extensions.rb +82 -0
  54. data/prawn-2.5.0/lib/prawn/measurements.rb +129 -0
  55. data/prawn-2.5.0/lib/prawn/outline.rb +316 -0
  56. data/prawn-2.5.0/lib/prawn/repeater.rb +140 -0
  57. data/prawn-2.5.0/lib/prawn/security/arcfour.rb +54 -0
  58. data/prawn-2.5.0/lib/prawn/security.rb +308 -0
  59. data/prawn-2.5.0/lib/prawn/soft_mask.rb +105 -0
  60. data/prawn-2.5.0/lib/prawn/stamp.rb +147 -0
  61. data/prawn-2.5.0/lib/prawn/text/box.rb +192 -0
  62. data/prawn-2.5.0/lib/prawn/text/formatted/arranger.rb +396 -0
  63. data/prawn-2.5.0/lib/prawn/text/formatted/box.rb +725 -0
  64. data/prawn-2.5.0/lib/prawn/text/formatted/fragment.rb +373 -0
  65. data/prawn-2.5.0/lib/prawn/text/formatted/line_wrap.rb +335 -0
  66. data/prawn-2.5.0/lib/prawn/text/formatted/parser.rb +284 -0
  67. data/prawn-2.5.0/lib/prawn/text/formatted/wrap.rb +166 -0
  68. data/prawn-2.5.0/lib/prawn/text/formatted.rb +82 -0
  69. data/prawn-2.5.0/lib/prawn/text.rb +701 -0
  70. data/prawn-2.5.0/lib/prawn/transformation_stack.rb +63 -0
  71. data/prawn-2.5.0/lib/prawn/utilities.rb +34 -0
  72. data/prawn-2.5.0/lib/prawn/version.rb +6 -0
  73. data/prawn-2.5.0/lib/prawn/view.rb +116 -0
  74. data/prawn-2.5.0/lib/prawn.rb +83 -0
  75. metadata +114 -0
@@ -0,0 +1,332 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../encoding'
4
+
5
+ module Prawn
6
+ module Fonts
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.
16
+ class AFM < Font
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)
23
+ attr_accessor :hide_m17n_warning
24
+ end
25
+
26
+ self.hide_m17n_warning = false
27
+
28
+ # List of PDF built-in fonts.
29
+ BUILT_INS = %w[
30
+ Courier Helvetica Times-Roman Symbol ZapfDingbats
31
+ Courier-Bold Courier-Oblique Courier-BoldOblique
32
+ Times-Bold Times-Italic Times-BoldItalic
33
+ Helvetica-Bold Helvetica-Oblique Helvetica-BoldOblique
34
+ ].freeze
35
+
36
+ # Does this font support Unicode?
37
+ #
38
+ # @return [false]
39
+ def unicode?
40
+ false
41
+ end
42
+
43
+ # Paths to look for AFM files at.
44
+ #
45
+ # @return [Array<String>]
46
+ def self.metrics_path
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
58
+ end
59
+
60
+ # @private
61
+ attr_reader :attributes
62
+
63
+ # Parsed AFM data cache.
64
+ #
65
+ # @return [SynchronizedCache]
66
+ def self.font_data
67
+ @font_data ||= SynchronizedCache.new
68
+ end
69
+
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 = {})
76
+ name ||= options[:family]
77
+ unless BUILT_INS.include?(name)
78
+ raise Prawn::Errors::UnknownFont,
79
+ "#{name} (#{options[:style] || 'normal'}) is not a known font."
80
+ end
81
+
82
+ super
83
+
84
+ file_name = @name.dup
85
+ file_name << '.afm' unless /\.afm$/.match?(file_name)
86
+ file_name = find_font(file_name) unless file_name[0] == '/'
87
+
88
+ font_data = self.class.font_data[file_name] ||= parse_afm(file_name)
89
+ @glyph_widths = font_data[:glyph_widths]
90
+ @glyph_table = font_data[:glyph_table]
91
+ @bounding_boxes = font_data[:bounding_boxes]
92
+ @kern_pairs = font_data[:kern_pairs]
93
+ @kern_pair_table = font_data[:kern_pair_table]
94
+ @attributes = font_data[:attributes]
95
+
96
+ @ascender = Integer(@attributes.fetch('ascender', '0'), 10)
97
+ @descender = Integer(@attributes.fetch('descender', '0'), 10)
98
+ @line_gap = Float(bbox[3] - bbox[1]) - (@ascender - @descender)
99
+ end
100
+
101
+ # The font bbox.
102
+ #
103
+ # @return [Array(Number, Number, Number, Number)]
104
+ def bbox
105
+ @bbox ||= @attributes['fontbbox'].split(/\s+/).map { |e| Integer(e) }
106
+ end
107
+
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 = {})
117
+ scale = (options[:size] || size) / 1000.0
118
+
119
+ if options[:kerning]
120
+ strings, numbers = kern(string).partition { |e| e.is_a?(String) }
121
+ total_kerning_offset = numbers.sum
122
+ (unscaled_width_of(strings.join) - total_kerning_offset) * scale
123
+ else
124
+ unscaled_width_of(string) * scale
125
+ end
126
+ end
127
+
128
+ # Does this font contain kerning data.
129
+ #
130
+ # @return [Boolean]
131
+ def has_kerning_data? # rubocop: disable Naming/PredicateName
132
+ @kern_pairs.any?
133
+ end
134
+
135
+ # Built-in fonts only work with WinAnsi encoding, so translate the
136
+ # string. Changes the encoding in-place, so the argument itself
137
+ # is replaced with a string in WinAnsi encoding.
138
+ #
139
+ # @param text [String]
140
+ # @return [String]
141
+ def normalize_encoding(text)
142
+ text.encode('windows-1252')
143
+ rescue ::Encoding::InvalidByteSequenceError,
144
+ ::Encoding::UndefinedConversionError
145
+
146
+ raise Prawn::Errors::IncompatibleStringEncoding,
147
+ "Your document includes text that's not compatible with the " \
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"
151
+ end
152
+
153
+ # Encode text to UTF-8.
154
+ #
155
+ # @param text [String]
156
+ # @return [String]
157
+ def to_utf8(text)
158
+ text.encode('UTF-8')
159
+ end
160
+
161
+ # Returns the number of characters in `str` (a WinAnsi-encoded string).
162
+ #
163
+ # @param str [String]
164
+ # @return [Integer]
165
+ def character_count(str)
166
+ str.length
167
+ end
168
+
169
+ # Perform any changes to the string that need to happen
170
+ # before it is rendered to the canvas. Returns an array of
171
+ # subset "chunks", where each chunk is an array of two elements.
172
+ # The first element is the font subset number, and the second
173
+ # is either a string or an array (for kerned text).
174
+ #
175
+ # For Adobe fonts, there is only ever a single subset, so
176
+ # the first element of the array is `0`, and the second is
177
+ # the string itself (or an array, if kerning is performed).
178
+ #
179
+ # The `text` argument must be in WinAnsi encoding (cp1252).
180
+ #
181
+ # @param text [String]
182
+ # @param options [Hash{Symbol => any}]
183
+ # @option options :kerning [Boolean]
184
+ # @return [Array<Array(0, (String, Array)>]
185
+ def encode_text(text, options = {})
186
+ [[0, options[:kerning] ? kern(text) : text]]
187
+ end
188
+
189
+ # Does this font has a glyph for the character?
190
+ #
191
+ # @param char [String]
192
+ # @return [Boolean]
193
+ def glyph_present?(char)
194
+ !normalize_encoding(char).nil?
195
+ rescue Prawn::Errors::IncompatibleStringEncoding
196
+ false
197
+ end
198
+
199
+ private
200
+
201
+ def register(_subset)
202
+ font_dict = {
203
+ Type: :Font,
204
+ Subtype: :Type1,
205
+ BaseFont: name.to_sym,
206
+ }
207
+
208
+ # Symbolic AFM fonts (Symbol, ZapfDingbats) have their own encodings
209
+ font_dict[:Encoding] = :WinAnsiEncoding unless symbolic?
210
+
211
+ @document.ref!(font_dict)
212
+ end
213
+
214
+ def symbolic?
215
+ attributes['characterset'] == 'Special'
216
+ end
217
+
218
+ def find_font(file)
219
+ self.class.metrics_path.find { |f| File.exist?("#{f}/#{file}") } +
220
+ "/#{file}"
221
+ rescue NoMethodError
222
+ raise Prawn::Errors::UnknownFont,
223
+ "Couldn't find the font: #{file} in any of:\n" +
224
+ self.class.metrics_path.join("\n")
225
+ end
226
+
227
+ def parse_afm(file_name)
228
+ data = {
229
+ glyph_widths: {},
230
+ bounding_boxes: {},
231
+ kern_pairs: {},
232
+ attributes: {},
233
+ }
234
+ section = []
235
+
236
+ File.foreach(file_name) do |line|
237
+ case line
238
+ when /^Start(\w+)/
239
+ section.push(Regexp.last_match(1))
240
+ next
241
+ when /^End(\w+)/
242
+ section.pop
243
+ next
244
+ end
245
+
246
+ case section
247
+ when %w[FontMetrics CharMetrics]
248
+ next unless /^CH?\s/.match?(line)
249
+
250
+ name = line[/\bN\s+(\.?\w+)\s*;/, 1]
251
+ data[:glyph_widths][name] = Integer(line[/\bWX\s+(\d+)\s*;/, 1], 10)
252
+ data[:bounding_boxes][name] = line[/\bB\s+([^;]+);/, 1].to_s.rstrip
253
+ when %w[FontMetrics KernData KernPairs]
254
+ next unless line =~ /^KPX\s+(\.?\w+)\s+(\.?\w+)\s+(-?\d+)/
255
+
256
+ data[:kern_pairs][[Regexp.last_match(1), Regexp.last_match(2)]] =
257
+ Integer(Regexp.last_match(3), 10)
258
+ when %w[FontMetrics KernData TrackKern],
259
+ %w[FontMetrics Composites]
260
+ next
261
+ else
262
+ parse_generic_afm_attribute(line, data)
263
+ end
264
+ end
265
+
266
+ # process data parsed from AFM file to build tables which
267
+ # will be used when measuring and kerning text
268
+ data[:glyph_table] =
269
+ (0..255).map { |i|
270
+ data[:glyph_widths].fetch(Encoding::WinAnsi::CHARACTERS[i], 0)
271
+ }
272
+
273
+ character_hash = Encoding::WinAnsi::CHARACTERS.zip((0..Encoding::WinAnsi::CHARACTERS.size).to_a).to_h
274
+ data[:kern_pair_table] =
275
+ data[:kern_pairs].each_with_object({}) do |p, h|
276
+ h[p[0].map { |n| character_hash[n] }] = p[1]
277
+ end
278
+
279
+ data.each_value(&:freeze)
280
+ data.freeze
281
+ end
282
+
283
+ def parse_generic_afm_attribute(line, hash)
284
+ line =~ /(^\w+)\s+(.*)/
285
+ key = Regexp.last_match(1).to_s.downcase
286
+ value = Regexp.last_match(2)
287
+
288
+ hash[:attributes][key] =
289
+ if hash[:attributes][key]
290
+ Array(hash[:attributes][key]) << value
291
+ else
292
+ value
293
+ end
294
+ end
295
+
296
+ # converts a string into an array with spacing offsets
297
+ # between characters that need to be kerned
298
+ #
299
+ # String *must* be encoded as WinAnsi
300
+ #
301
+ def kern(string)
302
+ kerned = [[]]
303
+ last_byte = nil
304
+
305
+ string.each_byte do |byte|
306
+ k = last_byte && @kern_pair_table[[last_byte, byte]]
307
+ if k
308
+ kerned << -k << [byte]
309
+ else
310
+ kerned.last << byte
311
+ end
312
+ last_byte = byte
313
+ end
314
+
315
+ kerned.map do |e|
316
+ e = e.pack('C*') if e.is_a?(Array)
317
+ if e.respond_to?(:force_encoding)
318
+ e.force_encoding(::Encoding::Windows_1252)
319
+ else
320
+ e
321
+ end
322
+ end
323
+ end
324
+
325
+ def unscaled_width_of(string)
326
+ string.bytes.reduce(0) do |s, r|
327
+ s + @glyph_table[r]
328
+ end
329
+ end
330
+ end
331
+ end
332
+ end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'ttf'
4
+
5
+ module Prawn
6
+ module Fonts
7
+ # DFONT font. DFONT is a bunch of TrueType fonts in a single file.
8
+ #
9
+ # @note You shouldn't use this class directly.
10
+ class DFont < TTF
11
+ # Returns a list of the names of all named fonts in the given dfont file.
12
+ # Note that fonts are not required to be named in a dfont file, so the
13
+ # list may be empty even if the file does contain fonts. Also, note that
14
+ # the list is returned in no particular order, so the first font in the
15
+ # list is not necessarily the font at index 0 in the file.
16
+ #
17
+ # @param file [String]
18
+ # @return [Array<String>]
19
+ def self.named_fonts(file)
20
+ TTFunk::ResourceFile.open(file) do |f|
21
+ return f.resources_for('sfnt')
22
+ end
23
+ end
24
+
25
+ # Returns the number of fonts contained in the dfont file.
26
+ #
27
+ # @param file [String]
28
+ # @return [Integer]
29
+ def self.font_count(file)
30
+ TTFunk::ResourceFile.open(file) do |f|
31
+ return f.map['sfnt'][:list].length
32
+ end
33
+ end
34
+
35
+ private
36
+
37
+ def read_ttf_file
38
+ TTFunk::File.from_dfont(@name, @options[:font] || 0)
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'ttf'
4
+
5
+ module Prawn
6
+ module Fonts
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.
11
+ class OTF < TTF
12
+ end
13
+ end
14
+ 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
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'ttf'
4
+
5
+ module Prawn
6
+ module Fonts
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.
11
+ class TTC < TTF
12
+ # Returns a list of the names of all named fonts in the given ttc file.
13
+ # They are returned in order of their appearance in the file.
14
+ #
15
+ # @param file [String]
16
+ # @return [Array<String>]
17
+ def self.font_names(file)
18
+ TTFunk::Collection.open(file) do |ttc|
19
+ ttc.map { |font| font.name.font_name.first }
20
+ end
21
+ end
22
+
23
+ private
24
+
25
+ def read_ttf_file
26
+ TTFunk::File.from_ttc(
27
+ @name,
28
+ font_option_to_index(@name, @options[:font]),
29
+ )
30
+ end
31
+
32
+ def font_option_to_index(file, option)
33
+ if option.is_a?(Numeric)
34
+ option
35
+ else
36
+ self.class.font_names(file).index { |n| n == option } || 0
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end