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.
data/lib/skrift/font.rb CHANGED
@@ -1,343 +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}'")
43
+ def reqtable(tag) = (tables[tag] or raise "Unable to get table '#{tag}'")
40
44
 
41
- def glyph_bbox(outline)
42
- box = at(outline+2, 8).unpack("s>*")
43
- raise "Broken bbox #{box.inspect}" if box[2] < box[0] || box[3] < box[1]
44
- return box
45
- 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
46
50
 
47
- # Returns the offset into the font that the glyph's outline is stored at
48
- def outline_offset(glyph) # 806
49
- loca = reqtable("loca")
50
- glyf = reqtable("glyf")
51
- if @loca_format == 0
52
- base = loca + 2 * glyph
53
- this = 2 * getu16(base)
54
- next_ = 2 * getu16(base + 2)
55
- else
56
- 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
57
63
  end
58
- return this == next_ ? nil : glyf + this
59
- end
60
64
 
61
- def each_cmap_entry
62
- cmap = reqtable("cmap")
63
- getu16(cmap + 2).times do |idx|
64
- entry = cmap + 4 + idx * 8
65
- type = getu16(entry) * 0100 + getu16(entry + 2)
66
- table = cmap + getu32(entry + 4)
67
- format = getu16(table)
68
- 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)
69
81
  end
70
- end
71
-
72
- # Maps unicode code points to glyph indices
73
- def glyph_id(char_code)
74
- each_cmap_entry do |type, table, format|
75
- if (type == 0004 || type == 0312)
76
- return cmap_fmt12_13(table, char_code, 12) if format == 12
77
- 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)
78
91
  end
79
92
  end
80
93
 
81
- # If no full repertoire cmap was found, try looking for a Unicode BMP map
82
- each_cmap_entry do |type, table, format|
83
- if type == 0003 || type == 0301
84
- return cmap_fmt4(table + 6, char_code) if format == 4
85
- return cmap_fmt6(table + 6, char_code) if format == 6
86
- 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
87
110
  end
111
+ return nil
88
112
  end
89
- return nil
90
- end
91
113
 
92
- def hor_metrics(glyph)
93
- hmtx = reqtable("hmtx")
94
- return nil if hmtx.nil?
95
- if glyph < @num_long_hmtx # In long metrics segment?
96
- offset = hmtx + 4 * glyph
97
- 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)
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
- def cmap_fmt4(table, char_code) # 572
110
- # cmap format 4 only supports the Unicode BMP
111
- return nil if char_code > 0xffff
112
- seg_count_x2 = getu16(table)
113
- raise "Error" if (seg_count_x2 & 1) != 0 or seg_count_x2 == 0
114
- # Find starting positions of the relevant arrays
115
- end_codes = table + 8
116
- start_codes = end_codes + seg_count_x2 + 2
117
- id_deltas = start_codes + seg_count_x2
118
- id_range_offsets = id_deltas + seg_count_x2
119
-
120
- @ecodes ||= at(end_codes,seg_count_x2 -1).unpack("n*")
121
- seg_id_x_x2 = @ecodes.bsearch_index {|i| i > char_code }.to_i * 2
122
-
123
- # Look up segment info from the arrays & short circuit if the spec requires
124
- start_code = getu16(start_codes + seg_id_x_x2)
125
- return 0 if start_code > char_code
126
- id_delta = getu16(id_deltas + seg_id_x_x2)
127
- if (id_range_offset = getu16(id_range_offsets + seg_id_x_x2)) == 0
128
- # Intentional integer under- and overflow
129
- 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
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
- def decode_outline(offset, rec_depth = 0, outl = Outline.new)
137
- num_contours = geti16(offset)
138
- return nil if num_contours == 0
139
- return simple_outline(offset + 10, num_contours, outl) if num_contours > 0
140
- return compound_outline(offset + 10, rec_depth, outl)
141
- 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
142
164
 
143
- def cmap_fmt6(table, char_code) # 621
144
- first_code, entry_count = at(table,4).unpack("S>*")
145
- return nil if !char_code.between?(first_code, 0xffff)
146
- char_code -= first_code
147
- return nil if (char_code >= entry_count)
148
- return getu16(table + 4 + 2 * char_code)
149
- 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
150
172
 
151
- def cmap_fmt12_13(table, char_code, which)
152
- getu32(table + 12).times do |i|
153
- first_code, last_code, glyph_offset = at(table + (i*12) + 16, 12).unpack("N*")
154
- next if char_code < first_code || char_code > last_code
155
- glyph_offset += char_code-first_code if which == 12
156
- 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
157
181
  end
158
- return nil
159
- end
160
182
 
161
- 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
162
196
 
163
- # For a simple outline, determines each point of the outline with a set of flags
164
- def simple_flags(off, num_pts, flags)
165
- value = 0
166
- repeat = 0
167
- num_pts.times do |i|
168
- if repeat > 0
169
- repeat -= 1
170
- else
171
- value = getu8(off)
172
- off += 1
173
- if value.allbits?(REPEAT_FLAG)
174
- repeat = getu8(off)
175
- 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
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
- flags[i] = value
215
+ nil
179
216
  end
180
- return off
181
- end
182
217
 
183
- X_CHANGE_IS_SMALL = 0x02 # x2 for Y
184
- X_CHANGE_IS_ZERO = 0x10 # x2 for Y
185
- X_CHANGE_IS_POSITIVE = 0x10 # x2 for Y
186
-
187
- def simple_points(offset, num_pts, points, base_point)
188
- [].tap do |flags|
189
- offset = simple_flags(offset, num_pts, flags)
190
-
191
- accum = 0.0
192
- accumulate = ->(i, factor) do
193
- if flags[i].allbits?(X_CHANGE_IS_SMALL * factor)
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
- def simple_outline(offset, num_contours, outl = Outline.new)
211
- base_points = outl.points.length
212
- num_pts = getu16(offset + (num_contours - 1) *2) + 1
213
- end_pts = at(offset, num_contours*2).unpack("S>*")
214
- offset += 2*num_contours
215
- # Falling end_pts have no sensible interpretation, so treat as error
216
- end_pts.each_cons(2) { |a, b| raise if b < a + 1 }
217
- offset += 2 + getu16(offset)
218
-
219
- flags = simple_points(offset, num_pts, outl.points, base_points)
220
-
221
- beg = 0
222
- num_contours.times do |i|
223
- decode_contour(outl, flags, beg, base_points+beg, end_pts[i] - beg + 1)
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
- POINT_IS_ON_CURVE = 0x01
230
-
231
- def decode_contour(outl, flags, off, base_point, count)
232
- return true if count < 2 # Invisible (no area)
233
-
234
- if flags[off].allbits?(POINT_IS_ON_CURVE)
235
- loose_end = base_point
236
- base_point+= 1
237
- off += 1
238
- count -= 1
239
- elsif flags[off + count - 1].allbits?(POINT_IS_ON_CURVE)
240
- count -= 1
241
- loose_end = base_point + count
242
- else
243
- loose_end = outl.points.length
244
- 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
245
270
  end
246
- beg = loose_end
247
- ctrl = nil
248
- count.times do |i|
249
- cur = base_point + i
250
- if flags[off+i].allbits?(POINT_IS_ON_CURVE)
251
- outl.segments << Outline::Segment.new(beg, cur, ctrl)
252
- beg = cur
253
- ctrl = nil
254
- else
255
- if ctrl # 2x control points in a row -> insert midpoint
256
- center = outl.points.length
257
- outl.points << midpoint(outl.points[ctrl], outl.points[cur])
258
- outl.segments << Outline::Segment.new(beg, center, ctrl)
259
- 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
260
291
  end
261
- 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) }
262
296
  end
263
297
  end
264
- outl.segments << Outline::Segment.new(beg, loose_end, ctrl)
265
- return true
266
- end
267
298
 
268
- OFFSETS_ARE_LARGE = 0x001
269
- ACTUAL_XY_OFFSETS = 0x002
270
- GOT_A_SINGLE_SCALE = 0x008
271
- THERE_ARE_MORE_COMPONENTS = 0x020
272
- GOT_AN_X_AND_Y_SCALE = 0x040
273
- GOT_A_SCALE_MATRIX = 0x080
274
-
275
- def compound_outline(offset, rec_depth, outl) # 1057
276
- # Guard against infinite recursion (compound glyphs that have themselves as component).
277
- return nil if rec_depth >= 4
278
- flags = THERE_ARE_MORE_COMPONENTS
279
- while flags.allbits?(THERE_ARE_MORE_COMPONENTS)
280
- flags, glyph = at(offset,4).unpack("S>*")
281
- offset += 4
282
- # We don't implement point matching, and neither does stb truetype
283
- return nil if (flags & ACTUAL_XY_OFFSETS) == 0
284
- # Read additional X and Y offsets (in FUnits) of this component.
285
- if (flags & OFFSETS_ARE_LARGE) != 0
286
- local = [[1.0, 0.0, geti16(offset)], [0.0,1.0, geti16(offset+2)]]
287
- 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
288
331
  else
289
- local = [[1.0, 0.0, geti8(offset)], [0.0, 1.0, geti8(offset)+1]]
290
- offset += 2
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
- if flags.allbits?(GOT_A_SINGLE_SCALE)
294
- local[0][0] = local[1][0] = geti16(offset) / 16384.0
295
- offset += 2
296
- elsif flags.allbits?(GOT_AN_X_AND_Y_SCALE)
297
- local[0][0] = geti16(offset + 0) / 16384.0
298
- 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>*")
299
370
  offset += 4
300
- elsif flags.allbits?(GOT_A_SCALE_MATRIX)
301
- local[0][0] = geti16(offset + 0) / 16384.0
302
- local[0][1] = geti16(offset + 2) / 16384.0
303
- local[1][0] = geti16(offset + 4) / 16384.0
304
- local[1][1] = geti16(offset + 6) / 16384.0
305
- offset += 8
306
- 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
307
396
 
308
- outline = outline_offset(glyph)
309
- return nil if outline.nil?
310
- base_point = outl.points.length
311
- return nil if decode_outline(outline, rec_depth + 1, outl).nil?
312
- 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
313
404
  end
314
- return outl
315
- end
316
405
 
317
- HORIZONTAL_KERNING = 0x01
318
- MINIMUM_KERNING = 0x02
319
- CROSS_STREAM_KERNING = 0x04
320
- OVERRIDE_KERNING = 0x08
321
-
322
- def kerning
323
- return @kerning if @kerning
324
- offset = tables["kern"]
325
- return nil if offset.nil? || getu16(offset) != 0
326
- offset += 4
327
- @kerning = {}
328
- getu16(offset - 2).times do
329
- length,format,flags = at(offset+2,6).unpack("S>CC")
330
- offset += 6
331
- if format == 0 && flags.allbits?(HORIZONTAL_KERNING) && flags.nobits?(MINIMUM_KERNING)
332
- offset += 8
333
- getu16(offset-8).times do |i|
334
- v = geti16(offset+i*6+4)
335
- @kerning[at(offset+i*6,4)] =
336
- 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
337
427
  end
428
+ offset += length
338
429
  end
339
- offset += length
430
+ @kerning
340
431
  end
341
- @kerning
342
432
  end
343
433
  end