568caf21
feat(font): fontconfig monospace lookup
a73x 2026-04-08 06:19
diff --git a/build.zig b/build.zig index 0f315c7..591d705 100644 --- a/build.zig +++ b/build.zig @@ -90,4 +90,29 @@ pub fn build(b: *std.Build) void { .root_module = vt_test_mod, }); test_step.dependOn(&b.addRunArtifact(vt_tests).step); // font module — fontconfig lookup + freetype rasterization + glyph atlas const font_mod = b.createModule(.{ .root_source_file = b.path("src/font.zig"), .target = target, .optimize = optimize, .link_libc = true, }); font_mod.linkSystemLibrary("fontconfig", .{}); font_mod.linkSystemLibrary("freetype2", .{}); exe_mod.addImport("font", font_mod); // Test font.zig const font_test_mod = b.createModule(.{ .root_source_file = b.path("src/font.zig"), .target = target, .optimize = optimize, .link_libc = true, }); font_test_mod.linkSystemLibrary("fontconfig", .{}); font_test_mod.linkSystemLibrary("freetype2", .{}); const font_tests = b.addTest(.{ .root_module = font_test_mod, }); test_step.dependOn(&b.addRunArtifact(font_tests).step); } diff --git a/src/font.zig b/src/font.zig new file mode 100644 index 0000000..7dcb9ca --- /dev/null +++ b/src/font.zig @@ -0,0 +1,54 @@ const std = @import("std"); const c = @cImport({ @cInclude("fontconfig/fontconfig.h"); @cInclude("ft2build.h"); @cInclude("freetype/freetype.h"); }); pub const FontLookup = struct { path: [:0]u8, index: c_int, pub fn deinit(self: *FontLookup, alloc: std.mem.Allocator) void { alloc.free(self.path); } }; pub fn lookupMonospace(alloc: std.mem.Allocator) !FontLookup { if (c.FcInit() == c.FcFalse) return error.FcInitFailed; const pattern = c.FcPatternCreate() orelse return error.FcPatternCreate; defer c.FcPatternDestroy(pattern); _ = c.FcPatternAddString(pattern, c.FC_FAMILY, @ptrCast("monospace")); _ = c.FcPatternAddInteger(pattern, c.FC_WEIGHT, c.FC_WEIGHT_REGULAR); _ = c.FcPatternAddInteger(pattern, c.FC_SLANT, c.FC_SLANT_ROMAN); _ = c.FcConfigSubstitute(null, pattern, c.FcMatchPattern); c.FcDefaultSubstitute(pattern); var result: c.FcResult = undefined; const matched = c.FcFontMatch(null, pattern, &result) orelse return error.FcFontMatchFailed; defer c.FcPatternDestroy(matched); var file_cstr: [*c]c.FcChar8 = null; if (c.FcPatternGetString(matched, c.FC_FILE, 0, &file_cstr) != c.FcResultMatch) { return error.FcGetFileFailed; } var index: c_int = 0; _ = c.FcPatternGetInteger(matched, c.FC_INDEX, 0, &index); const slice = std.mem.span(@as([*:0]const u8, @ptrCast(file_cstr))); const dup = try alloc.dupeZ(u8, slice); return .{ .path = dup, .index = index }; } test "lookupMonospace returns a valid font path" { var lookup = try lookupMonospace(std.testing.allocator); defer lookup.deinit(std.testing.allocator); // Just check the file exists const file = try std.fs.openFileAbsolute(lookup.path, .{}); file.close(); }