1
0
Fork 0
Conversion of stb_truetype.h to Odin.
Find a file
2026-02-03 12:48:42 -06:00
.gitignore Add more test directories/executables 2026-01-31 22:26:08 -06:00
license.md Add license and usage to comment in truetype.odin 2026-02-03 12:48:42 -06:00
README.md Update readme 2026-02-02 21:34:22 -06:00
truetype.odin Add license and usage to comment in truetype.odin 2026-02-03 12:48:42 -06:00

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_inline for 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: CFF including 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 bool for 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