From 7ed746ecd22a29227d7a5f852c91096d1abaa1b7 Mon Sep 17 00:00:00 2001 From: Brendan Hansen Date: Sun, 18 Apr 2021 11:12:48 -0500 Subject: [PATCH] started working on ttf font reading --- build.sh | 2 +- lib/events.onyx | 23 +- lib/imgui.onyx | 2 +- lib/ttf/types.onyx | 567 +++++++++++++++++++++++++++++++++++++++++++++ test/basic.onyx | 7 +- 5 files changed, 586 insertions(+), 15 deletions(-) create mode 100644 lib/ttf/types.onyx diff --git a/build.sh b/build.sh index 93ea467..e3ff4dd 100755 --- 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 diff --git a/lib/events.onyx b/lib/events.onyx index 8ac6025..138b6f3 100644 --- a/lib/events.onyx +++ b/lib/events.onyx @@ -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 */ diff --git a/lib/imgui.onyx b/lib/imgui.onyx index 385f9b1..34c1d76 100644 --- a/lib/imgui.onyx +++ b/lib/imgui.onyx @@ -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 index 0000000..caa8252 --- /dev/null +++ b/lib/ttf/types.onyx @@ -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; +} + diff --git a/test/basic.onyx b/test/basic.onyx index cf46f00..b1e51bd 100644 --- a/test/basic.onyx +++ b/test/basic.onyx @@ -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 }); -- 2.25.1