started working on ttf font reading
authorBrendan Hansen <brendan.f.hansen@gmail.com>
Sun, 18 Apr 2021 16:12:48 +0000 (11:12 -0500)
committerBrendan Hansen <brendan.f.hansen@gmail.com>
Sun, 18 Apr 2021 16:12:48 +0000 (11:12 -0500)
build.sh
lib/events.onyx
lib/imgui.onyx
lib/ttf/types.onyx [new file with mode: 0644]
test/basic.onyx

index 93ea467187cadc39c150569805aac7cea8768c37..e3ff4ddea8dc1a595f217b789d48914271be4251 100755 (executable)
--- a/build.sh
+++ b/build.sh
@@ -1,3 +1,3 @@
 #!/bin/sh
 
-onyx -r js -V --use-post-mvp-features test/basic.onyx -o site/imgui.wasm
+onyx -r js --use-post-mvp-features -V test/basic.onyx -o site/imgui.wasm
index 8ac60255fc7dc6343321755e23c068891c013dad..138b6f3b4bdfd41062a19adb41d3264b2c90354d 100644 (file)
@@ -79,17 +79,22 @@ init :: () {
     event_setup(^event_storage, sizeof Event);
 }
 
-poll :: (ev: ^Event) -> bool {
-    if event_storage.event_count == 0 do return false;
-
-    *ev = event_storage.event_buffer[0];
-    for i: 0 .. Num_Buffered_Events - 2 {
-        event_storage.event_buffer[i] = event_storage.event_buffer[i + 1];
+consume :: () -> Iterator(Event) {
+    next :: (_: rawptr) -> (Event, bool) {
+        use package core.intrinsics.onyx { __zero_value }
+        if event_storage.event_count == 0 do return __zero_value(Event), false;
+
+        event := event_storage.event_buffer[0];
+
+        for i: 0 .. Num_Buffered_Events - 2 {
+            event_storage.event_buffer[i] = event_storage.event_buffer[i + 1];
+        }
+        event_storage.event_count -= 1;
+        
+        return event, true;
     }
 
-    event_storage.event_count -= 1;
-
-    return true;
+    return .{ null, next };
 }
 
 /* Private members */
index 385f9b1d33fdd3319b3ed2e0ace5291f855fecdd..34c1d76ec2e274a8094e8d4d33a469014163f871 100644 (file)
@@ -18,4 +18,4 @@ package imgui
 
 #load "lib/gl/gl_utils"
 #load "lib/immediate_renderer"
-
+#load "lib/ttf/types"
diff --git a/lib/ttf/types.onyx b/lib/ttf/types.onyx
new file mode 100644 (file)
index 0000000..caa8252
--- /dev/null
@@ -0,0 +1,567 @@
+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;
+}
+
index cf46f0076aaa5f3de450fc8f83da9c8bfcd2851c..b1e51bd7540bc46a60559ac4caf5e533ed37a41c 100644 (file)
@@ -30,8 +30,7 @@ squares : [..] struct {
 poll_events :: () {
     use events.DomEventKind;
 
-    event: events.Event;
-    while events.poll(^event) do switch event.kind {
+    for event: events.consume() do switch event.kind {
         case Resize {
             println("The window was resized!");
 
@@ -73,8 +72,8 @@ draw :: () {
     y := math.sin(t);
 
     immediate_vertex(.{ 0, 0 }, color=.{ 1, 1, 1 });
-    immediate_vertex(.{ x, y });
-    immediate_vertex(.{ -y, x });
+    immediate_vertex(.{ x, y }, color=.{ 1-x, 1-y, 1 });
+    immediate_vertex(.{ -y, x }, color=.{ 1-y, 1-x, 1 });
 
     if cast(u32) t % 2 == 0 {
         immediate_vertex(.{ 0, 0 },     color=.{ 1, 0, 0 });