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.
data/lib/skrift/font.rb CHANGED
@@ -1,345 +1,433 @@
1
- class Font
2
- # TrueType, TrueType, OpenType
3
- FILE_MAGIC = ["\0\1\0\0", "true", "OTTO"]
4
-
5
- attr_reader :memory, :units_per_em
6
-
7
- def initialize(memory)
8
- @memory = memory
9
- raise "Unsupported format (magic value: #{at(0,4).inspect})" if !FILE_MAGIC.member?(at(0,4))
10
- head = reqtable("head")
11
- @units_per_em = getu16(head + 18)
12
- @loca_format = geti16(head + 50)
13
- hhea = reqtable("hhea")
14
- @num_long_hmtx = getu16(hhea + 34)
15
- end
16
-
17
- def Font.load(filename) # loadfile, 103
18
- memory = File.read(filename).force_encoding("ASCII-8BIT")
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
- def at(offset, len=1)
23
- raise "Out of bounds #{offset} / len #{len} (max: #{@memory.size})" if offset.to_i + len.to_i >= @memory.size
24
- @memory[offset..(offset+len-1)]
25
- end
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
- def getu8(offset); at(offset).ord; end
28
- def geti8(offset); at(offset).unpack1("c"); end
29
- def getu16(offset); at(offset,2).unpack1("S>"); end
30
- def geti16(offset); at(offset,2).unpack1("s>"); end
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
- def tables
34
- @tables ||= Hash[*
35
- getu16(4).times.map {|t| [at(t*16 + 12,4),getu32(t*16 + 20)] }.flatten
36
- ]
37
- end
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
- def reqtable(tag); tables[tag] or raise "Unable to get table '#{tag}'"; end
40
- def gettable(tag); tables[tag]; end
43
+ def reqtable(tag) = (tables[tag] or raise "Unable to get table '#{tag}'")
41
44
 
42
- def glyph_bbox(outline)
43
- box = at(outline+2, 8).unpack("s>*")
44
- raise "Broken bbox #{box.inspect}" if box[2] < box[0] || box[3] < box[1]
45
- return box
46
- end
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
- # Returns the offset into the font that the glyph's outline is stored at
49
- def outline_offset(glyph) # 806
50
- loca = reqtable("loca")
51
- glyf = reqtable("glyf")
52
- if @loca_format == 0
53
- base = loca + 2 * glyph
54
- this = 2 * getu16(base)
55
- next_ = 2 * getu16(base + 2)
56
- else
57
- this, next_ = at(loca + 4 * glyph,8).unpack("NN")
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
- def each_cmap_entry
63
- cmap = reqtable("cmap")
64
- getu16(cmap + 2).times do |idx|
65
- entry = cmap + 4 + idx * 8
66
- type = getu16(entry) * 0100 + getu16(entry + 2)
67
- table = cmap + getu32(entry + 4)
68
- format = getu16(table)
69
- yield(type, table, format)
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
- end
72
-
73
- # Maps unicode code points to glyph indices
74
- def glyph_id(char_code)
75
- each_cmap_entry do |type, table, format|
76
- if (type == 0004 || type == 0312)
77
- return cmap_fmt12_13(table, char_code, 12) if format == 12
78
- return nil
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
- # If no full repertoire cmap was found, try looking for a Unicode BMP map
83
- each_cmap_entry do |type, table, format|
84
- if type == 0003 || type == 0301
85
- return cmap_fmt4(table + 6, char_code) if format == 4
86
- return cmap_fmt6(table + 6, char_code) if format == 6
87
- return nil
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
- def hor_metrics(glyph)
94
- hmtx = reqtable("hmtx")
95
- return nil if hmtx.nil?
96
- if glyph < @num_long_hmtx # In long metrics segment?
97
- offset = hmtx + 4 * glyph
98
- return getu16(offset), geti16(offset + 2)
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
- def cmap_fmt4(table, char_code) # 572
111
- # cmap format 4 only supports the Unicode BMP
112
- return nil if char_code > 0xffff
113
- seg_count_x2 = getu16(table)
114
- raise "Error" if (seg_count_x2 & 1) != 0 or seg_count_x2 == 0
115
- # Find starting positions of the relevant arrays
116
- end_codes = table + 8
117
- start_codes = end_codes + seg_count_x2 + 2
118
- id_deltas = start_codes + seg_count_x2
119
- id_range_offsets = id_deltas + seg_count_x2
120
-
121
- @ecodes ||= at(end_codes,seg_count_x2 -1).unpack("n*")
122
- seg_id_x_x2 = @ecodes.bsearch_index {|i| i > char_code } * 2
123
-
124
- # Look up segment info from the arrays & short circuit if the spec requires
125
- start_code = getu16(start_codes + seg_id_x_x2)
126
- return 0 if start_code > char_code
127
- id_delta = getu16(id_deltas + seg_id_x_x2)
128
- if (id_range_offset = getu16(id_range_offsets + seg_id_x_x2)) == 0
129
- # Intentional integer under- and overflow
130
- return (char_code + id_delta) & 0xffff
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
- def decode_outline(offset, rec_depth = 0, outl = Outline.new)
138
- num_contours = geti16(offset)
139
- return nil if num_contours == 0
140
- return simple_outline(offset + 10, num_contours, outl) if num_contours > 0
141
- return compound_outline(offset + 10, rec_depth, outl)
142
- end
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
- def cmap_fmt6(table, char_code) # 621
145
- first_code, entry_count = at(table,4).unpack("S>*")
146
- return nil if !char_code.between?(first_code, 0xffff)
147
- char_code -= first_code
148
- return nil if (char_code >= entry_count)
149
- return getu16(table + 4 + 2 * char_code)
150
- end
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
- def cmap_fmt12_13(table, char_code, which)
153
- getu32(table + 12).times do |i|
154
- first_code, last_code, glyph_offset = at(table + (i*12) + 16, 12).unpack("N*")
155
- next if char_code < first_code || char_code > last_code
156
- glyph_offset += char_code-first_code if which == 12
157
- return glyph_offset
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
- REPEAT_FLAG = 0x08
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
- # For a simple outline, determines each point of the outline with a set of flags
165
- def simple_flags(off, num_pts, flags)
166
- value = 0
167
- repeat = 0
168
- num_pts.times do |i|
169
- if repeat > 0
170
- repeat -= 1
171
- else
172
- value = getu8(off)
173
- off += 1
174
- if value.allbits?(REPEAT_FLAG)
175
- repeat = getu8(off)
176
- off += 1
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
- flags[i] = value
215
+ nil
180
216
  end
181
- return off
182
- end
183
217
 
184
- X_CHANGE_IS_SMALL = 0x02 # x2 for Y
185
- X_CHANGE_IS_ZERO = 0x10 # x2 for Y
186
- X_CHANGE_IS_POSITIVE = 0x10 # x2 for Y
187
-
188
- def simple_points(offset, num_pts, points, base_point)
189
- [].tap do |flags|
190
- offset = simple_flags(offset, num_pts, flags)
191
-
192
- accum = 0.0
193
- accumulate = ->(i, factor) do
194
- if flags[i].allbits?(X_CHANGE_IS_SMALL * factor)
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
- def simple_outline(offset, num_contours, outl = Outline.new)
212
- base_points = outl.points.length
213
- num_pts = getu16(offset + (num_contours - 1) *2) + 1
214
- end_pts = at(offset, num_contours*2).unpack("S>*")
215
- offset += 2*num_contours
216
- # Falling end_pts have no sensible interpretation, so treat as error
217
- end_pts.each_cons(2) { |a, b| raise if b < a + 1 }
218
- offset += 2 + getu16(offset)
219
-
220
- flags = simple_points(offset, num_pts, outl.points, base_points)
221
-
222
- beg = 0
223
- num_contours.times do |i|
224
- decode_contour(outl, flags, beg, base_points+beg, end_pts[i] - beg + 1)
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
- POINT_IS_ON_CURVE = 0x01
231
-
232
- def decode_contour(outl, flags, off, base_point, count)
233
- return true if count < 2 # Invisible (no area)
234
-
235
- if flags[off].allbits?(POINT_IS_ON_CURVE)
236
- loose_end = base_point
237
- base_point+= 1
238
- off += 1
239
- count -= 1
240
- elsif flags[off + count - 1].allbits?(POINT_IS_ON_CURVE)
241
- count -= 1
242
- loose_end = base_point + count
243
- else
244
- loose_end = outl.points.length
245
- outl.points << midpoint(outl.points[base_point], outl.points[base_point + count - 1])
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
- beg = loose_end
248
- ctrl = nil
249
- count.times do |i|
250
- cur = base_point + i
251
- if flags[off+i].allbits?(POINT_IS_ON_CURVE)
252
- outl.segments << Outline::Segment.new(beg, cur, ctrl)
253
- beg = cur
254
- ctrl = nil
255
- else
256
- if ctrl # 2x control points in a row -> insert midpoint
257
- center = outl.points.length
258
- outl.points << midpoint(outl.points[ctrl], outl.points[cur])
259
- outl.segments << Outline::Segment.new(beg, center, ctrl)
260
- beg = center
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
- ctrl = cur
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
- OFFSETS_ARE_LARGE = 0x001
270
- ACTUAL_XY_OFFSETS = 0x002
271
- GOT_A_SINGLE_SCALE = 0x008
272
- THERE_ARE_MORE_COMPONENTS = 0x020
273
- GOT_AN_X_AND_Y_SCALE = 0x040
274
- GOT_A_SCALE_MATRIX = 0x080
275
-
276
- def compound_outline(offset, rec_depth, outl) # 1057
277
- # Guard against infinite recursion (compound glyphs that have themselves as component).
278
- return nil if rec_depth >= 4
279
- flags = THERE_ARE_MORE_COMPONENTS
280
- while flags.allbits?(THERE_ARE_MORE_COMPONENTS)
281
- flags, glyph = at(offset,4).unpack("S>*")
282
- p [flags,glyph]
283
- offset += 4
284
- # We don't implement point matching, and neither does stb truetype
285
- return nil if (flags & ACTUAL_XY_OFFSETS) == 0
286
- # Read additional X and Y offsets (in FUnits) of this component.
287
- if (flags & OFFSETS_ARE_LARGE) != 0
288
- local = Matrix[[1.0, 0.0, geti16(offset)], [0.0,1.0, geti16(offset+2)]]
289
- offset += 4
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
- local = Matrix[[1.0, 0.0, geti8(offset)], [0.0, 1.0, geti8(offset)+1]]
292
- offset += 2
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
- if flags.allbits?(GOT_A_SINGLE_SCALE)
296
- local[0][0] = local[1][0] = geti16(offset) / 16384.0
297
- offset += 2
298
- elsif flags.allbits?(GOT_AN_X_AND_Y_SCALE)
299
- local[0][0] = geti16(offset + 0) / 16384.0
300
- local[1][0] = geti16(offset + 2) / 16384.0
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
- elsif flags.allbits?(GOT_A_SCALE_MATRIX)
303
- local[0][0] = geti16(offset + 0) / 16384.0
304
- local[0][1] = geti16(offset + 2) / 16384.0
305
- local[1][0] = geti16(offset + 4) / 16384.0
306
- local[1][1] = geti16(offset + 6) / 16384.0
307
- offset += 8
308
- end
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
- outline = outline_offset(glyph)
311
- return nil if outline.nil?
312
- base_point = outl.points.length
313
- return nil if decode_outline(outline, rec_depth + 1, outl).nil?
314
- transform_points(local,outl.points[base_point..-1])
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
- HORIZONTAL_KERNING = 0x01
320
- MINIMUM_KERNING = 0x02
321
- CROSS_STREAM_KERNING = 0x04
322
- OVERRIDE_KERNING = 0x08
323
-
324
- def kerning
325
- return @kerning if @kerning
326
- offset = gettable("kern")
327
- return nil if offset.nil? || getu16(offset) != 0
328
- offset += 4
329
- @kerning = {}
330
- getu16(offset - 2).times do
331
- length,format,flags = at(offset+2,6).unpack("S>CC")
332
- offset += 6
333
- if format == 0 && flags.allbits?(HORIZONTAL_KERNING) && flags.nobits?(MINIMUM_KERNING)
334
- offset += 8
335
- getu16(offset-8).times do |i|
336
- v = geti16(offset+i*6+4)
337
- @kerning[at(offset+i*6,4)] =
338
- Kerning.new(* flags.allbits?(CROSS_STREAM_KERNING) ? [0,v] : [v,0])
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
- offset += length
430
+ @kerning
342
431
  end
343
- @kerning
344
432
  end
345
433
  end