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