feat(gui): implement get_fontfaces

This commit is contained in:
CJ van den Berg 2026-04-01 11:13:39 +02:00
parent 6faea2ef02
commit 4ca31b0f75
Signed by: neurocyte
GPG key ID: 8EB1E1BB660E3FB9
6 changed files with 95 additions and 2 deletions

View file

@ -606,6 +606,7 @@ pub fn build_exe(
.{ .name = "app", .module = app_mod }, .{ .name = "app", .module = app_mod },
.{ .name = "tuirenderer", .module = tui_renderer_mod }, .{ .name = "tuirenderer", .module = tui_renderer_mod },
.{ .name = "vaxis", .module = vaxis_mod }, .{ .name = "vaxis", .module = vaxis_mod },
.{ .name = "rasterizer", .module = truetype_rasterizer_mod },
}, },
}); });
break :blk mod; break :blk mod;

View file

@ -7,3 +7,12 @@ pub fn findFont(allocator: std.mem.Allocator, name: []const u8) ![]u8 {
else => error.FontFinderNotSupported, else => error.FontFinderNotSupported,
}; };
} }
/// Returns a sorted, deduplicated list of monospace font family names.
/// Caller owns the returned slice and each string within it.
pub fn listFonts(allocator: std.mem.Allocator) ![][]u8 {
return switch (builtin.os.tag) {
.linux => @import("font_finder/linux.zig").list(allocator),
else => error.FontFinderNotSupported,
};
}

View file

@ -3,6 +3,56 @@ const fc = @cImport({
@cInclude("fontconfig/fontconfig.h"); @cInclude("fontconfig/fontconfig.h");
}); });
/// Returns a sorted, deduplicated list of monospace font family names.
/// Caller owns the returned slice and each string within it.
pub fn list(allocator: std.mem.Allocator) ![][]u8 {
const config = fc.FcInitLoadConfigAndFonts() orelse return error.FontconfigInit;
defer fc.FcConfigDestroy(config);
const pat = fc.FcPatternCreate() orelse return error.OutOfMemory;
defer fc.FcPatternDestroy(pat);
_ = fc.FcPatternAddInteger(pat, fc.FC_SPACING, fc.FC_MONO);
const os = fc.FcObjectSetCreate() orelse return error.OutOfMemory;
defer fc.FcObjectSetDestroy(os);
_ = fc.FcObjectSetAdd(os, fc.FC_FAMILY);
const font_set = fc.FcFontList(config, pat, os) orelse return error.OutOfMemory;
defer fc.FcFontSetDestroy(font_set);
var names: std.ArrayList([]u8) = .empty;
errdefer {
for (names.items) |n| allocator.free(n);
names.deinit(allocator);
}
for (0..@intCast(font_set.*.nfont)) |i| {
var family: [*c]fc.FcChar8 = undefined;
if (fc.FcPatternGetString(font_set.*.fonts[i], fc.FC_FAMILY, 0, &family) != fc.FcResultMatch)
continue;
try names.append(allocator, try allocator.dupe(u8, std.mem.sliceTo(family, 0)));
}
const result = try names.toOwnedSlice(allocator);
std.mem.sort([]u8, result, {}, struct {
fn lessThan(_: void, a: []u8, b: []u8) bool {
return std.ascii.lessThanIgnoreCase(a, b);
}
}.lessThan);
// Remove adjacent duplicates that survived the sort.
var w: usize = 0;
for (result) |name| {
if (w == 0 or !std.ascii.eqlIgnoreCase(result[w - 1], name)) {
result[w] = name;
w += 1;
} else {
allocator.free(name);
}
}
return result[0..w];
}
pub fn find(allocator: std.mem.Allocator, name: []const u8) ![]u8 { pub fn find(allocator: std.mem.Allocator, name: []const u8) ![]u8 {
const config = fc.FcInitLoadConfigAndFonts() orelse return error.FontconfigInit; const config = fc.FcInitLoadConfigAndFonts() orelse return error.FontconfigInit;
defer fc.FcConfigDestroy(config); defer fc.FcConfigDestroy(config);

View file

@ -1,7 +1,7 @@
const std = @import("std"); const std = @import("std");
const TrueType = @import("TrueType"); const TrueType = @import("TrueType");
const XY = @import("xy").XY; const XY = @import("xy").XY;
const font_finder = @import("font_finder.zig"); pub const font_finder = @import("font_finder.zig");
const Self = @This(); const Self = @This();

View file

@ -202,6 +202,10 @@ pub fn setMouseCursor(shape: vaxis.Mouse.Shape) void {
wio.cancelWait(); wio.cancelWait();
} }
pub fn getFontName() []const u8 {
return if (font_name_len > 0) font_name_buf[0..font_name_len] else "monospace";
}
pub fn requestAttention() void { pub fn requestAttention() void {
attention_pending.store(true, .release); attention_pending.store(true, .release);
wio.cancelWait(); wio.cancelWait();

View file

@ -381,7 +381,36 @@ pub fn reset_fontface(self: *Self) void {
} }
pub fn get_fontfaces(self: *Self) void { pub fn get_fontfaces(self: *Self) void {
_ = self; const font_finder = @import("rasterizer").font_finder;
const dispatch = self.dispatch_event orelse return;
// Report the current font first.
if (self.fmtmsg(.{ "fontface", "current", app.getFontName() })) |msg|
dispatch(self.handler_ctx, msg)
else |_| {}
// Enumerate all available monospace fonts and report each one.
const names = font_finder.listFonts(self.allocator) catch {
// If enumeration fails, still close the palette with "done".
if (self.fmtmsg(.{ "fontface", "done" })) |msg|
dispatch(self.handler_ctx, msg)
else |_| {}
return;
};
defer {
for (names) |n| self.allocator.free(n);
self.allocator.free(names);
}
for (names) |name| {
if (self.fmtmsg(.{ "fontface", name })) |msg|
dispatch(self.handler_ctx, msg)
else |_| {}
}
if (self.fmtmsg(.{ "fontface", "done" })) |msg|
dispatch(self.handler_ctx, msg)
else |_| {}
} }
pub fn set_terminal_cursor_color(self: *Self, color: Color) void { pub fn set_terminal_cursor_color(self: *Self, color: Color) void {