/* TrueType Refreshingly simple, pure-Odin TrueType library. Built from stb_truetype's work and Raph Levien's work on quadratic flattening. Sharkk Minimal License (license.md) Copyright (c) 2026 Sharkk Honorable mentions: - stb_truetype.h by Sean Barrett / RAD Game Tools (public domain) - Flattening quadratic Béziers by Raph Levien (Apache 2.0) */ package font import "core:math" // Core structures Buf :: struct { data: [^]u8, cursor: i32, size: i32, } Font_Info :: struct { userdata: rawptr, data: [^]u8, data_size: i32, fontstart: i32, num_glyphs: i32, loca: i32, head: i32, glyf: i32, hhea: i32, hmtx: i32, kern: i32, gpos: i32, gsub: i32, svg: i32, index_map: i32, index_to_loc_format: i32, is_cff: bool, cff: Buf, charstrings: Buf, gsubrs: Buf, subrs: Buf, fontdicts: Buf, fdselect: Buf, // Caches kern_cache: map[u64]i32, kern_cache_ready: bool, shape_cache: map[i32]Cached_Shape, shape_cache_ready: bool, cmap_cache: map[i32]i32, cmap_cache_ready: bool, } // Cached glyph shape (pre-extracted vertices) Cached_Shape :: struct { vertices: [^]Vertex, num_vertices: i32, } Vertex :: struct { x, y, cx, cy, cx1, cy1: i16, type: u8, padding: u8, } Baked_Char :: struct { x0, y0, x1, y1: u16, xoff, yoff, xadvance: f32, } Packed_Char :: struct { x0, y0, x1, y1: u16, xoff, yoff, xadvance: f32, xoff2, yoff2: f32, } Aligned_Quad :: struct { x0, y0, s0, t0: f32, x1, y1, s1, t1: f32, } // ============================================================================ // ADVANCED FONT PACKING // ============================================================================ // Maximum oversampling factor (must be power of 2) MAX_OVERSAMPLE :: 8 // Rect packing context - simple row-by-row algorithm Rp_Context :: struct { width: i32, height: i32, x: i32, // current x cursor y: i32, // current row y bottom_y: i32, // bottom of current row } // Rect packing node (minimal for API compatibility) Rp_Node :: struct { x: u8, } // Rectangle for packing Rp_Rect :: struct { x, y: i32, // output position id: i32, w, h: i32, // input dimensions was_packed: i32, // output: 1 if packed } // Pack context for texture atlas creation Pack_Context :: struct { user_allocator_context: rawptr, pack_info: ^Rp_Context, width: i32, height: i32, stride_in_bytes: i32, padding: i32, skip_missing: i32, h_oversample: u32, v_oversample: u32, pixels: [^]u8, nodes: ^Rp_Node, } // Pack range for multi-range packing Pack_Range :: struct { font_size: f32, first_unicode_codepoint_in_range: i32, array_of_unicode_codepoints: [^]i32, num_chars: i32, chardata_for_range: [^]Packed_Char, h_oversample: u8, // internal use v_oversample: u8, // internal use } Kerning_Entry :: struct { glyph1: i32, glyph2: i32, advance: i32, } // Vertex types for glyph shapes VMOVE :: 1 VLINE :: 2 VCURVE :: 3 VCUBIC :: 4 // Simple glyph flags (TrueType spec) GLYPH_ON_CURVE :: 1 // Point is on curve (vs control point) GLYPH_X_SHORT_VECTOR :: 2 // X coordinate is 1 byte GLYPH_Y_SHORT_VECTOR :: 4 // Y coordinate is 1 byte GLYPH_REPEAT :: 8 // Next byte specifies repeat count GLYPH_X_IS_SAME :: 16 // If X_SHORT: x is positive; else: x is same as previous GLYPH_Y_IS_SAME :: 32 // If Y_SHORT: y is positive; else: y is same as previous // Composite glyph flags (TrueType spec) COMP_ARG_1_AND_2_ARE_WORDS :: 1 // Arguments are words (else bytes) COMP_ARGS_ARE_XY_VALUES :: 2 // Arguments are xy values (else point indices) COMP_ROUND_XY_TO_GRID :: 4 // Round xy values to grid COMP_WE_HAVE_A_SCALE :: 1 << 3 // Single scale value follows COMP_MORE_COMPONENTS :: 1 << 5 // More components follow this one COMP_WE_HAVE_AN_X_AND_Y_SCALE :: 1 << 6 // X and Y scales follow COMP_WE_HAVE_A_TWO_BY_TWO :: 1 << 7 // 2x2 transformation matrix follows COMP_WE_HAVE_INSTRUCTIONS :: 1 << 8 // Instructions follow components COMP_USE_MY_METRICS :: 1 << 9 // Use this component's metrics // Context for CFF CharString interpretation CS_Ctx :: struct { bounds: bool, started: bool, first_x: f32, first_y: f32, x: f32, y: f32, min_x: i32, max_x: i32, min_y: i32, max_y: i32, pvertices: [^]Vertex, num_vertices: i32, } // Bitmap output Bitmap :: struct { w: i32, h: i32, stride: i32, pixels: [^]u8, } // Platform IDs PLATFORM_ID_UNICODE :: 0 PLATFORM_ID_MAC :: 1 PLATFORM_ID_ISO :: 2 PLATFORM_ID_MICROSOFT :: 3 // Microsoft encoding IDs MS_EID_SYMBOL :: 0 MS_EID_UNICODE_BMP :: 1 MS_EID_SHIFTJIS :: 2 MS_EID_UNICODE_FULL :: 10 // Unicode encoding IDs UNICODE_EID_UNICODE_1_0 :: 0 UNICODE_EID_UNICODE_1_1 :: 1 UNICODE_EID_ISO_10646 :: 2 UNICODE_EID_UNICODE_2_0_BMP :: 3 UNICODE_EID_UNICODE_2_0_FULL :: 4 // Mac encoding IDs MAC_EID_ROMAN :: 0 MAC_EID_JAPANESE :: 1 MAC_EID_CHINESE_TRAD :: 2 MAC_EID_KOREAN :: 3 MAC_EID_ARABIC :: 4 MAC_EID_HEBREW :: 5 MAC_EID_GREEK :: 6 MAC_EID_RUSSIAN :: 7 // Microsoft language IDs MS_LANG_ENGLISH :: 0x0409 MS_LANG_CHINESE :: 0x0804 MS_LANG_DUTCH :: 0x0413 MS_LANG_FRENCH :: 0x040c MS_LANG_GERMAN :: 0x0407 MS_LANG_HEBREW :: 0x040d MS_LANG_ITALIAN :: 0x0410 MS_LANG_JAPANESE :: 0x0411 MS_LANG_KOREAN :: 0x0412 MS_LANG_RUSSIAN :: 0x0419 MS_LANG_SPANISH :: 0x0409 MS_LANG_SWEDISH :: 0x041D // Mac language IDs MAC_LANG_ENGLISH :: 0 MAC_LANG_FRENCH :: 1 MAC_LANG_GERMAN :: 2 MAC_LANG_ITALIAN :: 3 MAC_LANG_DUTCH :: 4 MAC_LANG_SWEDISH :: 5 MAC_LANG_SPANISH :: 6 MAC_LANG_HEBREW :: 10 MAC_LANG_JAPANESE :: 11 MAC_LANG_ARABIC :: 12 MAC_LANG_CHINESE_TRAD :: 19 MAC_LANG_KOREAN :: 23 MAC_LANG_RUSSIAN :: 32 MAC_LANG_CHINESE_SIMPLIFIED :: 33 // Mac style flags (for find_matching_font) MACSTYLE_DONTCARE :: 0 MACSTYLE_BOLD :: 1 MACSTYLE_ITALIC :: 2 MACSTYLE_UNDERSCORE :: 4 MACSTYLE_NONE :: 8 // Helper macros for reading big-endian data @(private) ttBYTE :: #force_inline proc(p: [^]u8) -> u8 { return p[0] } @(private) ttCHAR :: #force_inline proc(p: [^]u8) -> i8 { return i8(p[0]) } @(private) ttUSHORT :: #force_inline proc(p: [^]u8) -> u16 { return u16(p[0]) << 8 | u16(p[1]) } @(private) ttSHORT :: #force_inline proc(p: [^]u8) -> i16 { return i16(p[0]) << 8 | i16(p[1]) } @(private) ttULONG :: #force_inline proc(p: [^]u8) -> u32 { return u32(p[0]) << 24 | u32(p[1]) << 16 | u32(p[2]) << 8 | u32(p[3]) } @(private) ttLONG :: #force_inline proc(p: [^]u8) -> i32 { return i32(p[0]) << 24 | i32(p[1]) << 16 | i32(p[2]) << 8 | i32(p[3]) } @(private) tag4 :: #force_inline proc(p: [^]u8, c0, c1, c2, c3: u8) -> bool { return p[0] == c0 && p[1] == c1 && p[2] == c2 && p[3] == c3 } @(private) tag :: #force_inline proc(p: [^]u8, str: string) -> bool { return tag4(p, str[0], str[1], str[2], str[3]) }