Something went wrong. Try again.
A history browser for git, a replacement for gitk, drawn with Vulkan on Wayland.
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128// SPDX-FileCopyrightText: © 2026 Jeffrey C. Ollie <jeff@ocjtech.us>// SPDX-License-Identifier: MIT
//! The fonts grebe draws with, and lines of text drawn in them: one for//! the commit list and everything else of the interface, and a monospace//! one for diffs.
const std = @import("std");const Allocator = std.mem.Allocator;const aerie = @import("aerie");const Canvas = aerie.gpu.Canvas;const text = aerie.text;const Shaper = text.Shaper;
const Text = @This();
gpa: Allocator,fonts: *text.Fonts,families: [2]text.Family,shaper: text.Shaper,glyphs: text.Glyphs,/// Scratch for a line being drawn.shaped: std.ArrayList(text.Shaper.Glyph) = .empty,/// Pixels the families are drawn at, before scaling.base_px: [2]f32,scale: f32 = 1,
pub const Face = enum(u1) { ui, mono };
/// A family, by its fontconfig name, at a size in pixels before scaling.pub const Spec = struct { family: []const u8, px: f32,};
pub fn init(gpa: Allocator, fonts: *text.Fonts, specs: [2]Spec) !Text { var ui: text.Family = try .init(gpa, fonts, specs[0].family, specs[0].px); errdefer ui.deinit(); var mono: text.Family = try .init(gpa, fonts, specs[1].family, specs[1].px); errdefer mono.deinit(); var shaper: text.Shaper = try .init(gpa, .{}); errdefer shaper.deinit(); return .{ .gpa = gpa, .fonts = fonts, .families = .{ ui, mono }, .shaper = shaper, .glyphs = try .init(gpa), .base_px = .{ specs[0].px, specs[1].px }, };}
/// Draws in `specs` from now on: every glyph is rasterized again. On/// failure, keeps the families it had.pub fn setFamilies(t: *Text, specs: [2]Spec) !void { var ui: text.Family = try .init(t.gpa, t.fonts, specs[0].family, specs[0].px * t.scale); errdefer ui.deinit(); var mono: text.Family = try .init(t.gpa, t.fonts, specs[1].family, specs[1].px * t.scale); errdefer mono.deinit(); for (&t.families) |*f| f.deinit(); t.families = .{ ui, mono }; t.base_px = .{ specs[0].px, specs[1].px }; t.glyphs.clear();}
pub fn deinit(t: *Text) void { t.shaped.deinit(t.gpa); t.glyphs.deinit(); t.shaper.deinit(); for (&t.families) |*f| f.deinit(); t.* = undefined;}
pub fn family(t: *Text, face: Face) *text.Family { return &t.families[@backingInt(face)];}
/// Draws at `scale` from now on: every glyph is rasterized again.pub fn setScale(t: *Text, scale: f32) !void { t.scale = scale; for (&t.families, t.base_px) |*f, px| try f.setSize(px * scale); t.glyphs.clear();}
/// The height of a line in `face`.pub fn lineHeight(t: *Text, face: Face) f32 { return @floatFromInt(t.family(face).metrics.cell_height);}
/// Where the baseline goes for a line of `face` centered in `height`,/// down from its top.pub fn baselineIn(t: *Text, face: Face, height: f32) f32 { const m = t.family(face).metrics; return @round((height - @as(f32, @floatFromInt(m.cell_height))) / 2) + @as(f32, @floatFromInt(m.baseline));}
pub fn measure(t: *Text, face: Face, s: []const u8) !f32 { const f = t.family(face); return t.shaper.measure(f, f.size_px, .regular, s);}
/// The byte of `s` nearest `x` pixels from its start, on a code point's/// boundary: where a click in it puts a caret.pub fn offsetAt(t: *Text, face: Face, s: []const u8, x: f32) !usize { const f = t.family(face); t.shaped.clearRetainingCapacity(); const w = try t.shaper.shape(f, f.size_px, .regular, s, &t.shaped); return Shaper.offsetAt(t.shaped.items, w, s.len, x);}
/// How far into `s`, in pixels, its byte `offset` is.pub fn xOf(t: *Text, face: Face, s: []const u8, offset: usize) !f32 { const f = t.family(face); t.shaped.clearRetainingCapacity(); const w = try t.shaper.shape(f, f.size_px, .regular, s, &t.shaped); return Shaper.xOf(t.shaped.items, w, offset);}
/// Draws `s` with its origin at `x` on the baseline `baseline`. Returns/// its width.pub fn draw(t: *Text, canvas: *Canvas, face: Face, s: []const u8, x: f32, baseline: f32, color: [4]u8) !f32 { const f = t.family(face); t.shaped.clearRetainingCapacity(); const w = try t.shaper.shape(f, f.size_px, .regular, s, &t.shaped); try canvas.text(&t.glyphs, t.shaped.items, f.size_px, x, baseline, color); return w;}