#import "modules/std.sx";
#import "modules/std/mem.sx";
#import "modules/ffi/opengl.sx";
#import "modules/gpu/types.sx";
#import "modules/gpu/api.sx";
#import "vendors/stb_truetype/stb_truetype.sx";
#import "vendors/kb_text_shape/kb_text_shape.sx";
#import "vendors/file_utils/file_utils.sx";
#import "vendors/stb_image/stb_image.sx";
#import "modules/ui/types.sx";

// Cached glyph data with UV coordinates into the atlas texture
CachedGlyph :: struct {
    uv_x: f32;
    uv_y: f32;
    uv_w: f32;
    uv_h: f32;
    width: f32;
    height: f32;
    offset_x: f32;
    offset_y: f32;
    advance: f32;
}

// Cache entry: key + glyph data
GlyphEntry :: struct {
    key: u32;
    glyph: CachedGlyph;
}

// Quantize font size to half-point increments to limit cache entries.
// e.g., 13.0 -> 26, 13.5 -> 27, 14.0 -> 28
quantize_size :: (font_size: f32) -> u16 {
    xx (font_size * 2.0 + 0.5)
}

dequantize_size :: (q: u16) -> f32 {
    xx q / 2.0
}

// Pack (glyph_index, size_quantized) into a single u32 for fast comparison
make_glyph_key :: (glyph_index: u16, size_quantized: u16) -> u32 {
    (xx glyph_index << 16) | xx size_quantized
}

// Shaped glyph — output of text shaping (positioned glyph with index)
ShapedGlyph :: struct {
    glyph_index: u16;
    x: f32;       // horizontal position (logical units, cumulative)
    y: f32;       // vertical offset (logical units)
    advance: f32; // advance width (logical units)
}

is_ascii :: (text: string) -> bool {
    i : i64 = 0;
    while i < text.len {
        if text[i] >= 128 { return false; }
        i += 1;
    }
    true
}

// kbts constants (C enum values)
KBTS_DIRECTION_DONT_KNOW                  :u32: 0;
KBTS_LANGUAGE_DONT_KNOW                   :u32: 0;
KBTS_USER_ID_GENERATION_MODE_CODEPOINT_INDEX :u32: 0;

// SX structs matching kbts C struct layouts (64-bit).
// We define these in SX to access fields directly, casting from opaque C pointers.

KbtsGlyphIterator :: struct {
    glyph_storage: *void;
    current_glyph: *void;
    last_advance_x: i32;
    x: i32;
    y: i32;
}

KbtsRun :: struct {
    font: *void;
    script: u32;
    paragraph_direction: u32;
    direction: u32;
    flags: u32;
    glyphs: KbtsGlyphIterator;
}

KbtsGlyph :: struct {
    prev: *void;
    next: *void;
    codepoint: u32;
    id: u16;
    uid: u16;
    user_id_or_codepoint_index: i32;
    offset_x: i32;
    offset_y: i32;
    advance_x: i32;
    advance_y: i32;
}

// kbts_font_info2 base (simplified — we only need the Size field for dispatch)
KBTS_FONT_INFO_STRING_ID_COUNT :i32: 7;

KbtsFontInfo2 :: struct {
    size: u32;
    strings: [7]*void;     // char* array
    string_lengths: [7]u16;
    style_flags: u32;
    weight: u32;
    width: u32;
}

KbtsFontInfo2_1 :: struct {
    base: KbtsFontInfo2;
    units_per_em: u16;
    x_min: i16;
    y_min: i16;
    x_max: i16;
    y_max: i16;
    ascent: i16;
    descent: i16;
    line_gap: i16;
}

GLYPH_ATLAS_W :i32: 1024;
GLYPH_ATLAS_H :i32: 1024;
FONTINFO_SIZE  :i64: 256;

PackResult :: struct {
    x, y: i32;
}

// Dynamic glyph cache with on-demand rasterization and texture atlas packing.
GlyphCache :: struct {
    // Font data
    font_info: *void;   // heap-allocated stbtt_fontinfo (256 bytes)
    font_data: *void;   // raw TTF file bytes (kept alive for stbtt)

    // Atlas texture (GPU)
    texture_id: u32;
    atlas_width: i32;
    atlas_height: i32;

    // Atlas bitmap (CPU-side for updates)
    bitmap: [*]u8;

    // Shelf packer state
    shelf_y: i32;
    shelf_height: i32;
    cursor_x: i32;
    padding: i32;

    // Glyph lookup cache
    entries: List(GlyphEntry);

    // Hash table for O(1) glyph lookup (open addressing, linear probing)
    hash_keys: [*]u32;   // key per slot (0 = empty sentinel)
    hash_vals: [*]i32;   // index into entries list
    hash_cap: i64;       // table capacity (power of 2)

    // Dirty tracking for texture upload
    dirty: bool;

    // Font vertical metrics (at reference size 1.0 — scale by font_size)
    ascent: f32;
    descent: f32;
    line_gap: f32;

    // HiDPI: physical pixels per logical pixel (e.g. 2.0 on Retina)
    dpi_scale: f32;
    inv_dpi: f32;

    // Text shaping (kb_text_shape)
    shape_ctx: *void;
    shape_font: *void;
    units_per_em: u16;
    font_data_size: i32;
    shaped_buf: List(ShapedGlyph);

    // Shape cache: skip reshaping if same text + size as last call
    last_shape_ptr: [*]u8;
    last_shape_len: i64;
    last_shape_size_q: u16;

    // Allocator that owns every dynamically-grown buffer on this cache —
    // entries list, hash table, shaped_buf. Captured at init time so growth
    // never accidentally lands in a transient per-frame arena.
    parent_allocator: Allocator;

    // GPU protocol backend. When set, atlas creation + dirty uploads route
    // through `gpu` instead of raw GL.
    gpu: ?GPU = null;

    init :: (self: *GlyphCache, path: [:0]u8, default_size: f32) {
        // Preserve any pre-set GPU dispatch across the zero-out — the
        // surrounding struct memset would otherwise wipe it.
        saved_gpu := self.gpu;
        // Zero out the entire struct first (parent may be uninitialized with = ---)
        memset(self, 0, size_of(GlyphCache));
        self.gpu = saved_gpu;
        self.parent_allocator = context.allocator;

        // Load font file
        file_size : i32 = 0;
        font_data := read_file_bytes(path, @file_size);
        if font_data == null {
            out("Failed to load font: ");
            out(path);
            out("\n");
            return;
        }
        self.font_data = xx font_data;
        self.font_data_size = file_size;

        // Init stbtt_fontinfo
        self.font_info = self.parent_allocator.alloc_bytes(FONTINFO_SIZE);
        memset(self.font_info, 0, FONTINFO_SIZE);
        stbtt_InitFont(self.font_info, font_data, 0);

        // Get font vertical metrics (in unscaled font units)
        ascent_i : i32 = 0;
        descent_i : i32 = 0;
        linegap_i : i32 = 0;
        stbtt_GetFontVMetrics(self.font_info, @ascent_i, @descent_i, @linegap_i);

        // Store unscaled metrics — we'll scale per font_size in measure_text
        self.ascent = xx ascent_i;
        self.descent = xx descent_i;
        self.line_gap = xx linegap_i;

        // Init text shaping context
        self.shape_ctx = xx kbts_CreateShapeContext(xx 0, xx 0);
        if self.shape_ctx != null {
            self.shape_font = xx kbts_ShapePushFontFromMemory(xx self.shape_ctx, self.font_data, file_size, 0);
            // Get font metrics (units_per_em) from kbts
            kb_info : KbtsFontInfo2_1 = ---;
            memset(@kb_info, 0, size_of(KbtsFontInfo2_1));
            kb_info.base.size = xx size_of(KbtsFontInfo2_1);
            kbts_GetFontInfo2(xx self.shape_font, xx @kb_info);
            self.units_per_em = kb_info.units_per_em;
        }

        // Allocate atlas bitmap
        self.atlas_width = GLYPH_ATLAS_W;
        self.atlas_height = GLYPH_ATLAS_H;
        bitmap_size : i64 = xx self.atlas_width * xx self.atlas_height;
        self.bitmap = xx self.parent_allocator.alloc_bytes(bitmap_size);
        memset(self.bitmap, 0, bitmap_size);

        // Shelf packer init
        self.shelf_y = 0;
        self.shelf_height = 0;
        self.cursor_x = 0;
        self.padding = 1;

        self.dirty = false;
        self.dpi_scale = 1.0;
        self.inv_dpi = 1.0;

        // Init hash table (256 slots)
        self.hash_cap = 256;
        hash_bytes : i64 = self.hash_cap * 4;  // u32 per slot
        self.hash_keys = xx self.parent_allocator.alloc_bytes(hash_bytes);
        memset(self.hash_keys, 0, hash_bytes);
        val_bytes : i64 = self.hash_cap * 8;  // i64 per slot (i32 would suffice but alignment)
        self.hash_vals = xx self.parent_allocator.alloc_bytes(val_bytes);

        // Create the atlas texture. In GPU-protocol mode we create empty and
        // let the first `flush()` push the (zero-initialized) bitmap via
        // update_texture_region — same result as the GL path's glTexImage2D
        // with the zeroed bitmap, but works whether or not the backend
        // accepts CPU pixel pointers at create time.
        if self.gpu != null {
            self.texture_id = self.gpu.create_texture(
                self.atlas_width, self.atlas_height, .r8, null);
            self.dirty = true;
        } else {
            glGenTextures(1, @self.texture_id);
            glBindTexture(GL_TEXTURE_2D, self.texture_id);
            glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
            glTexImage2D(GL_TEXTURE_2D, 0, xx GL_R8, self.atlas_width, self.atlas_height, 0, GL_RED, GL_UNSIGNED_BYTE, self.bitmap);
            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, xx GL_LINEAR);
            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, xx GL_LINEAR);
            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, xx GL_CLAMP_TO_EDGE);
            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, xx GL_CLAMP_TO_EDGE);
        }

        out("GlyphCache initialized: ");
        out(path);
        out("\n");
    }

    // Look up or rasterize a glyph, returning a pointer to its cached entry.
    // Returns null for glyphs with no outline AND zero advance (shouldn't happen for valid chars).
    get_or_rasterize :: (self: *GlyphCache, glyph_index: u16, font_size: f32) -> *CachedGlyph {
        size_q := quantize_size(font_size);
        key := make_glyph_key(glyph_index, size_q);

        // Hash table lookup (open addressing, linear probing)
        mask := self.hash_cap - 1;
        slot : i64 = xx ((key * 2654435761) >> 24) & xx mask;
        while self.hash_keys[slot] != 0 {
            if self.hash_keys[slot] == key {
                return @self.entries.items[self.hash_vals[slot]].glyph;
            }
            slot = (slot + 1) & mask;
        }

        // Cache miss — rasterize
        actual_size := dequantize_size(size_q);
        scale := stbtt_ScaleForPixelHeight(self.font_info, actual_size);

        // Get glyph bounding box
        x0 : i32 = 0;
        y0 : i32 = 0;
        x1 : i32 = 0;
        y1 : i32 = 0;
        stbtt_GetGlyphBitmapBox(self.font_info, xx glyph_index, scale, scale, @x0, @y0, @x1, @y1);

        glyph_w := if x1 > x0 then x1 - x0 else 0;
        glyph_h := if y1 > y0 then y1 - y0 else 0;

        // Get horizontal metrics
        advance_i : i32 = 0;
        lsb_i : i32 = 0;
        stbtt_GetGlyphHMetrics(self.font_info, xx glyph_index, @advance_i, @lsb_i);
        advance : f32 = xx advance_i * scale;

        // Zero-size glyph (e.g. space) — cache with advance only
        if glyph_w == 0 or glyph_h == 0 {
            entry := GlyphEntry.{
                key = key,
                glyph = CachedGlyph.{
                    uv_x = 0.0, uv_y = 0.0, uv_w = 0.0, uv_h = 0.0,
                    width = 0.0, height = 0.0,
                    offset_x = xx x0, offset_y = xx y0,
                    advance = advance
                }
            };
            self.entries.append(entry, self.parent_allocator);
            self.hash_insert(key, self.entries.len - 1);
            return @self.entries.items[self.entries.len - 1].glyph;
        }

        // Pack into atlas
        pack := self.try_pack(glyph_w, glyph_h);
        if pack.x < 0 {
            // Atlas full — grow and retry
            self.grow();
            return self.get_or_rasterize(glyph_index, font_size);
        }

        // Rasterize directly into atlas bitmap
        dest_offset : i64 = xx pack.y * xx self.atlas_width + xx pack.x;
        stbtt_MakeGlyphBitmap(
            self.font_info,
            @self.bitmap[dest_offset],
            glyph_w, glyph_h,
            self.atlas_width,
            scale, scale,
            xx glyph_index
        );
        self.dirty = true;

        // Compute normalized UV coordinates
        atlas_wf : f32 = xx self.atlas_width;
        atlas_hf : f32 = xx self.atlas_height;

        entry := GlyphEntry.{
            key = key,
            glyph = CachedGlyph.{
                uv_x = xx pack.x / atlas_wf,
                uv_y = xx pack.y / atlas_hf,
                uv_w = xx glyph_w / atlas_wf,
                uv_h = xx glyph_h / atlas_hf,
                width = xx glyph_w,
                height = xx glyph_h,
                offset_x = xx x0,
                offset_y = xx y0,
                advance = advance
            }
        };
        self.entries.append(entry, self.parent_allocator);
        self.hash_insert(key, self.entries.len - 1);
        return @self.entries.items[self.entries.len - 1].glyph;
    }

    // Insert a key→index mapping into the hash table, growing if needed
    hash_insert :: (self: *GlyphCache, key: u32, index: i64) {
        // Grow if load factor > 70%
        if self.entries.len * 10 > self.hash_cap * 7 {
            self.hash_grow();
        }
        mask := self.hash_cap - 1;
        slot : i64 = xx ((key * 2654435761) >> 24) & xx mask;
        while self.hash_keys[slot] != 0 {
            slot = (slot + 1) & mask;
        }
        self.hash_keys[slot] = key;
        self.hash_vals[slot] = xx index;
    }

    // Double the hash table and rehash all entries
    hash_grow :: (self: *GlyphCache) {
        old_cap := self.hash_cap;
        old_keys := self.hash_keys;
        old_vals := self.hash_vals;

        self.hash_cap = old_cap * 2;
        hash_bytes : i64 = self.hash_cap * 4;
        self.hash_keys = xx self.parent_allocator.alloc_bytes(hash_bytes);
        memset(self.hash_keys, 0, hash_bytes);
        val_bytes : i64 = self.hash_cap * 8;
        self.hash_vals = xx self.parent_allocator.alloc_bytes(val_bytes);

        // Rehash
        mask := self.hash_cap - 1;
        i : i64 = 0;
        while i < old_cap {
            k := old_keys[i];
            if k != 0 {
                slot : i64 = xx ((k * 2654435761) >> 24) & xx mask;
                while self.hash_keys[slot] != 0 {
                    slot = (slot + 1) & mask;
                }
                self.hash_keys[slot] = k;
                self.hash_vals[slot] = old_vals[i];
            }
            i += 1;
        }

        self.parent_allocator.dealloc_bytes(old_keys);
        self.parent_allocator.dealloc_bytes(old_vals);
    }

    // Upload dirty atlas to GPU. On the Metal path, defer the upload to
    // end-of-frame (`upload_atlas_to_gpu`) — calling `replaceRegion:` against
    // the same R8 MTLTexture multiple times within one frame garbles the
    // contents on iOS-sim Metal. The dirty flag carries over so the final
    // end-of-frame upload picks up every rasterization that happened during
    // the frame's render pass.
    flush :: (self: *GlyphCache) {
        if self.dirty == false { return; }
        if self.gpu != null { return; }
        glBindTexture(GL_TEXTURE_2D, self.texture_id);
        glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
        glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, self.atlas_width, self.atlas_height, GL_RED, GL_UNSIGNED_BYTE, self.bitmap);
        self.dirty = false;
    }

    upload_atlas_to_gpu :: (self: *GlyphCache) {
        if self.gpu == null { return; }
        if self.dirty == false { return; }
        self.gpu.update_texture_region(self.texture_id, 0, 0,
            self.atlas_width, self.atlas_height, xx self.bitmap);
        self.dirty = false;
    }

    // Shelf-based rectangle packer.
    // Returns PackResult with x >= 0 on success, x = -1 if no space.
    try_pack :: (self: *GlyphCache, w: i32, h: i32) -> PackResult {
        padded_w := w + self.padding;
        padded_h := h + self.padding;

        // Try fitting on the current shelf
        eff_h := if self.shelf_height > padded_h then self.shelf_height else padded_h;
        if self.cursor_x + padded_w <= self.atlas_width and self.shelf_y + eff_h <= self.atlas_height {
            result := PackResult.{ x = self.cursor_x, y = self.shelf_y };
            self.cursor_x += padded_w;
            if padded_h > self.shelf_height {
                self.shelf_height = padded_h;
            }
            return result;
        }

        // Start a new shelf
        new_shelf_y := self.shelf_y + self.shelf_height;
        if new_shelf_y + padded_h <= self.atlas_height and padded_w <= self.atlas_width {
            self.shelf_y = new_shelf_y;
            self.shelf_height = padded_h;
            self.cursor_x = padded_w;
            return PackResult.{ x = 0, y = new_shelf_y };
        }

        // No space
        PackResult.{ x = 0 - 1, y = 0 - 1 }
    }

    // Grow the atlas by doubling dimensions
    grow :: (self: *GlyphCache) {
        new_w := self.atlas_width * 2;
        new_h := self.atlas_height * 2;
        new_size : i64 = xx new_w * xx new_h;
        new_bitmap : [*]u8 = xx self.parent_allocator.alloc_bytes(new_size);
        memset(new_bitmap, 0, new_size);

        // Copy old rows into new bitmap
        y : i32 = 0;
        while y < self.atlas_height {
            old_off : i64 = xx y * xx self.atlas_width;
            new_off : i64 = xx y * xx new_w;
            memcpy(@new_bitmap[new_off], @self.bitmap[old_off], xx self.atlas_width);
            y += 1;
        }

        self.parent_allocator.dealloc_bytes(self.bitmap);
        self.bitmap = new_bitmap;
        self.atlas_width = new_w;
        self.atlas_height = new_h;

        // Recreate atlas at the new size.
        if self.gpu != null {
            self.gpu.destroy_texture(self.texture_id);
            self.texture_id = self.gpu.create_texture(new_w, new_h, .r8, xx new_bitmap);
        } else {
            glDeleteTextures(1, @self.texture_id);
            glGenTextures(1, @self.texture_id);
            glBindTexture(GL_TEXTURE_2D, self.texture_id);
            glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
            glTexImage2D(GL_TEXTURE_2D, 0, xx GL_R8, new_w, new_h, 0, GL_RED, GL_UNSIGNED_BYTE, new_bitmap);
            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, xx GL_LINEAR);
            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, xx GL_LINEAR);
            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, xx GL_CLAMP_TO_EDGE);
            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, xx GL_CLAMP_TO_EDGE);
        }

        // Recompute UV coordinates for all cached glyphs
        atlas_wf : f32 = xx new_w;
        atlas_hf : f32 = xx new_h;
        i : i64 = 0;
        while i < self.entries.len {
            g := @self.entries.items[i].glyph;
            if g.width > 0.0 {
                g.uv_x = g.uv_x / 2.0;
                g.uv_y = g.uv_y / 2.0;
                g.uv_w = g.width / atlas_wf;
                g.uv_h = g.height / atlas_hf;
            }
            i += 1;
        }

        self.dirty = false;
        out("GlyphCache atlas grown\n");
    }

    set_dpi_scale :: (self: *GlyphCache, scale: f32) {
        self.dpi_scale = scale;
        self.inv_dpi = 1.0 / scale;
    }

    // Get the scale factor for a logical font size
    scale_for_size :: (self: *GlyphCache, font_size: f32) -> f32 {
        stbtt_ScaleForPixelHeight(self.font_info, font_size)
    }

    // Get scaled ascent for a logical font size
    get_ascent :: (self: *GlyphCache, font_size: f32) -> f32 {
        self.ascent * self.scale_for_size(font_size)
    }

    // Get scaled line height for a logical font size
    get_line_height :: (self: *GlyphCache, font_size: f32) -> f32 {
        s := self.scale_for_size(font_size);
        (self.ascent - self.descent + self.line_gap) * s
    }

    // Shape text into positioned glyphs.
    // Uses ASCII fast-path (stbtt byte-by-byte) for pure ASCII,
    // full kb_text_shape pipeline for Unicode/complex scripts.
    // Results stored in self.shaped_buf (reused across calls).
    shape_text :: (self: *GlyphCache, text: string, font_size: f32) {
        // Check shape cache: skip if same text + size as last call
        size_q := quantize_size(font_size);
        if text.len > 0 and text.ptr == self.last_shape_ptr and text.len == self.last_shape_len and size_q == self.last_shape_size_q {
            return;  // shaped_buf already has the result
        }

        self.shaped_buf.len = 0;
        if text.len == 0 { return; }

        if is_ascii(text) {
            self.shape_ascii(text, font_size);
        } else {
            self.shape_with_kb(text, font_size);
        }

        // Update shape cache
        self.last_shape_ptr = text.ptr;
        self.last_shape_len = text.len;
        self.last_shape_size_q = size_q;
    }

    shape_ascii :: (self: *GlyphCache, text: string, font_size: f32) {
        scale := stbtt_ScaleForPixelHeight(self.font_info, font_size);
        total : f32 = 0.0;
        i : i64 = 0;
        while i < text.len {
            ch : i32 = xx text[i];
            glyph_index : u16 = xx stbtt_FindGlyphIndex(self.font_info, ch);

            advance_i : i32 = 0;
            lsb_i : i32 = 0;
            stbtt_GetGlyphHMetrics(self.font_info, xx glyph_index, @advance_i, @lsb_i);
            adv : f32 = xx advance_i * scale;

            self.shaped_buf.append(ShapedGlyph.{
                glyph_index = glyph_index,
                x = total,
                y = 0.0,
                advance = adv
            }, self.parent_allocator);
            total += adv;
            i += 1;
        }
    }

    shape_with_kb :: (self: *GlyphCache, text: string, font_size: f32) {
        if self.shape_ctx == null {
            self.shape_ascii(text, font_size);
            return;
        }

        scale : f32 = font_size / xx self.units_per_em;
        total : f32 = 0.0;

        kbts_ShapeBegin(xx self.shape_ctx, KBTS_DIRECTION_DONT_KNOW, KBTS_LANGUAGE_DONT_KNOW);
        kbts_ShapeUtf8(xx self.shape_ctx, xx text.ptr, xx text.len, KBTS_USER_ID_GENERATION_MODE_CODEPOINT_INDEX);
        kbts_ShapeEnd(xx self.shape_ctx);

        run : KbtsRun = ---;
        while kbts_ShapeRun(xx self.shape_ctx, xx @run) != 0 {
            glyph_ptr : *KbtsGlyph = null;
            while kbts_GlyphIteratorNext(xx @run.glyphs, xx @glyph_ptr) != 0 {
                if glyph_ptr == null { continue; }
                gx := total + xx glyph_ptr.offset_x * scale;
                gy : f32 = xx glyph_ptr.offset_y * scale;
                adv : f32 = xx glyph_ptr.advance_x * scale;

                self.shaped_buf.append(ShapedGlyph.{
                    glyph_index = glyph_ptr.id,
                    x = gx,
                    y = gy,
                    advance = adv
                }, self.parent_allocator);
                total += adv;
            }
        }
    }

    // Measure text at a logical font size using text shaping.
    // Rasterizes at physical resolution (font_size * dpi_scale), returns logical dimensions.
    measure_text :: (self: *GlyphCache, text: string, font_size: f32) -> Size {
        self.shape_text(text, font_size);
        width : f32 = 0.0;
        i : i64 = 0;
        while i < self.shaped_buf.len {
            width += self.shaped_buf.items[i].advance;
            i += 1;
        }
        Size.{ width = width, height = self.get_line_height(font_size) }
    }
}
