package font import "core:mem" import "core:math" // Check if data represents a font @(private) is_font :: proc(font: [^]u8) -> bool { // Check for known font signatures if tag4(font, '1', 0, 0, 0) do return true // TrueType 1 if tag(font, "typ1") do return true // TrueType with type 1 font if tag(font, "OTTO") do return true // OpenType with CFF if tag4(font, 0, 1, 0, 0) do return true // OpenType 1.0 if tag(font, "true") do return true // Apple TrueType return false } // Find a table in the font directory @(private) find_table :: proc(data: [^]u8, fontstart: u32, tag_str: string) -> u32 { num_tables := i32(ttUSHORT(data[fontstart+4:])) tabledir := fontstart + 12 for i in 0.. i32 { // If it's just a font, there's only one valid index if is_font(font_collection) { return 0 if index == 0 else -1 } // Check if it's a TTC (TrueType Collection) if tag(font_collection, "ttcf") { // Version 1.0 or 2.0? version := ttULONG(font_collection[4:]) if version == 0x00010000 || version == 0x00020000 { n := ttLONG(font_collection[8:]) if index >= n { return -1 } return i32(ttULONG(font_collection[12 + u32(index)*4:])) } } return -1 } @(private) get_number_of_fonts :: proc(font_collection: [^]u8) -> i32 { // If it's just a font, there's only one valid font if is_font(font_collection) { return 1 } // Check if it's a TTC if tag(font_collection, "ttcf") { // Version 1.0 or 2.0? version := ttULONG(font_collection[4:]) if version == 0x00010000 || version == 0x00020000 { return ttLONG(font_collection[8:]) } } return 0 } @(private) new_buf :: proc(data: [^]u8, size: i32) -> Buf { return Buf{data = data, cursor = 0, size = size} } // Buf helper functions for CFF parsing @(private) buf_get8 :: #force_inline proc(b: ^Buf) -> u8 { if b.cursor < b.size { r := b.data[b.cursor] b.cursor += 1 return r } return 0 } @(private) buf_peek8 :: proc(b: ^Buf) -> u8 { if b.cursor < b.size { return b.data[b.cursor] } return 0 } @(private) buf_seek :: proc(b: ^Buf, o: i32) { b.cursor = b.size if (o > b.size || o < 0) else o } @(private) buf_skip :: proc(b: ^Buf, o: i32) { buf_seek(b, b.cursor + o) } @(private) buf_get :: proc(b: ^Buf, n: i32) -> u32 { v: u32 = 0 for _ in 0.. u32 { return buf_get(b, 2) } @(private) buf_get32 :: #force_inline proc(b: ^Buf) -> u32 { return buf_get(b, 4) } @(private) buf_range :: proc(b: ^Buf, o: i32, s: i32) -> Buf { if o < 0 || s < 0 || o > b.size || s > b.size - o { return new_buf(nil, 0) } return Buf{data = &b.data[o], cursor = 0, size = s} } // CFF INDEX parsing functions @(private) cff_get_index :: proc(b: ^Buf) -> Buf { start := b.cursor count := i32(buf_get16(b)) if count != 0 { offsize := i32(buf_get8(b)) buf_skip(b, offsize * count) buf_skip(b, i32(buf_get(b, offsize)) - 1) } return buf_range(b, start, b.cursor - start) } @(private) cff_index_count :: proc(b: ^Buf) -> i32 { b := b^ buf_seek(&b, 0) return i32(buf_get16(&b)) } @(private) cff_index_get :: proc(b: Buf, i: i32) -> Buf { b := b buf_seek(&b, 0) count := i32(buf_get16(&b)) offsize := i32(buf_get8(&b)) buf_skip(&b, i * offsize) start := i32(buf_get(&b, offsize)) end := i32(buf_get(&b, offsize)) return buf_range(&b, 2 + (count + 1) * offsize + start, end - start) } // CFF DICT parsing functions @(private) cff_int :: proc(b: ^Buf) -> i32 { b0 := buf_get8(b) if b0 >= 32 && b0 <= 246 { return i32(b0) - 139 } else if b0 >= 247 && b0 <= 250 { return (i32(b0) - 247) * 256 + i32(buf_get8(b)) + 108 } else if b0 >= 251 && b0 <= 254 { return -(i32(b0) - 251) * 256 - i32(buf_get8(b)) - 108 } else if b0 == 28 { return i32(buf_get16(b)) } else if b0 == 29 { return i32(buf_get32(b)) } return 0 } @(private) cff_skip_operand :: proc(b: ^Buf) { b0 := buf_peek8(b) if b0 == 30 { buf_skip(b, 1) for b.cursor < b.size { v := buf_get8(b) if (v & 0xF) == 0xF || (v >> 4) == 0xF { break } } } else { cff_int(b) } } @(private) dict_get :: proc(b: ^Buf, key: i32) -> Buf { buf_seek(b, 0) for b.cursor < b.size { start := b.cursor for buf_peek8(b) >= 28 { cff_skip_operand(b) } end := b.cursor op := i32(buf_get8(b)) if op == 12 { op = i32(buf_get8(b)) | 0x100 } if op == key { return buf_range(b, start, end - start) } } return buf_range(b, 0, 0) } @(private) dict_get_ints :: proc(b: ^Buf, key: i32, outcount: i32, out: [^]u32) { operands := dict_get(b, key) for i in 0..= operands.size { break } out[i] = u32(cff_int(&operands)) } } // Subroutine retrieval functions @(private) get_subrs :: proc(cff: Buf, fontdict: Buf) -> Buf { subrsoff: u32 = 0 private_loc: [2]u32 = {0, 0} fontdict := fontdict cff := cff dict_get_ints(&fontdict, 18, 2, &private_loc[0]) if private_loc[1] == 0 || private_loc[0] == 0 { return new_buf(nil, 0) } pdict := buf_range(&cff, i32(private_loc[1]), i32(private_loc[0])) dict_get_ints(&pdict, 19, 1, &subrsoff) if subrsoff == 0 { return new_buf(nil, 0) } buf_seek(&cff, i32(private_loc[1]) + i32(subrsoff)) return cff_get_index(&cff) } @(private) get_subr :: proc(idx: Buf, n: i32) -> Buf { idx := idx count := cff_index_count(&idx) bias: i32 = 107 if count >= 33900 { bias = 32768 } else if count >= 1240 { bias = 1131 } n := n + bias if n < 0 || n >= count { return new_buf(nil, 0) } return cff_index_get(idx, n) } @(private) cid_get_glyph_subrs :: proc(info: ^Font_Info, glyph_index: i32) -> Buf { fdselect := info.fdselect buf_seek(&fdselect, 0) fmt := buf_get8(&fdselect) fdselector: i32 = -1 if fmt == 0 { buf_skip(&fdselect, glyph_index) fdselector = i32(buf_get8(&fdselect)) } else if fmt == 3 { nranges := i32(buf_get16(&fdselect)) start := i32(buf_get16(&fdselect)) for _ in 0..= start && glyph_index < end { fdselector = v break } start = end } } if fdselector == -1 { return new_buf(nil, 0) } return get_subrs(info.cff, cff_index_get(info.fontdicts, fdselector)) } // Initialize a font from raw data with known size. // The size enables bounds checking on malformed font data. init_font_sized :: proc(info: ^Font_Info, data: [^]u8, data_size: i32, fontstart: i32) -> bool { info.data_size = data_size return init_font(info, data, fontstart) } init_font :: proc(info: ^Font_Info, data: [^]u8, fontstart: i32) -> bool { info.data = data info.fontstart = fontstart info.cff = new_buf(nil, 0) // Find required tables cmap := find_table(data, u32(fontstart), "cmap") info.loca = i32(find_table(data, u32(fontstart), "loca")) info.head = i32(find_table(data, u32(fontstart), "head")) info.glyf = i32(find_table(data, u32(fontstart), "glyf")) info.hhea = i32(find_table(data, u32(fontstart), "hhea")) info.hmtx = i32(find_table(data, u32(fontstart), "hmtx")) info.kern = i32(find_table(data, u32(fontstart), "kern")) info.gpos = i32(find_table(data, u32(fontstart), "GPOS")) info.gsub = i32(find_table(data, u32(fontstart), "GSUB")) // Verify required tables exist if cmap == 0 || info.head == 0 || info.hhea == 0 || info.hmtx == 0 { return false } // For TrueType, loca is required if info.glyf != 0 && info.loca == 0 { return false } // Cache font type for optimal dispatch performance info.is_cff = (info.glyf == 0) // Handle CFF/OpenType fonts if info.is_cff { cff_offset := find_table(data, u32(fontstart), "CFF ") if cff_offset == 0 { return false } info.fontdicts = new_buf(nil, 0) info.fdselect = new_buf(nil, 0) // Create CFF buffer - we need a reasonable size for the CFF data // The actual CFF data extent will be controlled by cursor/size tracking info.cff = new_buf(&data[cff_offset], 512 * 1024 * 1024) b := info.cff // Read CFF header buf_skip(&b, 2) // Skip major, minor version buf_seek(&b, i32(buf_get8(&b))) // Seek past header using hdrsize // Parse INDEX structures cff_get_index(&b) // Skip Name INDEX topdictidx := cff_get_index(&b) topdict := cff_index_get(topdictidx, 0) cff_get_index(&b) // Skip String INDEX info.gsubrs = cff_get_index(&b) // Get values from Top DICT charstrings: u32 = 0 cstype: u32 = 2 fdarrayoff: u32 = 0 fdselectoff: u32 = 0 dict_get_ints(&topdict, 17, 1, &charstrings) // CharStrings offset dict_get_ints(&topdict, 0x100 | 6, 1, &cstype) // CharstringType dict_get_ints(&topdict, 0x100 | 36, 1, &fdarrayoff) // FDArray offset dict_get_ints(&topdict, 0x100 | 37, 1, &fdselectoff) // FDSelect offset info.subrs = get_subrs(info.cff, topdict) // Only support Type 2 CharStrings if cstype != 2 { return false } if charstrings == 0 { return false } // Handle CID fonts if fdarrayoff != 0 { if fdselectoff == 0 { return false } buf_seek(&b, i32(fdarrayoff)) info.fontdicts = cff_get_index(&b) info.fdselect = buf_range(&b, i32(fdselectoff), b.size - i32(fdselectoff)) } buf_seek(&b, i32(charstrings)) info.charstrings = cff_get_index(&b) } // Get number of glyphs from maxp table t := find_table(data, u32(fontstart), "maxp") if t != 0 { info.num_glyphs = i32(ttUSHORT(data[t+4:])) } else { info.num_glyphs = 0xffff } info.svg = -1 // Find a cmap encoding table we understand num_tables := i32(ttUSHORT(data[cmap+2:])) info.index_map = 0 for i in 0.. 16384 { return false } // Initialize caches info.kern_cache = make(map[u64]i32) info.kern_cache_ready = true info.shape_cache = make(map[i32]Cached_Shape) info.shape_cache_ready = true // Build cmap cache lazily on first lookup (see find_glyph_index) return true } // Phase 1 Functions - Character to Glyph Mapping find_glyph_index :: proc(info: ^Font_Info, unicode_codepoint: i32) -> i32 { // Build cache on first call if !info.cmap_cache_ready { build_cmap_cache(info) } if gi, ok := info.cmap_cache[unicode_codepoint]; ok { return gi } return 0 } // Build cmap HashMap by iterating all codepoint→glyph mappings @(private) build_cmap_cache :: proc(info: ^Font_Info) { data := info.data index_map := u32(info.index_map) format := ttUSHORT(data[index_map:]) info.cmap_cache = make(map[i32]i32, int(info.num_glyphs)) info.cmap_cache_ready = true switch format { case 0: bytes := i32(ttUSHORT(data[index_map+2:])) for cp: i32 = 0; cp < bytes - 6; cp += 1 { gi := i32(ttBYTE(data[index_map+6+u32(cp):])) if gi != 0 do info.cmap_cache[cp] = gi } case 4: segcount := i32(ttUSHORT(data[index_map+6:]) >> 1) end_count := index_map + 14 for seg: i32 = 0; seg < segcount; seg += 1 { end := i32(ttUSHORT(data[end_count + u32(seg)*2:])) start := i32(ttUSHORT(data[index_map+14+u32(segcount)*2+2+u32(seg)*2:])) delta := i32(ttSHORT(data[index_map+14+u32(segcount)*4+2+u32(seg)*2:])) offset := u32(ttUSHORT(data[index_map+14+u32(segcount)*6+2+u32(seg)*2:])) if end == 0xFFFF && start == 0xFFFF do break for cp := start; cp <= end; cp += 1 { gi: i32 if offset == 0 { gi = i32(u16(cp + delta)) } else { addr := index_map+14+u32(segcount)*6+2+u32(seg)*2 + offset + u32(cp-start)*2 if info.data_size > 0 && i32(addr) + 2 > info.data_size do continue gi = i32(ttUSHORT(data[addr:])) if gi != 0 do gi = i32(u16(i32(gi) + delta)) } if gi != 0 do info.cmap_cache[cp] = gi } } case 6: first := i32(ttUSHORT(data[index_map+6:])) count := i32(ttUSHORT(data[index_map+8:])) for i: i32 = 0; i < count; i += 1 { gi := i32(ttUSHORT(data[index_map+10+u32(i)*2:])) if gi != 0 do info.cmap_cache[first + i] = gi } case 12, 13: ngroups := i32(ttULONG(data[index_map+12:])) for g: i32 = 0; g < ngroups; g += 1 { start_char := i32(ttULONG(data[index_map+16+u32(g)*12:])) end_char := i32(ttULONG(data[index_map+16+u32(g)*12+4:])) start_glyph := i32(ttULONG(data[index_map+16+u32(g)*12+8:])) for cp := start_char; cp <= end_char; cp += 1 { gi: i32 if format == 12 { gi = start_glyph + cp - start_char } else { gi = start_glyph } if gi != 0 do info.cmap_cache[cp] = gi } } } } // Phase 1 Functions - Scaling and Metrics scale_for_pixel_height :: proc(info: ^Font_Info, pixels: f32) -> f32 { fheight := f32(ttSHORT(info.data[u32(info.hhea)+4:]) - ttSHORT(info.data[u32(info.hhea)+6:])) return pixels / fheight } scale_for_mapping_em_to_pixels :: proc(info: ^Font_Info, pixels: f32) -> f32 { units_per_em := i32(ttUSHORT(info.data[u32(info.head)+18:])) return pixels / f32(units_per_em) } get_font_vmetrics :: proc(info: ^Font_Info, ascent: ^i32, descent: ^i32, line_gap: ^i32) { if ascent != nil { ascent^ = i32(ttSHORT(info.data[u32(info.hhea)+4:])) } if descent != nil { descent^ = i32(ttSHORT(info.data[u32(info.hhea)+6:])) } if line_gap != nil { line_gap^ = i32(ttSHORT(info.data[u32(info.hhea)+8:])) } } get_codepoint_hmetrics :: proc(info: ^Font_Info, codepoint: i32, advance_width: ^i32, left_side_bearing: ^i32) { glyph := find_glyph_index(info, codepoint) get_glyph_hmetrics(info, glyph, advance_width, left_side_bearing) } get_glyph_hmetrics :: proc(info: ^Font_Info, glyph_index: i32, advance_width: ^i32, left_side_bearing: ^i32) { num_of_long_hor_metrics := u32(ttUSHORT(info.data[u32(info.hhea)+34:])) if u32(glyph_index) < num_of_long_hor_metrics { if advance_width != nil { advance_width^ = i32(ttSHORT(info.data[u32(info.hmtx)+4*u32(glyph_index):])) } if left_side_bearing != nil { left_side_bearing^ = i32(ttSHORT(info.data[u32(info.hmtx)+4*u32(glyph_index)+2:])) } } else { if advance_width != nil { advance_width^ = i32(ttSHORT(info.data[u32(info.hmtx)+4*(num_of_long_hor_metrics-1):])) } if left_side_bearing != nil { left_side_bearing^ = i32(ttSHORT(info.data[u32(info.hmtx)+4*num_of_long_hor_metrics+2*u32(glyph_index-i32(num_of_long_hor_metrics)):])) } } } // Get horizontal metrics adjusted for variation coordinates. get_glyph_hmetrics_var :: proc(info: ^Font_Info, glyph_index: i32, coords: ^Var_Coords, advance_width: ^i32, left_side_bearing: ^i32) { get_glyph_hmetrics(info, glyph_index, advance_width, left_side_bearing) hvar := find_table(info.data, u32(info.fontstart), "HVAR") if hvar == 0 || advance_width == nil do return data := info.data[hvar:] version := ttULONG(data) if version != 0x00010000 do return var_store_off := u32(ttULONG(data[4:])) adv_map_off := u32(ttULONG(data[8:])) outer_idx: u16 = 0 inner_idx: u16 = u16(glyph_index) if adv_map_off != 0 { map_data := data[adv_map_off:] map_format := map_data[0] entry_format := map_data[1] map_count: i32 if map_format == 0 { map_count = i32(ttUSHORT(map_data[2:])) map_data = map_data[4:] } else { map_count = i32(ttULONG(map_data[2:])) map_data = map_data[6:] } inner_bits := u16((entry_format & 0x0F) + 1) entry_size := i32(((entry_format >> 4) & 3) + 1) inner_mask := u16((1 << inner_bits) - 1) idx := min(i32(glyph_index), map_count - 1) if idx >= 0 { entry: u32 e := map_data[u32(idx) * u32(entry_size):] switch entry_size { case 1: entry = u32(e[0]) case 2: entry = u32(ttUSHORT(e)) case 3: entry = (u32(e[0]) << 16) | u32(ttUSHORT(e[1:])) case 4: entry = ttULONG(e) } outer_idx = u16(entry >> inner_bits) inner_idx = u16(entry) & inner_mask } } delta := parse_item_var_delta(data[var_store_off:], outer_idx, inner_idx, coords) advance_width^ += i32(delta + 0.5) } @(private) parse_item_var_delta :: proc(store: [^]u8, outer_idx: u16, inner_idx: u16, coords: ^Var_Coords) -> f32 { format := ttUSHORT(store) if format != 1 do return 0 region_list_off := u32(ttULONG(store[2:])) data_count := i32(ttUSHORT(store[6:])) if i32(outer_idx) >= data_count do return 0 data_off := u32(ttULONG(store[8 + u32(outer_idx) * 4:])) ivd := store[data_off:] item_count := i32(ttUSHORT(ivd)) word_delta_count_raw := ttUSHORT(ivd[2:]) region_index_count := i32(ttUSHORT(ivd[4:])) if i32(inner_idx) >= item_count do return 0 word_delta_count := i32(word_delta_count_raw & 0x7FFF) long_words := (word_delta_count_raw & 0x8000) != 0 region_indices := ivd[6:] regions := store[region_list_off:] axis_count := i32(ttUSHORT(regions)) delta_data := ivd[6 + u32(region_index_count) * 2:] row_size: i32 if long_words { row_size = word_delta_count * 4 + (region_index_count - word_delta_count) * 2 } else { row_size = word_delta_count * 2 + (region_index_count - word_delta_count) } row := delta_data[u32(inner_idx) * u32(row_size):] result: f32 = 0 for ri in 0.. end_coord { scalar = 0; break } if v < peak_coord { if peak_coord != start_coord do scalar *= (v - start_coord) / (peak_coord - start_coord) } else { if peak_coord != end_coord do scalar *= (end_coord - v) / (end_coord - peak_coord) } } result += delta_val * scalar } return result } @(private) get_glyf_offset :: proc(info: ^Font_Info, glyph_index: i32) -> i32 { if glyph_index >= info.num_glyphs do return -1 if info.index_to_loc_format >= 2 do return -1 g1, g2: i32 if info.index_to_loc_format == 0 { g1 = info.glyf + i32(ttUSHORT(info.data[u32(info.loca)+u32(glyph_index)*2:])) * 2 g2 = info.glyf + i32(ttUSHORT(info.data[u32(info.loca)+u32(glyph_index)*2+2:])) * 2 } else { g1 = info.glyf + i32(ttULONG(info.data[u32(info.loca)+u32(glyph_index)*4:])) g2 = info.glyf + i32(ttULONG(info.data[u32(info.loca)+u32(glyph_index)*4+4:])) } return -1 if g1 == g2 else g1 }