diff --git a/build.zig b/build.zig index 65c97662..035b480f 100644 --- a/build.zig +++ b/build.zig @@ -606,6 +606,7 @@ pub fn build_exe( .{ .name = "app", .module = app_mod }, .{ .name = "tuirenderer", .module = tui_renderer_mod }, .{ .name = "vaxis", .module = vaxis_mod }, + .{ .name = "rasterizer", .module = truetype_rasterizer_mod }, }, }); break :blk mod; diff --git a/src/gui/rasterizer/font_finder.zig b/src/gui/rasterizer/font_finder.zig index 4bb42041..4ad8f875 100644 --- a/src/gui/rasterizer/font_finder.zig +++ b/src/gui/rasterizer/font_finder.zig @@ -7,3 +7,12 @@ pub fn findFont(allocator: std.mem.Allocator, name: []const u8) ![]u8 { 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, + }; +} diff --git a/src/gui/rasterizer/font_finder/linux.zig b/src/gui/rasterizer/font_finder/linux.zig index ffa824ee..8800402a 100644 --- a/src/gui/rasterizer/font_finder/linux.zig +++ b/src/gui/rasterizer/font_finder/linux.zig @@ -3,6 +3,56 @@ const fc = @cImport({ @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 { const config = fc.FcInitLoadConfigAndFonts() orelse return error.FontconfigInit; defer fc.FcConfigDestroy(config); diff --git a/src/gui/rasterizer/truetype.zig b/src/gui/rasterizer/truetype.zig index 565fdb11..416e895c 100644 --- a/src/gui/rasterizer/truetype.zig +++ b/src/gui/rasterizer/truetype.zig @@ -1,7 +1,7 @@ const std = @import("std"); const TrueType = @import("TrueType"); const XY = @import("xy").XY; -const font_finder = @import("font_finder.zig"); +pub const font_finder = @import("font_finder.zig"); const Self = @This(); diff --git a/src/gui/wio/app.zig b/src/gui/wio/app.zig index 7891e8a1..3237ea87 100644 --- a/src/gui/wio/app.zig +++ b/src/gui/wio/app.zig @@ -202,6 +202,10 @@ pub fn setMouseCursor(shape: vaxis.Mouse.Shape) void { 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 { attention_pending.store(true, .release); wio.cancelWait(); diff --git a/src/renderer/gui/renderer.zig b/src/renderer/gui/renderer.zig index 41ca35fe..3890fcea 100644 --- a/src/renderer/gui/renderer.zig +++ b/src/renderer/gui/renderer.zig @@ -381,7 +381,36 @@ pub fn reset_fontface(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 {