skrift 0.1.0 → 0.4.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.
- checksums.yaml +4 -4
- data/Rakefile +5 -0
- data/examples/banner.rb +96 -0
- data/examples/banner_boxdrawing.rb +139 -0
- data/examples/banner_shadow.rb +207 -0
- data/{test.rb → examples/glyph_dump.rb} +2 -2
- data/lib/skrift/font.rb +381 -293
- data/lib/skrift/font_set.rb +58 -0
- data/lib/skrift/glyph_cache.rb +142 -0
- data/lib/skrift/outline.rb +42 -45
- data/lib/skrift/raster.rb +74 -65
- data/lib/skrift/sft.rb +84 -70
- data/lib/skrift/version.rb +1 -1
- data/lib/skrift.rb +45 -11
- metadata +11 -7
- data/skrift.gemspec +0 -39
data/lib/skrift/font.rb
CHANGED
|
@@ -1,345 +1,433 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
Font.new(memory)
|
|
20
|
-
end
|
|
1
|
+
module Skrift
|
|
2
|
+
class Font
|
|
3
|
+
include Geometry
|
|
4
|
+
|
|
5
|
+
# TrueType, TrueType, OpenType
|
|
6
|
+
FILE_MAGIC = ["\0\1\0\0", "true", "OTTO"]
|
|
7
|
+
|
|
8
|
+
attr_reader :memory, :units_per_em
|
|
9
|
+
|
|
10
|
+
def initialize(memory)
|
|
11
|
+
@memory = memory
|
|
12
|
+
raise "Unsupported format (magic value: #{at(0,4).inspect})" if !FILE_MAGIC.member?(at(0,4))
|
|
13
|
+
head = reqtable("head")
|
|
14
|
+
@units_per_em = getu16(head + 18)
|
|
15
|
+
@loca_format = geti16(head + 50)
|
|
16
|
+
hhea = reqtable("hhea")
|
|
17
|
+
@num_long_hmtx = getu16(hhea + 34)
|
|
18
|
+
end
|
|
21
19
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
20
|
+
def Font.load(filename) # loadfile, 103
|
|
21
|
+
memory = File.read(filename).force_encoding("ASCII-8BIT")
|
|
22
|
+
Font.new(memory)
|
|
23
|
+
end
|
|
26
24
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
def getu32(offset); at(offset,4).unpack1("N"); end
|
|
25
|
+
def at(offset, len=1)
|
|
26
|
+
raise "Out of bounds #{offset} / len #{len} (max: #{@memory.size})" if offset.to_i + len.to_i > @memory.size
|
|
27
|
+
@memory[offset..(offset+len-1)]
|
|
28
|
+
end
|
|
32
29
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
30
|
+
def getu8(offset); at(offset).ord; end
|
|
31
|
+
def geti8(offset); at(offset).unpack1("c"); end
|
|
32
|
+
def getu16(offset); at(offset,2).unpack1("S>"); end
|
|
33
|
+
def geti16(offset); at(offset,2).unpack1("s>"); end
|
|
34
|
+
def getu32(offset); at(offset,4).unpack1("N"); end
|
|
35
|
+
def getu24(offset); (getu16(offset) << 8) | getu8(offset + 2); end
|
|
36
|
+
|
|
37
|
+
def tables
|
|
38
|
+
@tables ||= Hash[*
|
|
39
|
+
getu16(4).times.map {|t| [at(t*16 + 12,4),getu32(t*16 + 20)] }.flatten
|
|
40
|
+
]
|
|
41
|
+
end
|
|
38
42
|
|
|
39
|
-
|
|
40
|
-
def gettable(tag); tables[tag]; end
|
|
43
|
+
def reqtable(tag) = (tables[tag] or raise "Unable to get table '#{tag}'")
|
|
41
44
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
45
|
+
def glyph_bbox(outline)
|
|
46
|
+
box = at(outline+2, 8).unpack("s>*")
|
|
47
|
+
raise "Broken bbox #{box.inspect}" if box[2] < box[0] || box[3] < box[1]
|
|
48
|
+
return box
|
|
49
|
+
end
|
|
47
50
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
51
|
+
# Returns the offset into the font that the glyph's outline is stored at
|
|
52
|
+
def outline_offset(glyph) # 806
|
|
53
|
+
loca = reqtable("loca")
|
|
54
|
+
glyf = reqtable("glyf")
|
|
55
|
+
if @loca_format == 0
|
|
56
|
+
base = loca + 2 * glyph
|
|
57
|
+
this = 2 * getu16(base)
|
|
58
|
+
next_ = 2 * getu16(base + 2)
|
|
59
|
+
else
|
|
60
|
+
this, next_ = at(loca + 4 * glyph,8).unpack("NN")
|
|
61
|
+
end
|
|
62
|
+
return this == next_ ? nil : glyf + this
|
|
58
63
|
end
|
|
59
|
-
return this == next_ ? nil : glyf + this
|
|
60
|
-
end
|
|
61
64
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
65
|
+
# cmap subtable selector, packed as platformID * 64 + encodingID.
|
|
66
|
+
CMAP_UNICODE_BMP = 0 * 64 + 3 # Unicode platform, BMP only
|
|
67
|
+
CMAP_UNICODE_FULL = 0 * 64 + 4 # Unicode platform, full repertoire
|
|
68
|
+
CMAP_WIN_BMP = 3 * 64 + 1 # Windows platform, Unicode BMP
|
|
69
|
+
CMAP_WIN_UCS4 = 3 * 64 + 10 # Windows platform, Unicode UCS-4
|
|
70
|
+
|
|
71
|
+
# Unicode variation selectors. VS15 requests text, VS16 emoji presentation;
|
|
72
|
+
# a sequence <base, selector> can map to a different glyph via cmap format
|
|
73
|
+
# 14 (see #variation_glyph_id).
|
|
74
|
+
VS_TEXT = 0xFE0E # VS15 — text presentation
|
|
75
|
+
VS_EMOJI = 0xFE0F # VS16 — emoji presentation
|
|
76
|
+
|
|
77
|
+
# True if +cp+ is a Unicode variation selector (the VS1–VS16 block or the
|
|
78
|
+
# supplementary VS17–VS256 block).
|
|
79
|
+
def self.variation_selector?(cp)
|
|
80
|
+
(0xFE00..0xFE0F).cover?(cp) || (0xE0100..0xE01EF).cover?(cp)
|
|
70
81
|
end
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
82
|
+
|
|
83
|
+
def each_cmap_entry
|
|
84
|
+
cmap = reqtable("cmap")
|
|
85
|
+
getu16(cmap + 2).times do |idx|
|
|
86
|
+
entry = cmap + 4 + idx * 8
|
|
87
|
+
type = getu16(entry) * 64 + getu16(entry + 2)
|
|
88
|
+
table = cmap + getu32(entry + 4)
|
|
89
|
+
format = getu16(table)
|
|
90
|
+
yield(type, table, format)
|
|
79
91
|
end
|
|
80
92
|
end
|
|
81
93
|
|
|
82
|
-
#
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
94
|
+
# Maps unicode code points to glyph indices
|
|
95
|
+
def glyph_id(char_code)
|
|
96
|
+
each_cmap_entry do |type, table, format|
|
|
97
|
+
if type == CMAP_UNICODE_FULL || type == CMAP_WIN_UCS4
|
|
98
|
+
return cmap_fmt12_13(table, char_code, 12) if format == 12
|
|
99
|
+
return nil
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
# If no full repertoire cmap was found, try looking for a Unicode BMP map
|
|
104
|
+
each_cmap_entry do |type, table, format|
|
|
105
|
+
if type == CMAP_UNICODE_BMP || type == CMAP_WIN_BMP
|
|
106
|
+
return cmap_fmt4(table + 6, char_code) if format == 4
|
|
107
|
+
return cmap_fmt6(table + 6, char_code) if format == 6
|
|
108
|
+
return nil
|
|
109
|
+
end
|
|
88
110
|
end
|
|
111
|
+
return nil
|
|
89
112
|
end
|
|
90
|
-
return nil
|
|
91
|
-
end
|
|
92
113
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
114
|
+
def hor_metrics(glyph)
|
|
115
|
+
hmtx = reqtable("hmtx")
|
|
116
|
+
return nil if hmtx.nil?
|
|
117
|
+
if glyph < @num_long_hmtx # In long metrics segment?
|
|
118
|
+
offset = hmtx + 4 * glyph
|
|
119
|
+
return getu16(offset), geti16(offset + 2)
|
|
120
|
+
end
|
|
121
|
+
# Glyph is inside short metrics segment
|
|
122
|
+
boundary = hmtx + 4 * @num_long_hmtx
|
|
123
|
+
return nil if boundary < 4
|
|
124
|
+
offset = boundary - 4
|
|
125
|
+
advance_width = getu16(offset)
|
|
126
|
+
offset = boundary + 2 * (glyph - @num_long_hmtx)
|
|
127
|
+
return advance_width, geti16(offset)
|
|
99
128
|
end
|
|
100
|
-
# Glyph is inside short metrics segment
|
|
101
|
-
boundary = hmtx + 4 * @num_long_hmtx
|
|
102
|
-
return nil if boundary < 4
|
|
103
|
-
offset = boundary - 4
|
|
104
|
-
advance_width = getu16(offset)
|
|
105
|
-
offset = boundary + 2 * (glyph - @num_long_hmtx)
|
|
106
|
-
return advance_width, geti16(offset)
|
|
107
|
-
end
|
|
108
129
|
|
|
109
130
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
+
def cmap_fmt4(table, char_code) # 572
|
|
132
|
+
# cmap format 4 only supports the Unicode BMP
|
|
133
|
+
return nil if char_code > 0xffff
|
|
134
|
+
seg_count_x2 = getu16(table)
|
|
135
|
+
raise "Error" if (seg_count_x2 & 1) != 0 or seg_count_x2 == 0
|
|
136
|
+
# Find starting positions of the relevant arrays
|
|
137
|
+
end_codes = table + 8
|
|
138
|
+
start_codes = end_codes + seg_count_x2 + 2
|
|
139
|
+
id_deltas = start_codes + seg_count_x2
|
|
140
|
+
id_range_offsets = id_deltas + seg_count_x2
|
|
141
|
+
|
|
142
|
+
@ecodes ||= at(end_codes, seg_count_x2).unpack("n*")
|
|
143
|
+
seg_id_x_x2 = @ecodes.bsearch_index {|i| i >= char_code }.to_i * 2
|
|
144
|
+
|
|
145
|
+
# Look up segment info from the arrays & short circuit if the spec requires
|
|
146
|
+
start_code = getu16(start_codes + seg_id_x_x2)
|
|
147
|
+
return 0 if start_code > char_code
|
|
148
|
+
id_delta = getu16(id_deltas + seg_id_x_x2)
|
|
149
|
+
if (id_range_offset = getu16(id_range_offsets + seg_id_x_x2)) == 0
|
|
150
|
+
# Intentional integer under- and overflow
|
|
151
|
+
return (char_code + id_delta) & 0xffff
|
|
152
|
+
end
|
|
153
|
+
# Calculate offset into glyph array and determine ultimate value
|
|
154
|
+
id = getu16(id_range_offsets + seg_id_x_x2 + id_range_offset + 2 * (char_code - start_code))
|
|
155
|
+
return id ? (id + id_delta) & 0xffff : 0
|
|
131
156
|
end
|
|
132
|
-
# Calculate offset into glyph array and determine ultimate value
|
|
133
|
-
id = getu16(id_range_offsets + seg_id_x_x2 + id_range_offset + 2 * (char_code - start_code))
|
|
134
|
-
return id ? (id + id_delta) & 0xffff : 0
|
|
135
|
-
end
|
|
136
157
|
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
158
|
+
def decode_outline(offset, rec_depth = 0, outl = Outline.new)
|
|
159
|
+
num_contours = geti16(offset)
|
|
160
|
+
return nil if num_contours == 0
|
|
161
|
+
return simple_outline(offset + 10, num_contours, outl) if num_contours > 0
|
|
162
|
+
return compound_outline(offset + 10, rec_depth, outl)
|
|
163
|
+
end
|
|
143
164
|
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
165
|
+
def cmap_fmt6(table, char_code) # 621
|
|
166
|
+
first_code, entry_count = at(table,4).unpack("S>*")
|
|
167
|
+
return nil if !char_code.between?(first_code, 0xffff)
|
|
168
|
+
char_code -= first_code
|
|
169
|
+
return nil if (char_code >= entry_count)
|
|
170
|
+
return getu16(table + 4 + 2 * char_code)
|
|
171
|
+
end
|
|
151
172
|
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
173
|
+
def cmap_fmt12_13(table, char_code, which)
|
|
174
|
+
getu32(table + 12).times do |i|
|
|
175
|
+
first_code, last_code, glyph_offset = at(table + (i*12) + 16, 12).unpack("N*")
|
|
176
|
+
next if char_code < first_code || char_code > last_code
|
|
177
|
+
glyph_offset += char_code-first_code if which == 12
|
|
178
|
+
return glyph_offset
|
|
179
|
+
end
|
|
180
|
+
return nil
|
|
158
181
|
end
|
|
159
|
-
return nil
|
|
160
|
-
end
|
|
161
182
|
|
|
162
|
-
|
|
183
|
+
# Glyph for the variation sequence <+base+, +selector+> (e.g. an emoji base
|
|
184
|
+
# plus VS16), via the cmap format 14 subtable. Returns the mapped glyph id,
|
|
185
|
+
# or nil if the font does not define that sequence — in which case the
|
|
186
|
+
# caller should fall back to #glyph_id(base). A sequence that the font lists
|
|
187
|
+
# as using the *default* presentation resolves to the base glyph.
|
|
188
|
+
def variation_glyph_id(base, selector)
|
|
189
|
+
each_cmap_entry do |_type, table, format|
|
|
190
|
+
next unless format == 14
|
|
191
|
+
r = cmap_fmt14(table, base, selector)
|
|
192
|
+
return r == :default ? glyph_id(base) : r
|
|
193
|
+
end
|
|
194
|
+
nil
|
|
195
|
+
end
|
|
163
196
|
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
197
|
+
# Look up <base, selector> in one format-14 subtable. Returns a glyph id, the
|
|
198
|
+
# symbol :default if the sequence uses the base font's default glyph, or nil
|
|
199
|
+
# if this subtable does not map the sequence. cmap-14 has two per-selector
|
|
200
|
+
# tables: a non-default list mapping a base to a specific glyph, and a
|
|
201
|
+
# default list of base ranges that fall through to the regular glyph.
|
|
202
|
+
def cmap_fmt14(table, base, selector)
|
|
203
|
+
num_records = getu32(table + 6)
|
|
204
|
+
num_records.times do |i|
|
|
205
|
+
rec = table + 10 + i * 11
|
|
206
|
+
next unless getu24(rec) == selector # records sorted by selector
|
|
207
|
+
nondef = getu32(rec + 7)
|
|
208
|
+
if nondef != 0 && (gid = uvs_nondefault(table + nondef, base))
|
|
209
|
+
return gid
|
|
177
210
|
end
|
|
211
|
+
default = getu32(rec + 3)
|
|
212
|
+
return :default if default != 0 && uvs_default?(table + default, base)
|
|
213
|
+
return nil # selector present, base not in it
|
|
178
214
|
end
|
|
179
|
-
|
|
215
|
+
nil
|
|
180
216
|
end
|
|
181
|
-
return off
|
|
182
|
-
end
|
|
183
217
|
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
offset += 1
|
|
196
|
-
bit = flags[i].allbits?(X_CHANGE_IS_POSITIVE * factor) ? 1 : 0
|
|
197
|
-
accum -= (getu8(offset-1) ^ -bit) + bit
|
|
198
|
-
elsif flags[i].nobits?(X_CHANGE_IS_ZERO * factor)
|
|
199
|
-
offset += 2
|
|
200
|
-
accum += geti16(offset-2)
|
|
218
|
+
# Non-default UVS table: numMappings (u32) then sorted 5-byte records of
|
|
219
|
+
# <unicodeValue: u24, glyphID: u16>. Binary search for +base+.
|
|
220
|
+
def uvs_nondefault(t, base)
|
|
221
|
+
lo, hi = 0, getu32(t) - 1
|
|
222
|
+
while lo <= hi
|
|
223
|
+
mid = (lo + hi) / 2
|
|
224
|
+
m = t + 4 + mid * 5
|
|
225
|
+
u = getu24(m)
|
|
226
|
+
if base < u then hi = mid - 1
|
|
227
|
+
elsif base > u then lo = mid + 1
|
|
228
|
+
else return getu16(m + 3)
|
|
201
229
|
end
|
|
202
|
-
accum
|
|
203
230
|
end
|
|
204
|
-
|
|
205
|
-
num_pts.times {|i| points << Vector[accumulate.call(i,1), 0.0] }
|
|
206
|
-
accum = 0.0
|
|
207
|
-
num_pts.times {|i| points[base_point+i][1] = accumulate.call(i,2) }
|
|
231
|
+
nil
|
|
208
232
|
end
|
|
209
|
-
end
|
|
210
233
|
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
beg = end_pts[i] + 1
|
|
234
|
+
# Default UVS table: numRanges (u32) then sorted 4-byte ranges of
|
|
235
|
+
# <startUnicodeValue: u24, additionalCount: u8>. True if +base+ is covered.
|
|
236
|
+
def uvs_default?(t, base)
|
|
237
|
+
lo, hi = 0, getu32(t) - 1
|
|
238
|
+
while lo <= hi
|
|
239
|
+
mid = (lo + hi) / 2
|
|
240
|
+
r = t + 4 + mid * 4
|
|
241
|
+
start = getu24(r)
|
|
242
|
+
if base < start then hi = mid - 1
|
|
243
|
+
elsif base > start + getu8(r + 3) then lo = mid + 1
|
|
244
|
+
else return true
|
|
245
|
+
end
|
|
246
|
+
end
|
|
247
|
+
false
|
|
226
248
|
end
|
|
227
|
-
outl
|
|
228
|
-
end
|
|
229
249
|
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
250
|
+
REPEAT_FLAG = 0x08
|
|
251
|
+
|
|
252
|
+
# For a simple outline, determines each point of the outline with a set of flags
|
|
253
|
+
def simple_flags(off, num_pts, flags)
|
|
254
|
+
value = 0
|
|
255
|
+
repeat = 0
|
|
256
|
+
num_pts.times do |i|
|
|
257
|
+
if repeat > 0
|
|
258
|
+
repeat -= 1
|
|
259
|
+
else
|
|
260
|
+
value = getu8(off)
|
|
261
|
+
off += 1
|
|
262
|
+
if value.allbits?(REPEAT_FLAG)
|
|
263
|
+
repeat = getu8(off)
|
|
264
|
+
off += 1
|
|
265
|
+
end
|
|
266
|
+
end
|
|
267
|
+
flags[i] = value
|
|
268
|
+
end
|
|
269
|
+
return off
|
|
246
270
|
end
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
271
|
+
|
|
272
|
+
X_CHANGE_IS_SMALL = 0x02 # x2 for Y
|
|
273
|
+
X_CHANGE_IS_ZERO = 0x10 # x2 for Y
|
|
274
|
+
X_CHANGE_IS_POSITIVE = 0x10 # x2 for Y
|
|
275
|
+
|
|
276
|
+
def simple_points(offset, num_pts, points, base_point)
|
|
277
|
+
[].tap do |flags|
|
|
278
|
+
offset = simple_flags(offset, num_pts, flags)
|
|
279
|
+
|
|
280
|
+
accum = 0.0
|
|
281
|
+
accumulate = ->(i, factor) do
|
|
282
|
+
if flags[i].allbits?(X_CHANGE_IS_SMALL * factor)
|
|
283
|
+
offset += 1
|
|
284
|
+
bit = flags[i].allbits?(X_CHANGE_IS_POSITIVE * factor) ? 1 : 0
|
|
285
|
+
accum -= (getu8(offset-1) ^ -bit) + bit
|
|
286
|
+
elsif flags[i].nobits?(X_CHANGE_IS_ZERO * factor)
|
|
287
|
+
offset += 2
|
|
288
|
+
accum += geti16(offset-2)
|
|
289
|
+
end
|
|
290
|
+
accum
|
|
261
291
|
end
|
|
262
|
-
|
|
292
|
+
|
|
293
|
+
num_pts.times {|i| points << Raster::Vector.new(accumulate.call(i,1), 0.0) }
|
|
294
|
+
accum = 0.0
|
|
295
|
+
num_pts.times {|i| points[base_point+i][1] = accumulate.call(i,2) }
|
|
263
296
|
end
|
|
264
297
|
end
|
|
265
|
-
outl.segments << Outline::Segment.new(beg, loose_end, ctrl)
|
|
266
|
-
return true
|
|
267
|
-
end
|
|
268
298
|
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
299
|
+
def simple_outline(offset, num_contours, outl = Outline.new)
|
|
300
|
+
base_points = outl.points.length
|
|
301
|
+
num_pts = getu16(offset + (num_contours - 1) *2) + 1
|
|
302
|
+
end_pts = at(offset, num_contours*2).unpack("S>*")
|
|
303
|
+
offset += 2*num_contours
|
|
304
|
+
# Falling end_pts have no sensible interpretation, so treat as error
|
|
305
|
+
end_pts.each_cons(2) { |a, b| raise if b < a + 1 }
|
|
306
|
+
offset += 2 + getu16(offset)
|
|
307
|
+
|
|
308
|
+
flags = simple_points(offset, num_pts, outl.points, base_points)
|
|
309
|
+
|
|
310
|
+
beg = 0
|
|
311
|
+
num_contours.times do |i|
|
|
312
|
+
decode_contour(outl, flags, beg, base_points+beg, end_pts[i] - beg + 1)
|
|
313
|
+
beg = end_pts[i] + 1
|
|
314
|
+
end
|
|
315
|
+
outl
|
|
316
|
+
end
|
|
317
|
+
|
|
318
|
+
POINT_IS_ON_CURVE = 0x01
|
|
319
|
+
|
|
320
|
+
def decode_contour(outl, flags, off, base_point, count)
|
|
321
|
+
return true if count < 2 # Invisible (no area)
|
|
322
|
+
|
|
323
|
+
if flags[off].allbits?(POINT_IS_ON_CURVE)
|
|
324
|
+
loose_end = base_point
|
|
325
|
+
base_point+= 1
|
|
326
|
+
off += 1
|
|
327
|
+
count -= 1
|
|
328
|
+
elsif flags[off + count - 1].allbits?(POINT_IS_ON_CURVE)
|
|
329
|
+
count -= 1
|
|
330
|
+
loose_end = base_point + count
|
|
290
331
|
else
|
|
291
|
-
|
|
292
|
-
|
|
332
|
+
loose_end = outl.points.length
|
|
333
|
+
outl.points << midpoint(outl.points[base_point], outl.points[base_point + count - 1])
|
|
293
334
|
end
|
|
335
|
+
beg = loose_end
|
|
336
|
+
ctrl = nil
|
|
337
|
+
count.times do |i|
|
|
338
|
+
cur = base_point + i
|
|
339
|
+
if flags[off+i].allbits?(POINT_IS_ON_CURVE)
|
|
340
|
+
outl.segments << Outline::Segment.new(beg, cur, ctrl)
|
|
341
|
+
beg = cur
|
|
342
|
+
ctrl = nil
|
|
343
|
+
else
|
|
344
|
+
if ctrl # 2x control points in a row -> insert midpoint
|
|
345
|
+
center = outl.points.length
|
|
346
|
+
outl.points << midpoint(outl.points[ctrl], outl.points[cur])
|
|
347
|
+
outl.segments << Outline::Segment.new(beg, center, ctrl)
|
|
348
|
+
beg = center
|
|
349
|
+
end
|
|
350
|
+
ctrl = cur
|
|
351
|
+
end
|
|
352
|
+
end
|
|
353
|
+
outl.segments << Outline::Segment.new(beg, loose_end, ctrl)
|
|
354
|
+
return true
|
|
355
|
+
end
|
|
294
356
|
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
357
|
+
OFFSETS_ARE_LARGE = 0x001
|
|
358
|
+
ACTUAL_XY_OFFSETS = 0x002
|
|
359
|
+
GOT_A_SINGLE_SCALE = 0x008
|
|
360
|
+
THERE_ARE_MORE_COMPONENTS = 0x020
|
|
361
|
+
GOT_AN_X_AND_Y_SCALE = 0x040
|
|
362
|
+
GOT_A_SCALE_MATRIX = 0x080
|
|
363
|
+
|
|
364
|
+
def compound_outline(offset, rec_depth, outl) # 1057
|
|
365
|
+
# Guard against infinite recursion (compound glyphs that have themselves as component).
|
|
366
|
+
return nil if rec_depth >= 4
|
|
367
|
+
flags = THERE_ARE_MORE_COMPONENTS
|
|
368
|
+
while flags.allbits?(THERE_ARE_MORE_COMPONENTS)
|
|
369
|
+
flags, glyph = at(offset,4).unpack("S>*")
|
|
301
370
|
offset += 4
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
371
|
+
# We don't implement point matching, and neither does stb truetype
|
|
372
|
+
return nil if (flags & ACTUAL_XY_OFFSETS) == 0
|
|
373
|
+
# Read additional X and Y offsets (in FUnits) of this component.
|
|
374
|
+
if (flags & OFFSETS_ARE_LARGE) != 0
|
|
375
|
+
local = [[1.0, 0.0, geti16(offset)], [0.0,1.0, geti16(offset+2)]]
|
|
376
|
+
offset += 4
|
|
377
|
+
else
|
|
378
|
+
local = [[1.0, 0.0, geti8(offset)], [0.0, 1.0, geti8(offset+1)]]
|
|
379
|
+
offset += 2
|
|
380
|
+
end
|
|
381
|
+
|
|
382
|
+
if flags.allbits?(GOT_A_SINGLE_SCALE)
|
|
383
|
+
local[0][0] = local[1][0] = geti16(offset) / 16384.0
|
|
384
|
+
offset += 2
|
|
385
|
+
elsif flags.allbits?(GOT_AN_X_AND_Y_SCALE)
|
|
386
|
+
local[0][0] = geti16(offset + 0) / 16384.0
|
|
387
|
+
local[1][0] = geti16(offset + 2) / 16384.0
|
|
388
|
+
offset += 4
|
|
389
|
+
elsif flags.allbits?(GOT_A_SCALE_MATRIX)
|
|
390
|
+
local[0][0] = geti16(offset + 0) / 16384.0
|
|
391
|
+
local[0][1] = geti16(offset + 2) / 16384.0
|
|
392
|
+
local[1][0] = geti16(offset + 4) / 16384.0
|
|
393
|
+
local[1][1] = geti16(offset + 6) / 16384.0
|
|
394
|
+
offset += 8
|
|
395
|
+
end
|
|
309
396
|
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
397
|
+
outline = outline_offset(glyph)
|
|
398
|
+
return nil if outline.nil?
|
|
399
|
+
base_point = outl.points.length
|
|
400
|
+
return nil if decode_outline(outline, rec_depth + 1, outl).nil?
|
|
401
|
+
transform_points(local,outl.points[base_point..-1])
|
|
402
|
+
end
|
|
403
|
+
return outl
|
|
315
404
|
end
|
|
316
|
-
return outl
|
|
317
|
-
end
|
|
318
405
|
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
406
|
+
HORIZONTAL_KERNING = 0x01
|
|
407
|
+
MINIMUM_KERNING = 0x02
|
|
408
|
+
CROSS_STREAM_KERNING = 0x04
|
|
409
|
+
OVERRIDE_KERNING = 0x08
|
|
410
|
+
|
|
411
|
+
def kerning
|
|
412
|
+
return @kerning if @kerning
|
|
413
|
+
offset = tables["kern"]
|
|
414
|
+
return nil if offset.nil? || getu16(offset) != 0
|
|
415
|
+
offset += 4
|
|
416
|
+
@kerning = {}
|
|
417
|
+
getu16(offset - 2).times do
|
|
418
|
+
length,format,flags = at(offset+2,6).unpack("S>CC")
|
|
419
|
+
offset += 6
|
|
420
|
+
if format == 0 && flags.allbits?(HORIZONTAL_KERNING) && flags.nobits?(MINIMUM_KERNING)
|
|
421
|
+
offset += 8
|
|
422
|
+
getu16(offset-8).times do |i|
|
|
423
|
+
v = geti16(offset+i*6+4)
|
|
424
|
+
@kerning[at(offset+i*6,4)] =
|
|
425
|
+
Kerning.new(* flags.allbits?(CROSS_STREAM_KERNING) ? [0,v] : [v,0])
|
|
426
|
+
end
|
|
339
427
|
end
|
|
428
|
+
offset += length
|
|
340
429
|
end
|
|
341
|
-
|
|
430
|
+
@kerning
|
|
342
431
|
end
|
|
343
|
-
@kerning
|
|
344
432
|
end
|
|
345
433
|
end
|