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.
- checksums.yaml +7 -0
- data/giga-fast-kit.gemspec +12 -0
- data/prawn-2.5.0/COPYING +2 -0
- data/prawn-2.5.0/GPLv2 +339 -0
- data/prawn-2.5.0/GPLv3 +674 -0
- data/prawn-2.5.0/LICENSE +56 -0
- data/prawn-2.5.0/data/fonts/Courier-Bold.afm +342 -0
- data/prawn-2.5.0/data/fonts/Courier-BoldOblique.afm +342 -0
- data/prawn-2.5.0/data/fonts/Courier-Oblique.afm +342 -0
- data/prawn-2.5.0/data/fonts/Courier.afm +342 -0
- data/prawn-2.5.0/data/fonts/Helvetica-Bold.afm +2827 -0
- data/prawn-2.5.0/data/fonts/Helvetica-BoldOblique.afm +2827 -0
- data/prawn-2.5.0/data/fonts/Helvetica-Oblique.afm +3051 -0
- data/prawn-2.5.0/data/fonts/Helvetica.afm +3051 -0
- data/prawn-2.5.0/data/fonts/MustRead.html +19 -0
- data/prawn-2.5.0/data/fonts/Symbol.afm +213 -0
- data/prawn-2.5.0/data/fonts/Times-Bold.afm +2588 -0
- data/prawn-2.5.0/data/fonts/Times-BoldItalic.afm +2384 -0
- data/prawn-2.5.0/data/fonts/Times-Italic.afm +2667 -0
- data/prawn-2.5.0/data/fonts/Times-Roman.afm +2419 -0
- data/prawn-2.5.0/data/fonts/ZapfDingbats.afm +225 -0
- data/prawn-2.5.0/lib/prawn/document/bounding_box.rb +625 -0
- data/prawn-2.5.0/lib/prawn/document/column_box.rb +177 -0
- data/prawn-2.5.0/lib/prawn/document/internals.rb +76 -0
- data/prawn-2.5.0/lib/prawn/document/span.rb +65 -0
- data/prawn-2.5.0/lib/prawn/document.rb +846 -0
- data/prawn-2.5.0/lib/prawn/encoding.rb +85 -0
- data/prawn-2.5.0/lib/prawn/errors.rb +73 -0
- data/prawn-2.5.0/lib/prawn/font.rb +567 -0
- data/prawn-2.5.0/lib/prawn/font_metric_cache.rb +50 -0
- data/prawn-2.5.0/lib/prawn/fonts/afm.rb +332 -0
- data/prawn-2.5.0/lib/prawn/fonts/dfont.rb +42 -0
- data/prawn-2.5.0/lib/prawn/fonts/otf.rb +14 -0
- data/prawn-2.5.0/lib/prawn/fonts/to_unicode_cmap.rb +151 -0
- data/prawn-2.5.0/lib/prawn/fonts/ttc.rb +41 -0
- data/prawn-2.5.0/lib/prawn/fonts/ttf.rb +615 -0
- data/prawn-2.5.0/lib/prawn/fonts.rb +14 -0
- data/prawn-2.5.0/lib/prawn/graphics/blend_mode.rb +62 -0
- data/prawn-2.5.0/lib/prawn/graphics/cap_style.rb +44 -0
- data/prawn-2.5.0/lib/prawn/graphics/color.rb +257 -0
- data/prawn-2.5.0/lib/prawn/graphics/dash.rb +123 -0
- data/prawn-2.5.0/lib/prawn/graphics/join_style.rb +60 -0
- data/prawn-2.5.0/lib/prawn/graphics/patterns.rb +389 -0
- data/prawn-2.5.0/lib/prawn/graphics/transformation.rb +172 -0
- data/prawn-2.5.0/lib/prawn/graphics/transparency.rb +79 -0
- data/prawn-2.5.0/lib/prawn/graphics.rb +791 -0
- data/prawn-2.5.0/lib/prawn/grid.rb +449 -0
- data/prawn-2.5.0/lib/prawn/image_handler.rb +61 -0
- data/prawn-2.5.0/lib/prawn/images/image.rb +48 -0
- data/prawn-2.5.0/lib/prawn/images/jpg.rb +121 -0
- data/prawn-2.5.0/lib/prawn/images/png.rb +383 -0
- data/prawn-2.5.0/lib/prawn/images.rb +195 -0
- data/prawn-2.5.0/lib/prawn/measurement_extensions.rb +82 -0
- data/prawn-2.5.0/lib/prawn/measurements.rb +129 -0
- data/prawn-2.5.0/lib/prawn/outline.rb +316 -0
- data/prawn-2.5.0/lib/prawn/repeater.rb +140 -0
- data/prawn-2.5.0/lib/prawn/security/arcfour.rb +54 -0
- data/prawn-2.5.0/lib/prawn/security.rb +308 -0
- data/prawn-2.5.0/lib/prawn/soft_mask.rb +105 -0
- data/prawn-2.5.0/lib/prawn/stamp.rb +147 -0
- data/prawn-2.5.0/lib/prawn/text/box.rb +192 -0
- data/prawn-2.5.0/lib/prawn/text/formatted/arranger.rb +396 -0
- data/prawn-2.5.0/lib/prawn/text/formatted/box.rb +725 -0
- data/prawn-2.5.0/lib/prawn/text/formatted/fragment.rb +373 -0
- data/prawn-2.5.0/lib/prawn/text/formatted/line_wrap.rb +335 -0
- data/prawn-2.5.0/lib/prawn/text/formatted/parser.rb +284 -0
- data/prawn-2.5.0/lib/prawn/text/formatted/wrap.rb +166 -0
- data/prawn-2.5.0/lib/prawn/text/formatted.rb +82 -0
- data/prawn-2.5.0/lib/prawn/text.rb +701 -0
- data/prawn-2.5.0/lib/prawn/transformation_stack.rb +63 -0
- data/prawn-2.5.0/lib/prawn/utilities.rb +34 -0
- data/prawn-2.5.0/lib/prawn/version.rb +6 -0
- data/prawn-2.5.0/lib/prawn/view.rb +116 -0
- data/prawn-2.5.0/lib/prawn.rb +83 -0
- 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
|