package font import "core:math" import "core:mem" // ============================================================================ // AUTOHINTER — Lightweight grid-fitting without TrueType bytecode // ============================================================================ // Hint mode controls the aggressiveness of grid-fitting Hint_Mode :: enum { None, // No hinting (default, best for high-DPI) Light, // Vertical only — snap Y stems to pixel grid, preserve X Full, // Vertical + horizontal stem snapping } // Autohint settings Hint_Settings :: struct { mode: Hint_Mode, ppem: f32, // pixels per em (derived from scale) } // Apply autohinting to glyph vertices in-place. // Modifies vertex Y coordinates (and optionally X) to align with pixel grid. // scale is the scale_for_pixel_height value. autohint_glyph :: proc( info: ^Font_Info, vertices: [^]Vertex, num_verts: i32, scale: f32, mode: Hint_Mode, ) { if mode == .None || num_verts <= 0 do return // Get key vertical metrics in font units ascent, descent, line_gap: i32 get_font_vmetrics(info, &ascent, &descent, &line_gap) // Get OS/2 metrics for x-height and cap-height x_height: i32 = 0 cap_height: i32 = 0 os2: OS2_Metrics if get_os2_metrics(info, &os2) { x_height = i32(os2.x_height) cap_height = i32(os2.cap_height) } // Define alignment zones (font-unit Y values → snapped pixel Y) zones: [6]Hint_Zone zone_count: i32 = 0 // Baseline at 0 zones[zone_count] = make_zone(0, scale) zone_count += 1 // Ascender if ascent != 0 { zones[zone_count] = make_zone(ascent, scale) zone_count += 1 } // Descender if descent != 0 { zones[zone_count] = make_zone(descent, scale) zone_count += 1 } // Cap height if cap_height > 0 { zones[zone_count] = make_zone(cap_height, scale) zone_count += 1 } // x-height if x_height > 0 { zones[zone_count] = make_zone(x_height, scale) zone_count += 1 } // Apply Y-direction grid fitting for i in 0.. [^]u8 { arena_backing: [256 * 1024]u8 arena: mem.Arena mem.arena_init(&arena, arena_backing[:]) context.allocator = mem.arena_allocator(&arena) vertices_temp: ^Vertex = nil // Get uncached shape (we'll modify it) nv: i32 if info.is_cff { nv = get_glyph_shape_t2(info, glyph, cast(^^Vertex)&vertices_temp) } else { nv = get_glyph_shape_tt(info, glyph, cast(^^Vertex)&vertices_temp) } vertices := ([^]Vertex)(vertices_temp) if nv <= 0 || vertices == nil { if width != nil do width^ = 0 if height != nil do height^ = 0 return nil } // Apply autohinting autohint_glyph(info, vertices, nv, scale_y, mode) scale_x := scale_x; scale_y := scale_y if scale_x == 0 do scale_x = scale_y if scale_y == 0 { if scale_x == 0 do return nil; scale_y = scale_x } ix0, iy0, ix1, iy1: i32 get_glyph_bitmap_box_subpixel(info, glyph, scale_x, scale_y, 0, 0, &ix0, &iy0, &ix1, &iy1) gbm: Bitmap gbm.w = ix1 - ix0; gbm.h = iy1 - iy0 if width != nil do width^ = gbm.w if height != nil do height^ = gbm.h if xoff != nil do xoff^ = ix0 if yoff != nil do yoff^ = iy0 if gbm.w != 0 && gbm.h != 0 { ptr, _ := mem.alloc(int(gbm.w * gbm.h), allocator = allocator) gbm.pixels = ([^]u8)(ptr) if gbm.pixels != nil { gbm.stride = gbm.w rasterize_glyph(&gbm, 0.35, vertices, nv, scale_x, scale_y, 0, 0, ix0, iy0, 1) } } return gbm.pixels } get_codepoint_bitmap_hinted :: proc( info: ^Font_Info, scale: f32, codepoint: i32, mode: Hint_Mode, width: ^i32, height: ^i32, xoff: ^i32, yoff: ^i32, allocator := context.allocator, ) -> [^]u8 { return get_glyph_bitmap_hinted(info, scale, scale, find_glyph_index(info, codepoint), mode, width, height, xoff, yoff, allocator) } // ============================================================================ // Internal // ============================================================================ @(private) Hint_Zone :: struct { font_y: f32, // Y coordinate in font units pixel_y: f32, // Snapped pixel Y (rounded font_y * scale) } @(private) make_zone :: proc(font_y: i32, scale: f32) -> Hint_Zone { pixel_y := f32(font_y) * scale return { font_y = f32(font_y), pixel_y = math.round(pixel_y), } } // Snap a font-unit Y coordinate to the nearest alignment zone @(private) snap_to_zone :: proc(font_y: f32, zones: []Hint_Zone, scale: f32) -> f32 { // Zone capture radius in font units radius := 1.0 / scale * 0.5 // half-pixel in font units best_dist := radius result := font_y for z in zones { dist := abs(font_y - z.font_y) if dist < best_dist { best_dist = dist // Shift this coordinate by the same delta the zone was shifted delta := z.pixel_y - z.font_y * scale result = font_y + delta / scale } } return result } // Snap X coordinate to half-pixel grid for consistent stem widths @(private) snap_stem_x :: proc(font_x: f32, scale: f32) -> f32 { px := font_x * scale // Snap to half-pixel boundaries for stem consistency snapped := math.round(px * 2) / 2 return snapped / scale }