media: add stbtt binding

This commit is contained in:
2026-09-13 02:02:59 +02:00
parent 70076f2268
commit ff87d58319
5 changed files with 5336 additions and 2 deletions

View File

@@ -17,8 +17,11 @@ pub fn build(b: *std.Build) void {
}, },
}); });
mod.addCSourceFile(.{ mod.addCSourceFiles(.{
.file = b.path("src/stbi/stb_image.c"), .files = &.{
"src/stbi/stb_image.c",
"src/stbtt/stb_truetype.c",
},
.flags = &.{ .flags = &.{
"-std=c17", "-std=c17",
"-Wall", "-Wall",

View File

@@ -9,6 +9,7 @@ pub const png = @import("png.zig");
pub const qoa = @import("qoa.zig"); pub const qoa = @import("qoa.zig");
pub const qoi = @import("qoi.zig"); pub const qoi = @import("qoi.zig");
pub const stbi = @import("stbi.zig"); pub const stbi = @import("stbi.zig");
pub const stbtt = @import("stbtt.zig");
test "refAllDecls" { test "refAllDecls" {
std.testing.refAllDecls(@This()); std.testing.refAllDecls(@This());

View File

@@ -0,0 +1,227 @@
const std = @import("std");
const Self = @This();
allocator: std.mem.Allocator,
io: std.Io,
mutex: std.Io.Mutex = .init,
allocations: std.AutoHashMapUnmanaged(*anyopaque, usize) = .empty,
allocated_bytes: usize = 0,
const alignment: std.mem.Alignment = .@"16";
const VoidPtr = ?*align(alignment.toByteUnits()) anyopaque;
const log = std.log.scoped(.stbi);
pub fn init(allocator: std.mem.Allocator, io: std.Io) Self {
return .{
.allocator = allocator,
.io = io,
};
}
pub fn deinit(self: *Self) void {
std.log.scoped(.deinit).debug("Deinitializing {*}", .{self});
if (self.allocated_bytes > 0) {
log.warn("{d} byte(s) still allocated while deinitializing", .{self.allocated_bytes});
}
if (self.allocations.size > 0) {
log.warn("{d} allocation(s) still tracked while deinitializing", .{self.allocations.size});
var it = self.allocations.iterator();
var index: usize = 0;
while (it.next()) |entry| : (index += 1) {
log.warn("Leaked allocation ({d}/{d}) at 0x{x} of {d} byte(s)", .{
index + 1,
self.allocations.size,
@intFromPtr(entry.key_ptr.*),
entry.value_ptr.*,
});
const memory = @as([*]align(alignment.toByteUnits()) u8, @ptrCast(@alignCast(entry.key_ptr.*)))[0..entry.value_ptr.*];
self.allocator.free(memory);
}
}
self.allocations.deinit(self.allocator);
}
const import = struct {
const stbtt__buf = extern struct {
data: ?[*]const u8,
cursor: c_int,
size: c_int,
};
const stbtt_fontinfo = extern struct {
userdata: ?*anyopaque,
data: [*]const u8,
fonstart: c_int,
numGlyphs: c_int,
loca: c_int,
head: c_int,
glyf: c_int,
hhea: c_int,
hmtx: c_int,
kern: c_int,
gpos: c_int,
svg: c_int,
index_map: c_int,
indexToLocFormat: c_int,
cff: stbtt__buf,
charstrings: stbtt__buf,
gsubrs: stbtt__buf,
subrs: stbtt__buf,
fontdicts: stbtt__buf,
fdselect: stbtt__buf,
};
const stbtt_kerningentry = extern struct {
glyph1: c_int,
glyph2: c_int,
advance: c_int,
};
const STBTT_vmove = enum(u8) {
STBTT_vmove = 1,
STBTT_vline,
STBTT_vcurve,
STBTT_vcubic,
};
const stbtt_vertex = extern struct {
x: i16,
y: i16,
cx: i16,
cy: i16,
cx1: i16,
cy1: i16,
type: STBTT_vmove,
padding: u8,
};
extern fn stbtt_GetNumberOfFonts(data: [*]const u8) c_int;
extern fn stbtt_GetFontOffsetForIndex(index: c_int, data: [*]const u8) c_int;
extern fn stbtt_InitFont(info: *stbtt_fontinfo, data: [*]const u8, offset: c_int) c_int;
extern fn stbtt_FindGlyphIndex(info: *const stbtt_fontinfo, unicode_codepoint: c_int) c_int;
extern fn stbtt_GetFontVMetrics(info: *const stbtt_fontinfo, ascent: ?*c_int, descent: ?*c_int, lineGap: ?*c_int) void;
extern fn stbtt_GetFontVMetricsOS2(info: *const stbtt_fontinfo, typoAscent: ?*c_int, typoDescent: ?*c_int, typoLineGap: ?*c_int) c_int;
extern fn stbtt_GetGlyphHMetrics(info: *const stbtt_fontinfo, glyph_index: c_int, advanceWidth: *c_int, leftSideBearing: *c_int) void;
extern fn stbtt_GetGlyphKernAdvance(info: *const stbtt_fontinfo, glyph1: c_int, glyph2: c_int) c_int;
extern fn stbtt_GetGlyphBox(info: *const stbtt_fontinfo, glyph_index: c_int, x0: *c_int, y0: *c_int, x1: *c_int, y1: *c_int) c_int;
extern fn stbtt_GetKerningTableLength(info: *const stbtt_fontinfo) c_int;
extern fn stbtt_GetKerningTable(info: *const stbtt_fontinfo, table: [*]stbtt_kerningentry, table_length: c_int) c_int;
extern fn stbtt_IsGlyphEmpty(info: *const stbtt_fontinfo, glyph_index: c_int) c_int;
extern fn stbtt_GetGlyphShape(info: *const stbtt_fontinfo, glyph_index: c_int, vertices: *[*]stbtt_vertex) c_int;
extern fn stbtt_FreeShape(info: *const stbtt_fontinfo, vertices: [*]stbtt_vertex) void;
extern fn stbtt_UnitsPerEm(info: *const stbtt_fontinfo) c_int;
extern fn stbtt_GetFontCapHeight(info: *const stbtt_fontinfo, capHeight: ?*c_int) c_int;
};
pub fn initFont(self: *Self, data: []const u8) !*Font {
const info = try self.allocator.create(import.stbtt_fontinfo);
errdefer self.allocator.destroy(info);
info.userdata = self;
const res = import.stbtt_InitFont(info, data.ptr, 0);
if (res == 0) return error.StbttError;
return @ptrCast(info);
}
pub const Font = opaque {
pub fn deinit(self: *Font) void {
const info = @as(*import.stbtt_fontinfo, @ptrCast(@alignCast(self)));
const stbtt = @as(*Self, @ptrCast(@alignCast(info.userdata.?)));
stbtt.allocator.destroy(info);
}
pub fn findGlyphIndex(self: *const Font, codepoint: u21) ?u32 {
const info = @as(*const import.stbtt_fontinfo, @ptrCast(@alignCast(self)));
const res = import.stbtt_FindGlyphIndex(info, codepoint);
return if (res != 0) @intCast(res) else null;
}
pub fn getGlyphBox(self: *const Font, glyph: u32) ?GlyphBox {
const info = @as(*const import.stbtt_fontinfo, @ptrCast(@alignCast(self)));
var x0: c_int = undefined;
var y0: c_int = undefined;
var x1: c_int = undefined;
var y1: c_int = undefined;
const res = import.stbtt_GetGlyphBox(info, @intCast(glyph), &x0, &y0, &x1, &y1);
return if (res != 0) .{
.x_min = @intCast(x0),
.y_min = @intCast(y0),
.x_max = @intCast(x1),
.y_max = @intCast(y1),
} else null;
}
pub fn unitsPerEm(self: *const Font) u16 {
const info = @as(*const import.stbtt_fontinfo, @ptrCast(@alignCast(self)));
const res = import.stbtt_UnitsPerEm(info);
return @intCast(res);
}
pub fn getCapHeight(self: *const Font) ?i16 {
const info = @as(*const import.stbtt_fontinfo, @ptrCast(@alignCast(self)));
var cap_height: c_int = undefined;
if (import.stbtt_GetFontCapHeight(info, &cap_height) != 0) {
return @intCast(cap_height);
}
if (self.findGlyphIndex('H')) |glyph| {
if (self.getGlyphBox(glyph)) |box| {
return box.y_max;
}
}
return null;
}
test "refAllDecls" {
std.testing.refAllDecls(@This());
}
};
pub const GlyphBox = struct {
x_min: i16,
y_min: i16,
x_max: i16,
y_max: i16,
};
// --- MALLOC INTERFACE --------------------------------------------------------
export fn castle_media_stbtt_malloc(self: *Self, size: usize) callconv(.c) VoidPtr {
self.mutex.lock(self.io) catch return null;
defer self.mutex.unlock(self.io);
self.allocations.ensureUnusedCapacity(self.allocator, 1) catch return null;
const memory = self.allocator.alignedAlloc(u8, alignment, size) catch return null;
self.allocations.putAssumeCapacityNoClobber(memory.ptr, size);
self.allocated_bytes += size;
//log.debug("Allocated {d} bytes(s) at 0x{x}", .{ size, @intFromPtr(memory.ptr) });
return memory.ptr;
}
export fn castle_media_stbtt_free(self: *Self, maybe_ptr: VoidPtr) callconv(.c) void {
if (maybe_ptr) |ptr| {
self.mutex.lockUncancelable(self.io);
defer self.mutex.unlock(self.io);
const size = self.allocations.fetchRemove(ptr).?.value;
self.allocated_bytes -= size;
const memory = @as([*]align(alignment.toByteUnits()) u8, @ptrCast(ptr))[0..size];
self.allocator.free(memory);
}
}
test "refAllDecls" {
std.testing.refAllDecls(@This());
}

View File

@@ -0,0 +1,24 @@
#include <stddef.h>
void *castle_media_stbtt_malloc(void *self, size_t size);
void castle_media_stbtt_free(void *self, void *ptr);
#define STBTT_malloc(size, self) castle_media_stbtt_malloc(self, size)
#define STBTT_free(ptr, self) castle_media_stbtt_free(self, ptr)
#define STB_TRUETYPE_IMPLEMENTATION
#include "stb_truetype.h"
STBTT_DEF int stbtt_UnitsPerEm(const stbtt_fontinfo *info)
{
return ttUSHORT(info->data + info->head + 18);
}
STBTT_DEF int stbtt_GetFontCapHeight(const stbtt_fontinfo *info, int *capHeight)
{
int tab = stbtt__find_table(info->data, info->fontstart, "OS/2");
if (!tab || ttUSHORT(info->data+tab) < 2)
return 0;
if (capHeight) *capHeight = ttSHORT(info->data+tab + 88);
return 1;
}

File diff suppressed because it is too large Load Diff