568caf21
feat(font): fontconfig monospace lookup
a73x 2026-04-08 06:19
Commit message
build.zig
| Old | New | ||
|---|---|---|---|
| @@ -90,4 +90,29 @@ pub fn build(b: *std.Build) void { | |||
| 90 | .root_module = vt_test_mod, | 90 | .root_module = vt_test_mod, |
| 91 | }); | 91 | }); |
| 92 | test_step.dependOn(&b.addRunArtifact(vt_tests).step); | 92 | test_step.dependOn(&b.addRunArtifact(vt_tests).step); |
| 93 | |||
| 94 | // font module — fontconfig lookup + freetype rasterization + glyph atlas | ||
| 95 | const font_mod = b.createModule(.{ | ||
| 96 | .root_source_file = b.path("src/font.zig"), | ||
| 97 | .target = target, | ||
| 98 | .optimize = optimize, | ||
| 99 | .link_libc = true, | ||
| 100 | }); | ||
| 101 | font_mod.linkSystemLibrary("fontconfig", .{}); | ||
| 102 | font_mod.linkSystemLibrary("freetype2", .{}); | ||
| 103 | exe_mod.addImport("font", font_mod); | ||
| 104 | |||
| 105 | // Test font.zig | ||
| 106 | const font_test_mod = b.createModule(.{ | ||
| 107 | .root_source_file = b.path("src/font.zig"), | ||
| 108 | .target = target, | ||
| 109 | .optimize = optimize, | ||
| 110 | .link_libc = true, | ||
| 111 | }); | ||
| 112 | font_test_mod.linkSystemLibrary("fontconfig", .{}); | ||
| 113 | font_test_mod.linkSystemLibrary("freetype2", .{}); | ||
| 114 | const font_tests = b.addTest(.{ | ||
| 115 | .root_module = font_test_mod, | ||
| 116 | }); | ||
| 117 | test_step.dependOn(&b.addRunArtifact(font_tests).step); | ||
| 93 | } | 118 | } |
src/font.zig
| Old | New | ||
|---|---|---|---|
| @@ -0,0 +1,54 @@ | |||
| 1 | const std = @import("std"); | ||
| 2 | const c = @cImport({ | ||
| 3 | @cInclude("fontconfig/fontconfig.h"); | ||
| 4 | @cInclude("ft2build.h"); | ||
| 5 | @cInclude("freetype/freetype.h"); | ||
| 6 | }); | ||
| 7 | |||
| 8 | pub const FontLookup = struct { | ||
| 9 | path: [:0]u8, | ||
| 10 | index: c_int, | ||
| 11 | |||
| 12 | pub fn deinit(self: *FontLookup, alloc: std.mem.Allocator) void { | ||
| 13 | alloc.free(self.path); | ||
| 14 | } | ||
| 15 | }; | ||
| 16 | |||
| 17 | pub fn lookupMonospace(alloc: std.mem.Allocator) !FontLookup { | ||
| 18 | if (c.FcInit() == c.FcFalse) return error.FcInitFailed; | ||
| 19 | |||
| 20 | const pattern = c.FcPatternCreate() orelse return error.FcPatternCreate; | ||
| 21 | defer c.FcPatternDestroy(pattern); | ||
| 22 | |||
| 23 | _ = c.FcPatternAddString(pattern, c.FC_FAMILY, @ptrCast("monospace")); | ||
| 24 | _ = c.FcPatternAddInteger(pattern, c.FC_WEIGHT, c.FC_WEIGHT_REGULAR); | ||
| 25 | _ = c.FcPatternAddInteger(pattern, c.FC_SLANT, c.FC_SLANT_ROMAN); | ||
| 26 | |||
| 27 | _ = c.FcConfigSubstitute(null, pattern, c.FcMatchPattern); | ||
| 28 | c.FcDefaultSubstitute(pattern); | ||
| 29 | |||
| 30 | var result: c.FcResult = undefined; | ||
| 31 | const matched = c.FcFontMatch(null, pattern, &result) orelse return error.FcFontMatchFailed; | ||
| 32 | defer c.FcPatternDestroy(matched); | ||
| 33 | |||
| 34 | var file_cstr: [*c]c.FcChar8 = null; | ||
| 35 | if (c.FcPatternGetString(matched, c.FC_FILE, 0, &file_cstr) != c.FcResultMatch) { | ||
| 36 | return error.FcGetFileFailed; | ||
| 37 | } | ||
| 38 | |||
| 39 | var index: c_int = 0; | ||
| 40 | _ = c.FcPatternGetInteger(matched, c.FC_INDEX, 0, &index); | ||
| 41 | |||
| 42 | const slice = std.mem.span(@as([*:0]const u8, @ptrCast(file_cstr))); | ||
| 43 | const dup = try alloc.dupeZ(u8, slice); | ||
| 44 | return .{ .path = dup, .index = index }; | ||
| 45 | } | ||
| 46 | |||
| 47 | test "lookupMonospace returns a valid font path" { | ||
| 48 | var lookup = try lookupMonospace(std.testing.allocator); | ||
| 49 | defer lookup.deinit(std.testing.allocator); | ||
| 50 | |||
| 51 | // Just check the file exists | ||
| 52 | const file = try std.fs.openFileAbsolute(lookup.path, .{}); | ||
| 53 | file.close(); | ||
| 54 | } | ||