+/*
+
+Bitmap Fonts
+
+Simple bitmap font renderer. Splits an image of all font characters a set of glyphs
+that can be used to render individual characters / text.
+
+When creating a bitmap font, you need to provide the font texture, and the order of the
+glyphs in the texture in a string. You can use non-ASCII characters in the string using
+'\xAA' as a character.
+
+The font data provided is an RGBA byte array, with interlaced components.
+In other words the data should look like:
+
+ R G B A R G B A R G B A ...
+
+The top left (0-th) pixel is taken to be the separator color. It is used to determine the
+outline of every glyph. Glyphs are read from the top left, left to right, top to bottom.
+The separator color must be present on the entire boundaries of the glyph.
+
+*/
+
+
+
+
+
+package bitmap_font
+use package core
+
+Bitmap_Font :: struct {
+ font_texture : Bitmap_Font_Texture;
+
+ em: f32; // Width of 'M'
+
+ glyphs : map.Map(u32, Glyph);
+
+ Glyph :: struct {
+ x0, y0, x1, y1: f32;
+ w, h: f32;
+ }
+
+ get_glyph :: (use bmp: ^Bitmap_Font, char: u8) -> ^Glyph {
+ return map.get_ptr(^glyphs, ~~char);
+ }
+}
+
+Bitmap_Font_Texture :: struct {
+ Components_Per_Pixel :: 4; // NOTE: RGBA only
+
+ data : [] u8;
+ width, height : u32;
+
+ // NOTE: Assumes pixels are laid out in RGBA format.
+ get_pixel :: (use texture: ^Bitmap_Font_Texture, x: u32, y: u32) -> u32 {
+ return (cast(^u32) data.data)[y * width + x];
+
+ // SPEED: All of these values could be read simultaneously by treating the data
+ // array as a slice of u32, and then not multiplying by Components_Per_Pixel.
+ //
+ // return (cast(u32) data[(y * width + x) * Components_Per_Pixel + 0] << 24)
+ // | (cast(u32) data[(y * width + x) * Components_Per_Pixel + 1] << 16)
+ // | (cast(u32) data[(y * width + x) * Components_Per_Pixel + 2] << 8)
+ // | (cast(u32) data[(y * width + x) * Components_Per_Pixel + 3] << 0);
+ }
+}
+
+
+// NOTE: Takes the raw RGBA font data. Therefore, font_data.count is expected to
+// be a multiple of 4 in length, with interlaced red, green, blue and alpha components.
+// The font data can be load from another source using this method, which is why it
+// is preferred.
+bitmap_font_create :: (bft: Bitmap_Font_Texture, charset: str) -> Bitmap_Font {
+ assert(bft.data.count <= bft.width * bft.height * Bitmap_Font_Texture.Components_Per_Pixel, "Bad font size.");
+
+ bmp: Bitmap_Font;
+ bmp.font_texture = bft;
+ map.init(^bmp.glyphs, .{0,0,0,0,0,0});
+
+ success := bitmap_font_prepare_glyphs(^bmp, charset);
+ assert(success, "Failed to load glyphs out of font.");
+
+ return bmp;
+}
+
+bitmap_font_prepare_glyphs :: (use bmp: ^Bitmap_Font, charset: str) -> bool {
+ separator_color := font_texture->get_pixel(0, 0);
+
+ g_x0, g_y0: u32;
+ g_x0, g_y0 = 0, 0;
+
+ for char: charset {
+ if g_y0 >= font_texture.height do return false;
+
+ // These will need to be converted to floating point when they are inserted into the glyph,
+ // but it is easier to think about them as actual pixel coordinates when parsing the glyphs.
+ x0, y0, x1, y1: u32;
+ x0, y0 = g_x0 + 1, g_y0 + 1;
+ x1, y1 = x0, y0;
+
+ while font_texture->get_pixel(x1, y0) != separator_color do x1 += 1;
+ while font_texture->get_pixel(x0, y1) != separator_color do y1 += 1;
+
+ map.put(^glyphs, ~~char, .{
+ x0 = cast(f32) x0 / ~~font_texture.width,
+ y0 = cast(f32) y0 / ~~font_texture.height,
+ x1 = cast(f32) x1 / ~~font_texture.width,
+ y1 = cast(f32) y1 / ~~font_texture.height,
+ w = cast(f32) (x1 - x0) / ~~font_texture.width,
+ h = cast(f32) (y1 - y0) / ~~font_texture.height,
+ });
+
+ g_x0 = x1 + 1;
+
+ // CLEANUP: This is gross because Onyx does not support short circuit boolean operators,
+ // and because g_x0 being greater than font_texture.width would potentially overflow in
+ // get_pixel, they need to be checked in two different steps instead of with '||'.
+ reset := g_x0 >= font_texture.width;
+ if !reset do reset = font_texture->get_pixel(g_x0, g_y0) != separator_color;
+ if reset {
+ g_x0 = 0;
+ g_y0 = y1 + 1;
+ }
+ }
+
+ M_glyph := map.get_ptr(^glyphs, #char "M");
+ if M_glyph != null {
+ em = M_glyph.w * ~~font_texture.width;
+ } else {
+ // ROBUSTNESS: If there is no 'M' in the character set, then just use 16. It will probably be very wrong.
+ em = 16;
+ }
+
+ return true;
+}