some progress to bitmap rendering TTF master
authorBrendan Hansen <brendan.f.hansen@gmail.com>
Tue, 20 Apr 2021 02:31:54 +0000 (21:31 -0500)
committerBrendan Hansen <brendan.f.hansen@gmail.com>
Tue, 20 Apr 2021 02:31:54 +0000 (21:31 -0500)
lib/bitmap/bitmap.onyx [new file with mode: 0644]
lib/imgui.onyx
lib/ttf/types.onyx
test/basic.onyx

diff --git a/lib/bitmap/bitmap.onyx b/lib/bitmap/bitmap.onyx
new file mode 100644 (file)
index 0000000..5cd759e
--- /dev/null
@@ -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;
+    }
+}
index 34c1d76ec2e274a8094e8d4d33a469014163f871..e93467db407716799b2501b03280282f869b3919 100644 (file)
@@ -19,3 +19,4 @@ package imgui
 #load "lib/gl/gl_utils"
 #load "lib/immediate_renderer"
 #load "lib/ttf/types"
+#load "lib/bitmap/bitmap"
index 340973bb358f8c70f8bd8731898dfba774febe43..14a56b65f0068e0cd28a0419b5753b7e029f917b 100644 (file)
@@ -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;
index 6b87b5d72aaaeb0f1fc91b9f2085d1d3a839159d..128f981c98eb5be72ff864025360f2b4d95aa0aa 100644 (file)
@@ -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();