Conversion of stb_truetype.h to Odin.
- Odin 100%
| .gitignore | ||
| license.md | ||
| README.md | ||
| truetype.odin | ||
TrueType
A resfreshingly pure Odin implementation of TrueType font parsing and rendering, featuring Raph Levien's analytical curve tessellation for crystal-clear text that flows smoothly at any size.
✨ What Makes This Library Special?
Core Features
- Complete Font Support: Handles both TrueType (.ttf) and OpenType/CFF (.otf) fonts
- Smooth Curves: Raph Levien's analytical curve flattening for mathematically perfect results
- High Performance: Optimized critical paths with inline functions
- Everything Included: Font metrics, glyph shapes, bitmap rendering, kerning, and signed distance fields
- Zero Dependencies: Pure Odin implementation, clean as a coral reef
Advanced Features
- Analytical Tessellation: Mathematical analysis instead of recursive subdivision
- Subpixel Precision: Smooth glyph positioning with subpixel accuracy
- Quality Scaling: 1x to 8x oversampling support
- Font Atlases: Efficient texture atlas generation
- SDF Generation: Signed distance field rendering for infinitely scalable text
- Color Fonts: Full support for embedded SVG glyphs
- Global Scripts: Advanced OpenType features and kerning support
🚀 Quick Start
package main
import tt "path/to/truetype"
import "core:os"
main :: proc() {
// Load font data
font_data, ok := os.read_entire_file("path/to/font.ttf")
if !ok do return
defer delete(font_data)
// Initialize the font
font: tt.Font_Info
if !tt.init_font(&font, raw_data(font_data), 0) {
return
}
// Get font metrics
ascent, descent, line_gap: i32
tt.get_font_vmetrics(&font, &ascent, &descent, &line_gap)
// Calculate scale for desired pixel height
scale := tt.scale_for_pixel_height(&font, 24.0)
// Render a glyph
width, height, xoff, yoff: i32
bitmap := tt.get_codepoint_bitmap(&font, scale, scale, 'A', &width, &height, &xoff, &yoff)
if bitmap != nil {
defer tt.free_bitmap(bitmap)
// Use bitmap data...
}
}
📚 API Overview
Font Initialization
// Initialize font from data
init_font :: proc(info: ^Font_Info, data: [^]u8, fontstart: i32) -> bool
// Get number of fonts in collection
get_number_of_fonts :: proc(data: [^]u8) -> i32
// Get offset for specific font in collection
get_font_offset_for_index :: proc(data: [^]u8, index: i32) -> i32
Font Metrics
// Scaling
scale_for_pixel_height :: proc(info: ^Font_Info, pixels: f32) -> f32
scale_for_mapping_em_to_pixels :: proc(info: ^Font_Info, pixels: f32) -> f32
// Vertical metrics
get_font_vmetrics :: proc(info: ^Font_Info, ascent: ^i32, descent: ^i32, line_gap: ^i32)
// Character metrics
get_codepoint_hmetrics :: proc(info: ^Font_Info, codepoint: i32, advance_width: ^i32, left_side_bearing: ^i32)
get_codepoint_box :: proc(info: ^Font_Info, codepoint: i32, x0: ^i32, y0: ^i32, x1: ^i32, y1: ^i32) -> bool
Bitmap Rendering
// Simple bitmap rendering
get_codepoint_bitmap :: proc(info: ^Font_Info, scale_x: f32, scale_y: f32, codepoint: i32,
width: ^i32, height: ^i32, xoff: ^i32, yoff: ^i32, allocator := context.allocator) -> [^]u8
// Subpixel rendering
get_codepoint_bitmap_subpixel :: proc(info: ^Font_Info, scale_x: f32, scale_y: f32, shift_x: f32, shift_y: f32,
codepoint: i32, width: ^i32, height: ^i32, xoff: ^i32, yoff: ^i32,
allocator := context.allocator) -> [^]u8
// Render into existing buffer
make_codepoint_bitmap :: proc(info: ^Font_Info, output: [^]u8, out_w: i32, out_h: i32, out_stride: i32,
scale_x: f32, scale_y: f32, codepoint: i32)
Advanced Features
// Signed distance fields
get_codepoint_sdf :: proc(info: ^Font_Info, scale: f32, codepoint: i32, padding: i32,
onedge_value: u8, pixel_dist_scale: f32, width: ^i32, height: ^i32,
xoff: ^i32, yoff: ^i32, allocator := context.allocator) -> [^]u8
// Glyph shapes (vector data)
get_codepoint_shape :: proc(info: ^Font_Info, codepoint: i32, pvertices: ^^Vertex) -> i32
// Kerning
get_codepoint_kern_advance :: proc(info: ^Font_Info, ch1: i32, ch2: i32) -> i32
// Font packing for texture atlases
pack_font_ranges :: proc(spc: ^Pack_Context, fontdata: [^]u8, font_index: i32, ranges: [^]Pack_Range, num_ranges: i32) -> i32
🔬 Technical Details
Analytical Tessellation
This library uses Raph Levien's analytical curve flattening as its primary tessellation method—the crown jewel of the rendering pipeline. This approach offers significant advantages over traditional recursive subdivision:
- Mathematical Precision: Maps quadratic Bézier curves to canonical parabola form for exact computation
- Consistent Quality: Analytical computation produces reliable results regardless of curve complexity
- Better Performance: Eliminates recursive call overhead and reduces memory allocations
- Predictable Results: Consistent curve quality across all glyphs
Reference: Flattening quadratic Béziers
Performance Optimizations
- Inlined Critical Functions: Hot path functions marked with
#force_inlinefor smooth sailing - Smart Font Type Caching: Eliminates repeated CFF/TrueType dispatch overhead
- Stack-Based Operations: Uses stack arrays for small, temporary operations
- Optimized Bounding: Efficient bounding calculations without unnecessary vertex generation
Memory Management
- Allocator Support: Functions accept optional allocator parameters
- Arena Optimization: Uses temporary arenas for intermediate allocations
- Zero Copy: Direct font data access without unnecessary copying
📋 Data Types
Core Types
Font_Info :: struct {
// Font parsing state and table offsets
data: [^]u8,
fontstart: i32,
num_glyphs: i32,
is_cff: bool, // Cached font type for performance
// ... additional fields
}
Vertex :: struct {
x, y, cx, cy, cx1, cy1: i16, // Coordinates and control points
type: u8, // Vertex type (move, line, curve, cubic)
padding: u8,
}
Vertex Types
VMOVE :: 1 // Move to point
VLINE :: 2 // Line to point
VCURVE :: 3 // Quadratic curve to point
VCUBIC :: 4 // Cubic curve to point
🌍 Font Support
Supported Formats
- TrueType (.ttf): Complete support including compound glyphs
- OpenType (.otf): CFF-based fonts with PostScript outlines
- Font Collections (.ttc): Multiple fonts in single file
- Variable Fonts: Basic support for variable font instances
Supported Tables
- Core:
head,hhea,hmtx,maxp,name,OS/2,post - TrueType:
glyf,loca - CFF:
CFFincluding CID-keyed fonts - Advanced:
kern,GPOS(basic kerning),SVG(color fonts)
🛡️ Error Handling
The library follows Odin's explicit error handling patterns, weathering edge cases gracefully:
- Clear Results: Functions return
boolfor unambiguous success/failure - Safe Defaults: Invalid inputs trigger early returns with sensible defaults
- Graceful Degradation: Memory allocation failures are handled cleanly
- Data Validation: Comprehensive font data validation prevents crashes on malformed files
💡 Examples
Font Atlas Generation
// Create texture atlas with multiple character ranges
pack_context: tt.Pack_Context
atlas_width := 512
atlas_height := 512
atlas_data := make([]u8, atlas_width * atlas_height)
tt.pack_begin(&pack_context, atlas_data, atlas_width, atlas_height, 0, 1)
// Pack ASCII range
char_data := make([]tt.Packed_Char, 95)
range := tt.Pack_Range{
font_size = 24,
first_unicode_codepoint_in_range = 32,
num_chars = 95,
chardata_for_range = raw_data(char_data),
}
ranges := [1]tt.Pack_Range{range}
result := tt.pack_font_ranges(&pack_context, raw_data(font_data), 0, raw_data(ranges), 1)
tt.pack_end(&pack_context)
SDF Text Rendering
// Generate signed distance field for scalable text
width, height, xoff, yoff: i32
sdf := tt.get_codepoint_sdf(&font, scale, 'A', 3, 128, 8.0, &width, &height, &xoff, &yoff)
if sdf != nil {
defer tt.free_bitmap(sdf)
// Use SDF for GPU-accelerated text rendering
}
⚡ Performance Notes
- CFF vs TrueType: CFF fonts require CharString interpretation, making them roughly 10x slower for glyph operations—take the express route with TrueType when possible
- Caching Strategy: Cache font metrics and glyph shapes when rendering large text volumes
- Memory Tips: Use temporary allocators for short-lived allocations to reduce GC pressure
- Threading: Font_Info structures aren't thread-safe; use separate instances per thread