package font import "core:mem" import "core:math" @(private) ifloor :: #force_inline proc(x: f32) -> i32 { return i32(math.floor(x)) } @(private) iceil :: #force_inline proc(x: f32) -> i32 { return i32(math.ceil(x)) } // Coverage-accumulation rasterizer @(private) rasterize_glyph :: proc(result: ^Bitmap, flatness_in_pixels: f32, vertices: [^]Vertex, num_verts: i32, scale_x: f32, scale_y: f32, shift_x: f32, shift_y: f32, x_off: i32, y_off: i32, invert: i32) { w := int(result.w) h := int(result.h) if w == 0 || h == 0 do return STACK_SIZE :: 4096 stack_buf: [STACK_SIZE]f32 buf: [^]f32 buf_len := w * h + 3 heap_buf: rawptr = nil if buf_len <= STACK_SIZE { mem.zero(&stack_buf[0], buf_len * size_of(f32)) buf = &stack_buf[0] } else { ptr, _ := mem.alloc(buf_len * size_of(f32)) if ptr == nil do return heap_buf = ptr buf = ([^]f32)(ptr) mem.zero(ptr, buf_len * size_of(f32)) } defer if heap_buf != nil do mem.free(heap_buf) scale := min(scale_x, scale_y) flatness := flatness_in_pixels / scale flatness_sq := flatness * flatness y_dir: f32 = -1.0 if invert != 0 else 1.0 cx, cy: f32 #no_bounds_check for vi in 0..= f32(h) do return dxdy := (x1 - x0) / (y1 - y0) row_start := max(i32(y0), 0) row_end := min(i32(y1), h - 1) #no_bounds_check for row := row_start; row <= row_end; row += 1 { rt := f32(row) yt := max(y0, rt) yb := min(y1, rt + 1.0) if yt >= yb do continue height := (yb - yt) * sign xt := x0 + (yt - y0) * dxdy xb := x0 + (yb - y0) * dxdy mid := (xt + xb) * 0.5 col := clamp(i32(mid), 0, w - 1) frac := clamp(mid - f32(col), 0, 1) idx := int(row) * int(w) + int(col) m := height * frac buf[idx] += height - m if col + 1 < w { buf[idx + 1] += m } } } @(private) raster_quad :: proc(buf: [^]f32, w: i32, h: i32, x0: f32, y0: f32, cx: f32, cy: f32, x1: f32, y1: f32, flatness_sq: f32) { mx := (x0 + 2*cx + x1) / 4 my := (y0 + 2*cy + y1) / 4 dx := (x0+x1)/2 - mx dy := (y0+y1)/2 - my if dx*dx + dy*dy <= flatness_sq { raster_line(buf, w, h, x0, y0, x1, y1) return } cx01 := (x0 + cx) / 2; cy01 := (y0 + cy) / 2 cx12 := (cx + x1) / 2; cy12 := (cy + y1) / 2 mx2 := (cx01 + cx12) / 2; my2 := (cy01 + cy12) / 2 raster_quad(buf, w, h, x0, y0, cx01, cy01, mx2, my2, flatness_sq) raster_quad(buf, w, h, mx2, my2, cx12, cy12, x1, y1, flatness_sq) } @(private) raster_cubic :: proc(buf: [^]f32, w: i32, h: i32, x0: f32, y0: f32, x1: f32, y1: f32, x2: f32, y2: f32, x3: f32, y3: f32, flatness_sq: f32, depth: i32) { if depth > 16 do return dx := x3 - x0; dy := y3 - y0 dx0 := x1 - x0; dy0 := y1 - y0 dx1 := x2 - x1; dy1 := y2 - y1 dx2 := x3 - x2; dy2 := y3 - y2 longlen := math.sqrt(dx0*dx0+dy0*dy0) + math.sqrt(dx1*dx1+dy1*dy1) + math.sqrt(dx2*dx2+dy2*dy2) shortlen := math.sqrt(dx*dx+dy*dy) if (longlen - shortlen) * (longlen - shortlen) <= flatness_sq { raster_line(buf, w, h, x0, y0, x3, y3) return } x01 := (x0+x1)/2; y01 := (y0+y1)/2 x12 := (x1+x2)/2; y12 := (y1+y2)/2 x23 := (x2+x3)/2; y23 := (y2+y3)/2 xa := (x01+x12)/2; ya := (y01+y12)/2 xb := (x12+x23)/2; yb := (y12+y23)/2 mx := (xa+xb)/2; my := (ya+yb)/2 raster_cubic(buf, w, h, x0,y0, x01,y01, xa,ya, mx,my, flatness_sq, depth+1) raster_cubic(buf, w, h, mx,my, xb,yb, x23,y23, x3,y3, flatness_sq, depth+1) } // Phase 2.5 Functions - Public API get_glyph_bitmap_box_subpixel :: proc(font: ^Font_Info, glyph: i32, scale_x: f32, scale_y: f32, shift_x: f32, shift_y: f32, ix0: ^i32, iy0: ^i32, ix1: ^i32, iy1: ^i32) { x0, y0, x1, y1: i32 = 0, 0, 0, 0 if !get_glyph_box(font, glyph, &x0, &y0, &x1, &y1) { // e.g. space character if ix0 != nil do ix0^ = 0 if iy0 != nil do iy0^ = 0 if ix1 != nil do ix1^ = 0 if iy1 != nil do iy1^ = 0 } else { // move to integral bboxes (treating pixels as little squares, what pixels get touched)? if ix0 != nil do ix0^ = ifloor(f32(x0) * scale_x + shift_x) if iy0 != nil do iy0^ = ifloor(f32(-y1) * scale_y + shift_y) if ix1 != nil do ix1^ = iceil(f32(x1) * scale_x + shift_x) if iy1 != nil do iy1^ = iceil(f32(-y0) * scale_y + shift_y) } } get_glyph_bitmap_box :: proc(font: ^Font_Info, glyph: i32, scale_x: f32, scale_y: f32, ix0: ^i32, iy0: ^i32, ix1: ^i32, iy1: ^i32) { get_glyph_bitmap_box_subpixel(font, glyph, scale_x, scale_y, 0.0, 0.0, ix0, iy0, ix1, iy1) } get_codepoint_bitmap_box :: proc(font: ^Font_Info, codepoint: i32, scale_x: f32, scale_y: f32, ix0: ^i32, iy0: ^i32, ix1: ^i32, iy1: ^i32) { get_glyph_bitmap_box_subpixel(font, find_glyph_index(font, codepoint), scale_x, scale_y, 0.0, 0.0, ix0, iy0, ix1, iy1) } get_glyph_bitmap_subpixel :: proc(info: ^Font_Info, scale_x: f32, scale_y: f32, shift_x: f32, shift_y: f32, glyph: i32, width: ^i32, height: ^i32, xoff: ^i32, yoff: ^i32, allocator := context.allocator) -> [^]u8 { // Use arena allocator for intermediate allocations (optimization) // Stack-based backing to avoid heap allocation overhead arena_backing: [256 * 1024]u8 arena: mem.Arena mem.arena_init(&arena, arena_backing[:]) context.allocator = mem.arena_allocator(&arena) ix0, iy0, ix1, iy1: i32 gbm: Bitmap vertices_temp: ^Vertex = nil vertices: [^]Vertex = nil num_verts := get_glyph_shape(info, glyph, cast(^^Vertex)&vertices_temp) vertices = ([^]Vertex)(vertices_temp) 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 { // vertices allocated from arena, will be freed with arena_backing return nil } scale_y = scale_x } get_glyph_bitmap_box_subpixel(info, glyph, scale_x, scale_y, shift_x, shift_y, &ix0, &iy0, &ix1, &iy1) // now we get the size gbm.w = (ix1 - ix0) gbm.h = (iy1 - iy0) gbm.pixels = nil // in case we error 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 { // Use caller's allocator for the returned bitmap 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, num_verts, scale_x, scale_y, shift_x, shift_y, ix0, iy0, 1) } } // vertices allocated from arena, will be freed with arena_backing return gbm.pixels } get_glyph_bitmap :: proc(info: ^Font_Info, scale_x: f32, scale_y: f32, glyph: i32, width: ^i32, height: ^i32, xoff: ^i32, yoff: ^i32, allocator := context.allocator) -> [^]u8 { return get_glyph_bitmap_subpixel(info, scale_x, scale_y, 0.0, 0.0, glyph, width, height, xoff, yoff, allocator) } 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 { return get_glyph_bitmap_subpixel(info, scale_x, scale_y, 0.0, 0.0, find_glyph_index(info, codepoint), width, height, xoff, yoff, allocator) } // Render a variable glyph to bitmap at specific axis coordinates get_glyph_bitmap_var :: proc(info: ^Font_Info, scale_x: f32, scale_y: f32, glyph: i32, coords: ^Var_Coords, width: ^i32, height: ^i32, xoff: ^i32, yoff: ^i32, allocator := context.allocator) -> [^]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 num_verts := get_glyph_shape_var(info, glyph, coords, cast(^^Vertex)&vertices_temp) vertices := ([^]Vertex)(vertices_temp) 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, num_verts, scale_x, scale_y, 0, 0, ix0, iy0, 1) } } return gbm.pixels } make_glyph_bitmap_subpixel :: proc(info: ^Font_Info, output: [^]u8, out_w: i32, out_h: i32, out_stride: i32, scale_x: f32, scale_y: f32, shift_x: f32, shift_y: f32, glyph: i32) { // Use arena allocator for intermediate allocations (optimization) // Stack-based backing to avoid heap allocation overhead arena_backing: [256 * 1024]u8 arena: mem.Arena mem.arena_init(&arena, arena_backing[:]) context.allocator = mem.arena_allocator(&arena) ix0, iy0: i32 vertices_temp: ^Vertex = nil vertices: [^]Vertex = nil num_verts := get_glyph_shape(info, glyph, cast(^^Vertex)&vertices_temp) vertices = ([^]Vertex)(vertices_temp) gbm: Bitmap get_glyph_bitmap_box_subpixel(info, glyph, scale_x, scale_y, shift_x, shift_y, &ix0, &iy0, nil, nil) gbm.pixels = output gbm.w = out_w gbm.h = out_h gbm.stride = out_stride if gbm.w != 0 && gbm.h != 0 { rasterize_glyph(&gbm, 0.35, vertices, num_verts, scale_x, scale_y, shift_x, shift_y, ix0, iy0, 1) } // vertices allocated from arena, will be freed with arena_backing } make_glyph_bitmap :: proc(info: ^Font_Info, output: [^]u8, out_w: i32, out_h: i32, out_stride: i32, scale_x: f32, scale_y: f32, glyph: i32) { make_glyph_bitmap_subpixel(info, output, out_w, out_h, out_stride, scale_x, scale_y, 0.0, 0.0, glyph) } 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) { make_glyph_bitmap_subpixel(info, output, out_w, out_h, out_stride, scale_x, scale_y, 0.0, 0.0, find_glyph_index(info, codepoint)) } // Render glyph with oversampling and apply prefiltering for smoother results. // The prefilter_x/y values are the oversample factors (1-8). // sub_x/sub_y receive the subpixel shift values for proper glyph positioning. make_glyph_bitmap_subpixel_prefilter :: proc( info: ^Font_Info, output: [^]u8, out_w: i32, out_h: i32, out_stride: i32, scale_x: f32, scale_y: f32, shift_x: f32, shift_y: f32, prefilter_x: i32, prefilter_y: i32, sub_x: ^f32, sub_y: ^f32, glyph: i32, ) { // Render at reduced dimensions (prefiltering expands back to full size) make_glyph_bitmap_subpixel(info, output, out_w - (prefilter_x - 1), out_h - (prefilter_y - 1), out_stride, scale_x, scale_y, shift_x, shift_y, glyph) // Apply horizontal prefilter if prefilter_x > 1 { h_prefilter(output, out_w, out_h, out_stride, u32(prefilter_x)) } // Apply vertical prefilter if prefilter_y > 1 { v_prefilter(output, out_w, out_h, out_stride, u32(prefilter_y)) } // Return computed subpixel offsets sub_x^ = oversample_shift(prefilter_x) sub_y^ = oversample_shift(prefilter_y) } make_codepoint_bitmap_subpixel_prefilter :: proc( info: ^Font_Info, output: [^]u8, out_w: i32, out_h: i32, out_stride: i32, scale_x: f32, scale_y: f32, shift_x: f32, shift_y: f32, prefilter_x: i32, prefilter_y: i32, sub_x: ^f32, sub_y: ^f32, codepoint: i32, ) { make_glyph_bitmap_subpixel_prefilter(info, output, out_w, out_h, out_stride, scale_x, scale_y, shift_x, shift_y, prefilter_x, prefilter_y, sub_x, sub_y, find_glyph_index(info, codepoint)) } free_bitmap :: proc(bitmap: [^]u8) { mem.free(bitmap) }