From: Brendan Hansen Date: Tue, 20 Apr 2021 02:31:54 +0000 (-0500) Subject: some progress to bitmap rendering TTF X-Git-Url: https://git.brendanfh.com/?a=commitdiff_plain;ds=sidebyside;p=onyx-imgui.git some progress to bitmap rendering TTF --- diff --git a/lib/bitmap/bitmap.onyx b/lib/bitmap/bitmap.onyx new file mode 100644 index 0000000..5cd759e --- /dev/null +++ b/lib/bitmap/bitmap.onyx @@ -0,0 +1,321 @@ +package imgui.bitmap + +use package core +#private_file ttf :: package imgui.ttf + +Point :: struct { + x, y: f32; +} + + +Bitmap :: struct { + Style :: enum { + Outlined_Aliased; + Outlined_Border; + Filled; + Raw; + } + + allocator : Allocator; + ttf : ^ttf.TrueTypeFont; + buffer : [] u8; + + width : i32 = 1; + height : i32 = 1; + + bytes_per_pixel : u32 = 4; + background_color : u32 = 0xFFFFFF00; + foreground_color : u32 = 0x000000FF; + + scale : f32 = 1.0f; + scale_x : f32 = 1.0f; + scale_y : f32 = 1.0f; + + space_width : f32 = 1.0f; + space_multiplier : f32 = 0.0f; + + transformation_matrix: [9] f32 = f32.[1,0,0,0,1,0,0,0,0]; + character_matrix: [9] f32 = f32.[1,0,0,0,1,0,0,0,0]; + + style: Style = Style.Filled; + justify := false; + justify_fill_ratio : f32 = 0.5; + + filler : [..][..] i32; + + use_font_metrics: bool = false; +} + +bitmap_clear :: (use bitmap: ^Bitmap) { + size := width * width * bytes_per_pixel; + memory.set(buffer.data, 0, size); +} + +bitmap_transform_text :: (use bitmap: ^Bitmap, p: Point) -> (i32, i32) { + return + cast(i32) (p.x * transformation_matrix[0] + p.y * transformation_matrix[1] + transformation_matrix[4]), + cast(i32) (p.x * transformation_matrix[2] + p.y * transformation_matrix[3] + transformation_matrix[5]); +} + +bitmap_transform_character :: (use bitmap: ^Bitmap, p: Point) -> (i32, i32) { + return + cast(i32) (p.x * character_matrix[0] + p.y * character_matrix[1] + character_matrix[4]), + cast(i32) (p.x * character_matrix[2] + p.y * character_matrix[3] + character_matrix[5]); +} + +bitmap_set_pos :: (use bitmap: ^Bitmap, x: f32, y: f32) { + transformation_matrix[6] = x; + transformation_matrix[7] = y; +} + +bitmap_set_rotation :: (use bitmap: ^Bitmap, angle: f32) { + transformation_matrix[0] = math.cos(angle); + transformation_matrix[1] = -math.sin(angle); + transformation_matrix[2] = math.sin(angle); + transformation_matrix[3] = math.cos(angle); +} + +bitmap_init_filler :: (use bitmap: ^Bitmap) { + h := height - filler.count; + if h < 1 do return; + + for _: h { + new_row := array.make(i32, allocator=allocator); + array.push(^filler, new_row); + } +} + +bitmap_clear_filler :: (use bitmap: ^Bitmap) { + for i: height { + array.clear(^filler[i]); + } +} + +bitmap_exec_filler :: (use bitmap: ^Bitmap) { + cmp_asc :: (x: $T, y: T) -> i32 do return cast(i32) (x - y); + + for y: height { + if filler[y].count > 0 { + array.sort(^filler[y], cmp_asc); + + if filler[y].count % 2 != 0 { + // Need to have an even number of lines + continue; + } + + index := 0; + while index < filler[y].count { + defer index += 2; + + startx := filler[y][index] + 1; + endx := filler[y][index + 1]; + + if startx >= endx do continue; + + for x: startx .. endx + 1 { // @Check: +1 may not be necessary + bitmap_plot(bitmap, x, y, foreground_color); + } + } + } + } +} + +// This function should be inlined at some point because it is very small +// and cheap. +bitmap_plot :: (use bitmap: ^Bitmap, x: i32, y: i32, color: u32) -> bool { + if x < 0 || x >= width || y < 0 || y >= height do return false; + + index := (x + y * width) * bytes_per_pixel; + buffer[index] = cast(u8) (color & 0xFF); + + return true; +} + +bitmap_fline :: (use bitmap: ^Bitmap, ix0: i32, iy0: i32, ix1: i32, iy1: i32, color: u32) { + + if (ix0 < 0 && ix1 < 0) || (ix0 > width && ix1 > width) do return; + + x0 := cast(f32) ix0; + y0 := cast(f32) iy0; + x1 := cast(f32) ix1; + y1 := cast(f32) iy1; + + if y1 < y0 { + x0, x1 = x1, x0; + y0, y1 = y1, y0; + } + + dx := x1 - x0; + dy := y1 - y0; + + if dy == 0 { + if iy0 >= 0 && iy0 < filler.count { + if ix0 <= ix1 { + array.push(^filler[iy0], ix0); + array.push(^filler[iy0], ix1); + } else { + array.push(^filler[iy0], ix1); + array.push(^filler[iy0], ix0); + } + } + + return; + } + + n := dx / dy; + for y: cast(i32) (dy + 0.5) { + yd := cast(i32) (y + iy0); + x := n * ~~y + x0; + + if x > ~~width || yd >= ~~filler.count do break; + + if yd >= 0 && yd < filler.count { + array.push(^filler[yd], cast(i32) (x + 0.5)); + } + } +} + +bitmap_aline :: (use bitmap: ^Bitmap, ix0: i32, iy0: i32, ix1: i32, iy1: i32, color: u32) { + x0 := cast(f32) ix0; + y0 := cast(f32) iy0; + x1 := cast(f32) ix1; + y1 := cast(f32) iy1; + + dx := x1 - x0; + dy := y1 - y0; + + dist := cast(f32) 0.4; + if math.abs(dx) > math.abs(dy) { + if x1 > x0 { + x0, x1 = x1, x0; + y0, y1 = y1, y0; + } + + dx := x1 - x0; + dy := y1 - y0; + + x0 += 0.5; + y0 += 0.5; + + m := dy / dx; + x := x0; + while x <= x1 + 0.5 { + y := m * (x - x0) + y0; + e := 1 - math.abs(y - 0.5 - math.floor(y)); + bitmap_plot(bitmap, ~~x, ~~y, foreground_color); // Needs to be aliased! + + ys1 := y + dist; + if cast(i32) ys1 != cast(i32) y { + v1 := math.abs(ys1 - y) / dist * (1 - e); + bitmap_plot(bitmap, ~~x, ~~ys1, foreground_color); // Needs to be aliased! + } + + ys2 := y - dist; + if cast(i32) ys2 != cast(i32) y { + v2 := math.abs(y - ys2) / dist * (1 - e); + bitmap_plot(bitmap, ~~x, ~~ys2, foreground_color); // Needs to be aliased! + } + + x += 1; + } + + } else { + if y1 > y0 { + x0, x1 = x1, x0; + y0, y1 = y1, y0; + } + + dx := x1 - x0; + dy := y1 - y0; + + x0 += 0.5; + y0 += 0.5; + + n := dx / dy; + y := y0; + while y <= y1 + 0.5 { + x := n * (y - x0) + y0; + e := 1 - math.abs(x - 0.5 - math.floor(x)); + bitmap_plot(bitmap, ~~x, ~~y, foreground_color); // Needs to be aliased! + + xs1 := x + dist; + if cast(i32) xs1 != cast(i32) x { + v1 := math.abs(xs1 - x) / dist * (1 - e); + bitmap_plot(bitmap, ~~xs1, ~~y, foreground_color); // Needs to be aliased! + } + + xs2 := x - dist; + if cast(i32) xs1 != cast(i32) x { + v1 := math.abs(x - xs2) / dist * (1 - e); + bitmap_plot(bitmap, ~~xs2, ~~y, foreground_color); // Needs to be aliased! + } + + y += 1.0; + } + } +} + +bitmap_line :: (use bitmap: ^Bitmap, ix0: i32, iy0: i32, ix1: i32, iy1: i32, color: u32) { + use Bitmap.Style; + switch style { + case Outlined_Aliased { + bitmap_aline(bitmap, ix0, iy0, ix1, iy1, color); + return; + } + case Filled { + bitmap_aline(bitmap, ix0, iy0, ix1, iy1, color); + bitmap_fline(bitmap, ix0, iy0, ix1, iy1, color); + return; + } + case Raw { + bitmap_fline(bitmap, ix0, iy0, ix1, iy1, color); + return; + } + } + + assert(false, "Unhandled bitmap_line case"); +} + +bitmap_box :: (use bitmap: ^Bitmap, ix0: i32, iy0: i32, ix1: i32, iy1: i32, color: u32) { + bitmap_line(bitmap, ix0, iy0, ix1, iy0, color); + bitmap_line(bitmap, ix1, iy0, ix1, iy1, color); + bitmap_line(bitmap, ix0, iy1, ix1, iy1, color); + bitmap_line(bitmap, ix0, iy0, ix0, iy1, color); +} + +bitmap_quadratic :: (use bitmap: ^Bitmap, ix0: i32, iy0: i32, ix1: i32, iy1: i32, icx: i32, icy: i32, color: u32) { + x0 := cast(f64) ix0; + x1 := cast(f64) ix1; + y0 := cast(f64) iy0; + y1 := cast(f64) iy1; + cx := cast(f64) icx; + cy := cast(f64) icy; + + division := cast(f64) 1; + dx := math.abs(x0 - x1); + dy := math.abs(y0 - y1); + + if dx <= 2 || dy <= 2 { + bitmap_line(bitmap, ~~x0, ~~y0, ~~x1, ~~y1, color); + return; + } + + division = 1 / cast(f64) math.max(dx, dy); + + x_old := x0; + y_old := y0; + t := cast(f64) 0; + + while t <= (1 + division / 2) { + s := 1 - t; + x := s * s * x0 + 2.0 * s * t * cx + t * t * x1; + y := s * s * y0 + 2.0 * s * t * cy + t * t * y1; + xi := cast(i32) (x + 0.5); + yi := cast(i32) (y + 0.5); + + bitmap_line(bitmap, ~~x_old, ~~y_old, ~~xi, ~~yi, color); + x_old = ~~ xi; + y_old = ~~ yi; + t += division; + } +} diff --git a/lib/imgui.onyx b/lib/imgui.onyx index 34c1d76..e93467d 100644 --- a/lib/imgui.onyx +++ b/lib/imgui.onyx @@ -19,3 +19,4 @@ package imgui #load "lib/gl/gl_utils" #load "lib/immediate_renderer" #load "lib/ttf/types" +#load "lib/bitmap/bitmap" diff --git a/lib/ttf/types.onyx b/lib/ttf/types.onyx index 340973b..14a56b6 100644 --- a/lib/ttf/types.onyx +++ b/lib/ttf/types.onyx @@ -63,6 +63,7 @@ ttf_create :: (ttf_data: [] u8, allocator := context.allocator) -> ^TrueTypeFont ttf_read_head_table(ttf); ttf_read_cmap_table(ttf); ttf_read_hhea_table(ttf); + ttf_read_kern_table(ttf); return ttf; } @@ -87,8 +88,17 @@ TTIndexToLocFormat :: enum (i16) { } TTGlyph :: struct { + Type :: enum { + Unknown :: 0x00; + Simple :: 0x01; + Compound :: 0x02; + } + + type: Type = Type.Unknown; allocator: Allocator; + valid_glyph := false; + contour_count : i16; x_min : i16; x_max : i16; @@ -97,12 +107,21 @@ TTGlyph :: struct { points : [..] TTGlyphPoint; contour_ends : [..] u16; + + components: [] GlyphComponent; + + GlyphComponent :: struct { + glyph_index : u16; + dest_point_index : i16; + src_point_index : i16; + matrix : [6] f32; + } } TTGlyphPoint :: struct { on_curve : bool; - x : i16 = ~~0; - y : i16 = ~~0; + x : i16 = 0; + y : i16 = 0; } ttf_read_offset_table :: (use ttf: ^TrueTypeFont) { @@ -116,7 +135,6 @@ ttf_read_offset_table :: (use ttf: ^TrueTypeFont) { for i: num_tables { tag := read_string(^reader, 4); - println(tag); table_info : TTTableInfo; table_info.checksum = read_u32(^reader); @@ -159,7 +177,7 @@ ttf_read_head_table :: (use ttf: ^TrueTypeFont) { index_to_loc_format = cast(TTIndexToLocFormat) read_i16(^reader); glyph_data_format = read_i16(^reader); - assert(magic_number == 0x54053cf5, "TTF Magic Number wrong!"); + assert(magic_number == 0x5f0f3cf5, "TTF Magic Number wrong!"); } ttf_lookup_glyph_offset :: (use ttf: ^TrueTypeFont, glyph_index: i32) -> u32 { @@ -215,6 +233,7 @@ ttf_read_glyph :: (use ttf: ^TrueTypeFont, glyph_index: i32, glyph_allocator := ttf_read_simple_glyph(ttf, glyph); } + glyph.valid_glyph = true; return glyph; } @@ -307,6 +326,103 @@ ttf_read_simple_glyph :: (use ttf: ^TrueTypeFont, glyph: ^TTGlyph) { } } +#private +TFKC_Flags :: enum #flags { + arg_1_and_2_are_words; + args_are_xy_values; + round_xy_to_grid; + we_have_a_scale; + _; // reserved + more_components; + we_have_an_x_and_y_scale; + we_have_a_two_by_two; + we_have_instructions; + use_my_metrics; + overlap_component; +} + +ttf_read_compound_glyph :: (use ttf: ^TrueTypeFont, glyph: ^TTGlyph) { + use package core.io.binary + + glyph.type = TTGlyph.Type.Compound; + + component : TTGlyph.GlyphComponent; + flags := TFKC_Flags.more_components; + + while (flags & TFKC_Flags.more_components) != ~~ 0 { + arg1, arg2 : i16; + + flags = ~~ read_u16(^reader); + + component.glyph_index = read_u16(^reader); + + if (flags & TFKC_Flags.arg_1_and_2_are_words) != ~~ 0 { + arg1 = read_i16(^reader); + arg2 = read_i16(^reader); + } else { + arg1 = cast(i16) read_u8(^reader); + arg2 = cast(i16) read_u8(^reader); + } + + if (flags & TFKC_Flags.args_are_xy_values) != ~~ 0 { + component.matrix[4] = ~~ cast(i32) arg1; + component.matrix[5] = ~~ cast(i32) arg2; + } else { + component.dest_point_index = arg1; + component.src_point_index = arg2; + } + + if (flags & TFKC_Flags.we_have_a_scale) != ~~ 0 { + component.matrix[0] = read_2dot14(^reader); + component.matrix[3] = component.matrix[0]; + } + elseif (flags & TFKC_Flags.we_have_an_x_and_y_scale) != ~~ 0 { + component.matrix[0] = read_2dot14(^reader); + component.matrix[3] = read_2dot14(^reader); + } + elseif (flags & TFKC_Flags.we_have_a_two_by_two) != ~~ 0 { + component.matrix[0] = read_2dot14(^reader); + component.matrix[2] = read_2dot14(^reader); + component.matrix[3] = read_2dot14(^reader); + component.matrix[4] = read_2dot14(^reader); + } + + old_pos := tell(^reader); + + simple_glyph := ttf_read_glyph(ttf, ~~ component.glyph_index, ttf.allocator); + if simple_glyph.valid_glyph { + point_offset := glyph.points.count; + for i: simple_glyph.contour_ends.count { + array.push(^glyph.contour_ends, cast(u16) (simple_glyph.contour_ends[i] + ~~ point_offset)); + } + + for p: simple_glyph.points { + px := cast(f32) cast(i32) p.x; + py := cast(f32) cast(i32) p.y; + + x := component.matrix[0] * px + component.matrix[1] * py + component.matrix[4]; + y := component.matrix[2] * px + component.matrix[3] * py + component.matrix[5]; + + array.push(^glyph.points, .{ + x = ~~ cast(i32) x, + y = ~~ cast(i32) y, + on_curve = p.on_curve, + }); + } + } + + seek(^reader, old_pos); + } + + glyph.contour_count = cast(i16) glyph.contour_ends.count; + + if (flags & TFKC_Flags.we_have_instructions) != ~~ 0 { + seek(^reader, tell(^reader) + ~~ read_u16(^reader)); + } + + glyph.valid_glyph = true; +} + ttf_glyph_count :: (use ttf: ^TrueTypeFont) -> u32 { use package core.io.binary @@ -519,7 +635,7 @@ TTKern0Table :: struct { } get :: (use kern: ^TTKern0Table, glyph_index: i32) -> (i32, i32) { - x := 0 + x := 0; if old_index >= 0 { ch := ((cast(u32) old_index & 0xFFFF) << 16) | (cast(u32) glyph_index & 0xFFFF); @@ -592,6 +708,20 @@ ttf_read_kern0 :: (use ttf: ^TrueTypeFont, vertical: bool, cross: bool) -> TTKer return kt; } +ttf_next_kern :: (use ttf: ^TrueTypeFont, glyph_index: i32) -> (i32, i32) { + // BUG: I cannot do this here because x and y do not + // automatically become a 'i32' type. + // x, y := 0, 0; + x := 0; + y := 0; + for i: 0 .. kern.count { + tmp_x, tmp_y := kern[i]->get(glyph_index); + x += tmp_x; + y += tmp_y; + } + return x, y; +} + TTHorizontalMetrics :: struct { advance_width : u16; left_side_bearing : i16; diff --git a/test/basic.onyx b/test/basic.onyx index 6b87b5d..128f981 100644 --- a/test/basic.onyx +++ b/test/basic.onyx @@ -6,6 +6,7 @@ use package core #private_file imgui :: package imgui #private_file events :: package imgui.events #private_file gl :: package gl +#private_file bm :: package imgui.bitmap init :: () { events.init();