skrift 0.2.1 → 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/{example.rb → examples/banner.rb} +1 -1
- data/{example2.rb → examples/banner_boxdrawing.rb} +1 -1
- data/{example3.rb → examples/banner_shadow.rb} +1 -1
- data/{test.rb → examples/glyph_dump.rb} +1 -1
- data/lib/skrift/font.rb +381 -291
- data/lib/skrift/font_set.rb +58 -0
- data/lib/skrift/glyph_cache.rb +142 -0
- data/lib/skrift/outline.rb +42 -48
- data/lib/skrift/raster.rb +74 -72
- data/lib/skrift/sft.rb +84 -71
- data/lib/skrift/version.rb +1 -1
- data/lib/skrift.rb +45 -10
- metadata +10 -8
data/lib/skrift/font.rb
CHANGED
|
@@ -1,343 +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
|
-
|
|
43
|
+
def reqtable(tag) = (tables[tag] or raise "Unable to get table '#{tag}'")
|
|
40
44
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
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
|
|
46
50
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
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
|
|
57
63
|
end
|
|
58
|
-
return this == next_ ? nil : glyf + this
|
|
59
|
-
end
|
|
60
64
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
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)
|
|
69
81
|
end
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
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)
|
|
78
91
|
end
|
|
79
92
|
end
|
|
80
93
|
|
|
81
|
-
#
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
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
|
|
87
110
|
end
|
|
111
|
+
return nil
|
|
88
112
|
end
|
|
89
|
-
return nil
|
|
90
|
-
end
|
|
91
113
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
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)
|
|
98
128
|
end
|
|
99
|
-
# Glyph is inside short metrics segment
|
|
100
|
-
boundary = hmtx + 4 * @num_long_hmtx
|
|
101
|
-
return nil if boundary < 4
|
|
102
|
-
offset = boundary - 4
|
|
103
|
-
advance_width = getu16(offset)
|
|
104
|
-
offset = boundary + 2 * (glyph - @num_long_hmtx)
|
|
105
|
-
return advance_width, geti16(offset)
|
|
106
|
-
end
|
|
107
129
|
|
|
108
130
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
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
|
|
130
156
|
end
|
|
131
|
-
# Calculate offset into glyph array and determine ultimate value
|
|
132
|
-
id = getu16(id_range_offsets + seg_id_x_x2 + id_range_offset + 2 * (char_code - start_code))
|
|
133
|
-
return id ? (id + id_delta) & 0xffff : 0
|
|
134
|
-
end
|
|
135
157
|
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
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
|
|
142
164
|
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
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
|
|
150
172
|
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
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
|
|
157
181
|
end
|
|
158
|
-
return nil
|
|
159
|
-
end
|
|
160
182
|
|
|
161
|
-
|
|
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
|
|
162
196
|
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
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
|
|
176
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
|
|
177
214
|
end
|
|
178
|
-
|
|
215
|
+
nil
|
|
179
216
|
end
|
|
180
|
-
return off
|
|
181
|
-
end
|
|
182
217
|
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
offset += 1
|
|
195
|
-
bit = flags[i].allbits?(X_CHANGE_IS_POSITIVE * factor) ? 1 : 0
|
|
196
|
-
accum -= (getu8(offset-1) ^ -bit) + bit
|
|
197
|
-
elsif flags[i].nobits?(X_CHANGE_IS_ZERO * factor)
|
|
198
|
-
offset += 2
|
|
199
|
-
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)
|
|
200
229
|
end
|
|
201
|
-
accum
|
|
202
230
|
end
|
|
203
|
-
|
|
204
|
-
num_pts.times {|i| points << Raster::Vector.new(accumulate.call(i,1), 0.0) }
|
|
205
|
-
accum = 0.0
|
|
206
|
-
num_pts.times {|i| points[base_point+i][1] = accumulate.call(i,2) }
|
|
231
|
+
nil
|
|
207
232
|
end
|
|
208
|
-
end
|
|
209
233
|
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
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
|
|
225
248
|
end
|
|
226
|
-
outl
|
|
227
|
-
end
|
|
228
249
|
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
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
|
|
245
270
|
end
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
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
|
|
260
291
|
end
|
|
261
|
-
|
|
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) }
|
|
262
296
|
end
|
|
263
297
|
end
|
|
264
|
-
outl.segments << Outline::Segment.new(beg, loose_end, ctrl)
|
|
265
|
-
return true
|
|
266
|
-
end
|
|
267
298
|
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
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
|
|
288
331
|
else
|
|
289
|
-
|
|
290
|
-
|
|
332
|
+
loose_end = outl.points.length
|
|
333
|
+
outl.points << midpoint(outl.points[base_point], outl.points[base_point + count - 1])
|
|
291
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
|
|
292
356
|
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
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>*")
|
|
299
370
|
offset += 4
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
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
|
|
307
396
|
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
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
|
|
313
404
|
end
|
|
314
|
-
return outl
|
|
315
|
-
end
|
|
316
405
|
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
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
|
|
337
427
|
end
|
|
428
|
+
offset += length
|
|
338
429
|
end
|
|
339
|
-
|
|
430
|
+
@kerning
|
|
340
431
|
end
|
|
341
|
-
@kerning
|
|
342
432
|
end
|
|
343
433
|
end
|