--- /dev/null
+package imgui.ttf
+
+use package core
+
+TrueTypeFont :: struct {
+ allocator : Allocator;
+ reader: io.binary.BinaryReader;
+
+ scalar_type : u32;
+ search_range : u16;
+ entry_selector : u16;
+ range_shift : u16;
+
+ table_map : map.Map(str, TTTableInfo);
+ char_maps : [..] TTCmap;
+
+ version : u32;
+ font_revision : u32;
+ checksum_adjustment : u32;
+ magic_number : u32;
+ flags : u16;
+ units_per_em : u16;
+ created : u64;
+ modified : u64;
+ x_min : i16;
+ x_max : i16;
+ y_min : i16;
+ y_max : i16;
+ mac_style : u16;
+ lowest_rec_ppem : u16;
+ font_direction_hint : i16;
+ index_to_loc_format : TTIndexToLocFormat;
+ glyph_data_format : i16;
+ kern : [..] TTKern0Table;
+
+ hhea : struct {
+ version : u32;
+ ascent : i16;
+ descent : i16;
+ line_gap : i16;
+ advance_width_max : u16;
+ min_left_side_bearing : i16;
+ min_right_side_bearing : i16;
+ x_max_extent : i16;
+ caret_slope_rise : i16;
+ caret_slope_run : i16;
+ caret_offset : i16;
+ metric_data_format : i16;
+ num_of_long_hor_metrics : u16;
+ };
+}
+
+ttf_create :: (ttf_data: [] u8, allocator := context.allocator) -> ^TrueTypeFont {
+ ttf := new(TrueTypeFont, allocator=allocator);
+ ttf.allocator = allocator;
+
+ ttf.reader = io.binary.create_reader(ttf_data);
+
+ map.init(^ttf.table_map, .{});
+ array.init(^ttf.char_maps, allocator=allocator);
+ array.init(^ttf.kern, allocator=allocator);
+ ttf_read_offset_table(ttf);
+ ttf_read_head_table(ttf);
+ ttf_read_cmap_table(ttf);
+ ttf_read_hhea_table(ttf);
+
+ return ttf;
+}
+
+ttf_free :: (ttf: ^TrueTypeFont) {
+ array.free(^ttf.char_maps);
+ array.free(^ttf.kern);
+ map.free(^ttf.table_map);
+
+ raw_free(ttf.allocator, ttf);
+}
+
+TTTableInfo :: struct {
+ checksum : u32 = 0;
+ offset : u32 = 0;
+ length : u32 = 0;
+}
+
+TTIndexToLocFormat :: enum (i16) {
+ Short :: 0x00;
+ Long :: 0x01;
+}
+
+TTGlyph :: struct {
+ allocator: Allocator;
+
+ contour_count : i16;
+ x_min : i16;
+ x_max : i16;
+ y_min : i16;
+ y_max : i16;
+
+ points : [..] TTGlyphPoint;
+ contour_ends : [..] u16;
+}
+
+TTGlyphPoint :: struct {
+ on_curve : bool;
+ x : i16 = ~~0;
+ y : i16 = ~~0;
+}
+
+ttf_read_offset_table :: (use ttf: ^TrueTypeFont) {
+ use package core.io.binary
+
+ scalar_type = read_u32(^reader);
+ num_tables := cast(u32) read_u16(^reader);
+ search_range = read_u16(^reader);
+ entry_selector = read_u16(^reader);
+ range_shift = read_u16(^reader);
+
+ for i: num_tables {
+ tag := read_string(^reader, 4);
+ println(tag);
+
+ table_info : TTTableInfo;
+ table_info.checksum = read_u32(^reader);
+ table_info.offset = read_u32(^reader);
+ table_info.length = read_u32(^reader);
+
+ map.put(^table_map, tag, table_info);
+
+ // CLEANUP: There should be a "!=" for strings.
+ if !(tag == "head") {
+ csum := ttf_calc_table_checksum(^reader, table_info.offset, table_info.length);
+ if table_info.checksum != csum {
+ printf("WARNING: Checksum for table '%s' did not match.\n", tag);
+ }
+ }
+ }
+}
+
+ttf_read_head_table :: (use ttf: ^TrueTypeFont) {
+ use package core.io.binary
+
+ head_table_info := map.get(^table_map, "head");
+ seek(^reader, head_table_info.offset);
+
+ version = read_u32(^reader);
+ font_revision = read_u32(^reader);
+ checksum_adjustment = read_u32(^reader);
+ magic_number = read_u32(^reader);
+ flags = read_u16(^reader);
+ units_per_em = read_u16(^reader);
+ created = read_date(^reader);
+ modified = read_date(^reader);
+ x_min = read_fword(^reader);
+ y_min = read_fword(^reader);
+ x_max = read_fword(^reader);
+ y_max = read_fword(^reader);
+ mac_style = read_u16(^reader);
+ lowest_rec_ppem = read_u16(^reader);
+ font_direction_hint = read_i16(^reader);
+ index_to_loc_format = cast(TTIndexToLocFormat) read_i16(^reader);
+ glyph_data_format = read_i16(^reader);
+
+ assert(magic_number == 0x54053cf5, "TTF Magic Number wrong!");
+}
+
+ttf_lookup_glyph_offset :: (use ttf: ^TrueTypeFont, glyph_index: i32) -> u32 {
+ use package core.io.binary
+
+ loca_table_info := map.get(^table_map, "loca");
+ glyf_table_info := map.get(^table_map, "glyf");
+
+ old: u32;
+ defer seek(^reader, old);
+
+ switch index_to_loc_format {
+ case TTIndexToLocFormat.Long {
+ old = seek(^reader, loca_table_info.offset + glyph_index * 4);
+ return read_u32(^reader) + glyf_table_info.offset;
+ }
+
+ case #default {
+ old = seek(^reader, loca_table_info.offset + glyph_index * 2);
+ return 2 * cast(u32) read_u16(^reader) + glyf_table_info.offset;
+ }
+ }
+
+ return 0xffffffff;
+}
+
+// Result is expected to be freed
+ttf_read_glyph :: (use ttf: ^TrueTypeFont, glyph_index: i32, glyph_allocator := context.allocator) -> ^TTGlyph {
+ use package core.io.binary
+
+ offset := ttf_lookup_glyph_offset(ttf, glyph_index);
+
+ glyf_table_info := map.get(^table_map, "glyf");
+
+ if offset >= glyf_table_info.offset + glyf_table_info.length do return null;
+
+ seek(^reader, offset);
+
+ glyph := new(TTGlyph, glyph_allocator);
+ glyph.allocator = glyph_allocator;
+ glyph.contour_count = read_i16(^reader);
+ glyph.x_min = read_fword(^reader);
+ glyph.y_min = read_fword(^reader);
+ glyph.x_max = read_fword(^reader);
+ glyph.y_max = read_fword(^reader);
+
+ if glyph.contour_count < 0 { raw_free(glyph_allocator, glyph); return null; }
+ if glyph.contour_count == ~~ -1 {
+ // Compound glyph
+ return null;
+ } else {
+ // Simple glyph
+ ttf_read_simple_glyph(ttf, glyph);
+ }
+
+ return glyph;
+}
+
+ttf_glyph_destroy :: (glyph: ^TTGlyph) {
+ array.free(^glyph.contour_ends);
+ array.free(^glyph.points);
+ raw_free(glyph.allocator, glyph);
+}
+
+#private_file
+TTGlyphFlags :: enum #flags {
+ On_Curve :: 0x01;
+ X_Is_Byte :: 0x2;
+ Y_Is_Byte :: 0x4;
+ Repeat :: 0x8;
+ X_Delta :: 0x10;
+ Y_Delta :: 0x20;
+}
+
+ttf_read_simple_glyph :: (use ttf: ^TrueTypeFont, glyph: ^TTGlyph) {
+ use package core.io.binary
+
+ if glyph.contour_count == 0 do return;
+
+ array.init(^glyph.contour_ends, ~~glyph.contour_count);
+ array.init(^glyph.points);
+
+ for _: 0 .. ~~glyph.contour_count {
+ array.push(^glyph.contour_ends, read_u16(^reader));
+ }
+
+ seek(^reader, ~~read_u16(^reader) + tell(^reader));
+
+ num_points := array.fold(^glyph.contour_ends, cast(u16) 0, math.max_poly) + 1;
+
+ flags : [..] TTGlyphFlags;
+ array.init(^flags);
+ defer array.free(^flags);
+
+ while i := 0; i < ~~num_points {
+ defer i += 1;
+
+ flag := cast(TTGlyphFlags) read_u8(^reader);
+ array.push(^flags, flag);
+ array.push(^glyph.points, .{ on_curve = (flag & TTGlyphFlags.On_Curve) != ~~0 });
+
+ if (flag & TTGlyphFlags.Repeat) != ~~0 {
+ rep_count := read_u8(^reader);
+ i += ~~ rep_count;
+
+ for _: 0 .. ~~rep_count {
+ array.push(^flags, flag);
+ array.push(^glyph.points, .{ on_curve = (flag & TTGlyphFlags.On_Curve) != ~~0 });
+ }
+ }
+ }
+
+ value: i16 = 0;
+ for i: 0 .. ~~num_points {
+ flag := flags[i];
+
+ if (flag & TTGlyphFlags.X_Is_Byte) != ~~0 {
+ if (flag & TTGlyphFlags.X_Delta) != ~~0 {
+ value += ~~read_u8(^reader);
+ } else {
+ value -= ~~read_u8(^reader);
+ }
+ } elseif (flag & TTGlyphFlags.X_Delta) == ~~0 {
+ value += read_i16(^reader);
+ }
+
+ glyph.points[i].x = value;
+ }
+
+ value = 0;
+ for i: 0 .. ~~num_points {
+ flag := flags[i];
+
+ if (flag & TTGlyphFlags.Y_Is_Byte) != ~~0 {
+ if (flag & TTGlyphFlags.Y_Delta) != ~~0 {
+ value += ~~read_u8(^reader);
+ } else {
+ value -= ~~read_u8(^reader);
+ }
+ } elseif (flag & TTGlyphFlags.Y_Delta) == ~~0 {
+ value += read_i16(^reader);
+ }
+
+ glyph.points[i].y = value;
+ }
+}
+
+ttf_glyph_count :: (use ttf: ^TrueTypeFont) -> u32 {
+ use package core.io.binary
+
+ maxp_table_info := map.get(^table_map, "maxp");
+ old := seek(^reader, maxp_table_info.offset + 4);
+ defer seek(^reader, old);
+
+ return ~~read_u16(^reader);
+}
+
+ttf_read_cmap_table :: (use ttf: ^TrueTypeFont) {
+ use package core.io.binary
+
+ cmap_table_info := map.get(^table_map, "cmap");
+ seek(^reader, cmap_table_info.offset);
+
+ version := read_u16(^reader);
+ num_subtables := read_u16(^reader);
+
+ for i: 0 .. ~~num_subtables {
+ platform_id := read_u16(^reader);
+ platform_specific_id := read_u16(^reader);
+ offset := read_u32(^reader);
+
+ // Microsoft Unicode, BMP only
+ if platform_id == 3 && platform_specific_id <= 1 {
+ ttf_read_cmap(ttf, offset + cmap_table_info.offset);
+ }
+ }
+}
+
+TTCmapFormat :: enum (u16) {
+ Simple :: 0x00;
+ Segmented :: 0x04;
+}
+
+TTCmapBase :: struct { format : TTCmapFormat; }
+TTCmap0 :: struct {
+ use base: TTCmapBase;
+
+ glyph_indicies: [] u8;
+}
+TTCmap4 :: struct {
+ use base: TTCmapBase;
+
+ seg_count : u16;
+ search_range : u16;
+ entry_selector : u16;
+ range_shift : u16;
+
+ segments : [..] TTSegment;
+ cache : map.Map(i32, i32);
+}
+
+TTSegment :: struct {
+ start_code : u16;
+ end_code : u16;
+ id_delta : u16;
+ id_range_offset : u16;
+}
+
+TTCmap :: struct #union {
+ use base: TTCmapBase;
+ cmap0: TTCmap0;
+ cmap4: TTCmap4;
+}
+
+ttf_read_cmap :: (use ttf: ^TrueTypeFont, offset: u32) {
+ use package core.io.binary
+
+ old := seek(^reader, offset);
+ defer seek(^reader, old);
+
+ format := read_u16(^reader);
+ length := read_u16(^reader);
+ lang := read_u16(^reader);
+
+ switch cast(i32) format {
+ case 0 do ttf_read_cmap0(ttf);
+ case 4 do ttf_read_cmap4(ttf);
+
+ case #default { printf("Unsupported cmap format: %d\n", cast(i32) format); }
+ }
+}
+
+ttf_read_cmap0 :: (use ttf: ^TrueTypeFont) {
+ use package core.io.binary
+
+ cmap : TTCmap;
+ cmap.cmap0.format = TTCmapFormat.Simple;
+
+ glyphs : [..] u8;
+ array.init(^glyphs, 256);
+ for i: 0 .. 256 do array.push(^glyphs, read_u8(^reader));
+
+ cmap.cmap0.glyph_indicies = array.to_slice(^glyphs);
+
+ array.push(^char_maps, cmap);
+}
+
+ttf_read_cmap4 :: (use ttf: ^TrueTypeFont) {
+ use package core.io.binary
+
+ cmap : TTCmap;
+ cmap.cmap4.format = TTCmapFormat.Segmented;
+ imap := ^cmap.cmap4;
+ map.init(^imap.cache);
+
+ imap.seg_count = read_u16(^reader) >> 1;
+ imap.search_range = read_u16(^reader);
+ imap.entry_selector = read_u16(^reader);
+ imap.range_shift = read_u16(^reader);
+
+ array.init(^imap.segments, ~~imap.seg_count);
+ imap.segments.count = cast(u32) imap.seg_count;
+
+ for ^seg: imap.segments do seg.end_code = read_u16(^reader);
+ read_u16(^reader); // Reserved and unused
+ for ^seg: imap.segments do seg.start_code = read_u16(^reader);
+ for ^seg: imap.segments do seg.id_delta = read_u16(^reader);
+ for ^seg: imap.segments {
+ seg.id_range_offset = read_u16(^reader);
+ if seg.id_range_offset != 0 do seg.id_range_offset += ~~(tell(^reader) - 2);
+ }
+
+ array.push(^char_maps, cmap);
+}
+
+ttf_lookup_glyph_by_char :: (use ttf: ^TrueTypeFont, charcode: u32) -> u32 {
+ potential_code := 0;
+
+ for ^cmap: char_maps {
+ switch cmap.format {
+ case TTCmapFormat.Simple do potential_code = ttf_lookup_in_cmap0(ttf, ~~cmap, charcode);
+ case TTCmapFormat.Segmented do potential_code = ttf_lookup_in_cmap4(ttf, ~~cmap, charcode);
+ }
+
+ if potential_code != 0 do return potential_code;
+ }
+
+ return potential_code;
+}
+
+#private_file
+ttf_lookup_in_cmap0 :: (use ttf: ^TrueTypeFont, cmap: ^TTCmap0, charcode: u32) -> u32 {
+ if charcode < 0 || charcode >= 256 do return 0;
+ return ~~cmap.glyph_indicies[charcode];
+}
+
+#private_file
+ttf_lookup_in_cmap4 :: (use ttf: ^TrueTypeFont, cmap: ^TTCmap4, charcode: u32) -> u32 {
+ use package core.io.binary
+
+ if map.has(^cmap.cache, charcode) do return map.get(^cmap.cache, charcode);
+
+ index := 0;
+ for ^seg: cmap.segments {
+ if ~~seg.start_code <= charcode && ~~charcode <= seg.end_code {
+ if seg.id_range_offset != 0 {
+ glyph_index_address := ~~seg.id_range_offset + 2 * (charcode - ~~seg.start_code);
+ seek(^reader, glyph_index_address);
+ index = cast(u32) read_u16(^reader);
+ } else {
+ index = (~~seg.id_delta + charcode) & 0xffff;
+ }
+
+ break;
+ }
+ }
+
+ map.put(^cmap.cache, charcode, index);
+
+ return index;
+}
+
+ttf_read_hhea_table :: (use ttf: ^TrueTypeFont) {
+ use package core.io.binary
+
+ hhea_table_info := map.get(^table_map, "hhea");
+ seek(^reader, hhea_table_info.offset);
+
+ hhea.version = read_u32(^reader);
+ hhea.ascent = read_fword(^reader);
+ hhea.descent = read_fword(^reader);
+ hhea.line_gap = read_fword(^reader);
+ hhea.advance_width_max = read_u16(^reader);
+ hhea.min_left_side_bearing = read_u16(^reader);
+ hhea.min_right_side_bearing = read_u16(^reader);
+ hhea.x_max_extent = read_fword(^reader);
+ hhea.caret_slope_rise = read_i16(^reader);
+ hhea.caret_slope_run = read_i16(^reader);
+ hhea.caret_offset = read_fword(^reader);
+ read_i16(^reader); // Reserved
+ read_i16(^reader); // Reserved
+ read_i16(^reader); // Reserved
+ read_i16(^reader); // Reserved
+ hhea.metric_data_format = read_i16(^reader);
+ hhea.num_of_long_hor_metrics = read_u16(^reader);
+}
+
+TTKern0Table :: struct {
+ swap : bool;
+ offset : u32;
+ n_pairs : i32;
+ kmap : map.Map(u32, i16);
+ old_index : i32 = -1;
+}
+
+
+TTHorizontalMetrics :: struct {
+ advance_width : u16;
+ left_side_bearing : i16;
+}
+
+ttf_lookup_horizontal_metrics :: (use ttf: ^TrueTypeFont, glyph_index: u32) -> TTHorizontalMetrics {
+ use package core.io.binary
+
+ hmtx_table_info := map.get(^table_map, "hmtx");
+ offset := hmtx_table_info.offset;
+
+ hmtx : TTHorizontalMetrics;
+
+ nmets := cast(u32) hhea.num_of_long_hor_metrics;
+
+ if glyph_index < nmets {
+ offset += glyph_index * 4;
+ old := seek(^reader, offset);
+ defer seek(^reader, old);
+
+ hmtx.advance_width = read_u16(^reader);
+ hmtx.left_side_bearing = read_i16(^reader);
+
+ } else {
+ old := seek(^reader, offset + (nmets - 1) * 4);
+ defer seek(^reader, old);
+
+ hmtx.advance_width = read_u16(^reader);
+ seek(^reader, offset + nmets * 4 + 2 * (glyph_index - nmets));
+ hmtx.left_side_bearing = read_i16(^reader);
+ }
+
+ return hmtx;
+}
+
+
+#private_file
+ttf_calc_table_checksum :: (reader: ^io.binary.BinaryReader, offset: u32, length: u32) -> u32 {
+ use package core.io.binary
+
+ old := seek(reader, offset);
+ defer seek(reader, old);
+
+ sum := 0;
+ nlongs := (length + 3) >> 2;
+ for i: 0 .. nlongs do sum += read_u32(reader);
+ return sum;
+}
+