summaryrefslogtreecommitdiff
path: root/src/gui/gui.zig
diff options
context:
space:
mode:
Diffstat (limited to 'src/gui/gui.zig')
-rw-r--r--src/gui/gui.zig60
1 files changed, 56 insertions, 4 deletions
diff --git a/src/gui/gui.zig b/src/gui/gui.zig
index 98a203a6..e3229b38 100644
--- a/src/gui/gui.zig
+++ b/src/gui/gui.zig
@@ -2453,6 +2453,7 @@ fn localSession(
core.native_image_patches = true;
observeGuiFont(g, core);
syncTaglineFont(g, core);
+ syncLigatures(g, core);
var prompt_rcs = host_io.Shell.prepare();
defer prompt_rcs.deinit();
@@ -2580,6 +2581,7 @@ fn localSession(
shell.surface = null;
observeGuiFont(g, core);
syncTaglineFont(g, core);
+ syncLigatures(g, core);
}
}
@@ -3966,6 +3968,7 @@ fn pollFrame(ctx: ?*anyopaque) void {
};
syncWindowOpacity(g, core);
syncTaglineFont(g, core);
+ syncLigatures(g, core);
if (core.takeFontRequest()) |path| blk: {
const bytes = filesystem.readFile(s.gpa, path) catch {
core.rejectFont();
@@ -6056,6 +6059,17 @@ fn resetGlyphAtlas(g: *Gui) void {
g.atlas_dirty = true;
}
+/// A changed Ligatures setting drops what was resolved under the old one: the
+/// per-codepoint cells (which say whether a cell is shaped) and the shaped
+/// words. The atlas keeps its glyphs, plain and strip alike; the plain ones
+/// draw either way, and the strips come back into use when ligatures do.
+/// The frame this runs before redraws every cell with the new setting.
+fn syncLigatures(g: *Gui, core: *pardes.Pardes) void {
+ if (core.settings.ligatures == g.words.ligatures) return;
+ g.words.clear();
+ g.words.ligatures = core.settings.ligatures;
+}
+
fn syncTaglineFont(g: *Gui, core: *pardes.Pardes) void {
const percent = core.settings.font.tagline_percent;
if (percent == g.tagline_percent) return;
@@ -6083,6 +6097,10 @@ const Words = struct {
text: std.ArrayListUnmanaged(u32) = .empty,
slots: std.ArrayListUnmanaged(Slot) = .empty,
shaped: std.ArrayListUnmanaged(c.UIShapedCell) = .empty,
+ /// The Ligatures setting. Off, no cell is shaped, so each draws its own
+ /// glyph, as in a font without ligatures. It lives here because every
+ /// cell and word above was resolved under it (`syncLigatures`).
+ ligatures: bool = true,
/// The word the last cell was in, so the rest of it costs a compare. It
/// is positional, so every frame forgets it: the cells change under it.
memo: Memo = .{},
@@ -6144,9 +6162,9 @@ fn wordBreak(a: WordCell, b: WordCell, i: usize, cursor: ?usize) bool {
/// The atlas slot `line[at]` draws: blank for a plain space (or concealed
/// text), its own glyph when no ligature could replace that glyph (every
-/// cell, in a font without ligatures), else its slice of the word around it,
-/// shaped once and cached by the word's text. The cells of a word being walked
-/// cost a compare. Taglines are not shaped: they are drawn at two pitches
+/// cell, in a font without ligatures or with Ligatures off), else its slice
+/// of the word around it, shaped once and cached by the word's text. The
+/// cells of a word being walked cost a compare. Taglines are not shaped: they are drawn at two pitches
/// (tagline_width in a tag layer, a whole cell in the grid), and a strip would
/// not line up in one of them.
fn cellSlot(g: *Gui, line: []const pardes.Cell, at: usize, cursor: ?usize, role: pardes.FontRole) Slot {
@@ -6163,7 +6181,7 @@ fn cellSlot(g: *Gui, line: []const pardes.Cell, at: usize, cursor: ?usize, role:
if (!known.found_existing) {
const drawn = glyphForCodepoint(g, here.cp);
const font = if (drawn.face == 0) g.font else g.fallbacks[drawn.face - 1].?.face;
- known.value_ptr.* = if (role == .body and c.ui_font_substitutes(font, drawn.glyph) != 0)
+ known.value_ptr.* = if (role == .body and words.ligatures and c.ui_font_substitutes(font, drawn.glyph) != 0)
Words.shape_slot
else
cachedGlyph(g, .{ .face = drawn.face, .glyph = drawn.glyph, .role = role, .decoration = here.decoration }) orelse g.space_slot;
@@ -6328,6 +6346,40 @@ test "a ligature draws across its cells, one slice each, and comes apart under t
try std.testing.expectEqual(c.ui_font_glyph(font, '-'), (try glyphKeyAt(&g, cellSlot(&g, &line, 1, null, .tagline))).glyph);
}
+test "Ligatures off draws a ligature as its cells' own glyphs, and on shapes it again" {
+ const bytes = try filesystem.readFile(std.testing.allocator, "assets/MapleMono-NF-Regular.ttf");
+ defer std.testing.allocator.free(bytes);
+ const font = c.ui_font_new(bytes.ptr, @intCast(bytes.len)) orelse return error.FontInit;
+ defer c.ui_font_free(font);
+ var g = try textTestGui(font, 27);
+ defer font_allocator = std.heap.smp_allocator;
+ defer std.testing.allocator.free(g.atlas_stage);
+ defer g.glyphs.deinit();
+ defer g.words.deinit(std.testing.allocator);
+ const core = try pardes.Pardes.init(std.testing.allocator, .{ .cols = 80, .rows = 24 });
+ defer core.deinit();
+
+ const text = "a->b";
+ const line = textTestLine(text);
+ var shaped: [text.len]Slot = undefined;
+ for (&shaped, 0..) |*slot, i| slot.* = cellSlot(&g, &line, i, null, .body);
+ try std.testing.expectEqual(shaped[1].u + g.cell_w, shaped[2].u);
+
+ // Off, every cell is the slot the per-glyph path gives its codepoint.
+ try std.testing.expect(core.executeBuiltinLine(core.active, "Ligatures off"));
+ syncLigatures(&g, core);
+ for (text, 0..) |byte, i| {
+ const plain = cachedGlyph(&g, .{ .face = 0, .glyph = c.ui_font_glyph(font, byte), .role = .body }).?;
+ try std.testing.expectEqual(plain, cellSlot(&g, &line, i, null, .body));
+ }
+ try std.testing.expect(!std.meta.eql(shaped[1], cellSlot(&g, &line, 1, null, .body)));
+
+ // On again, the arrow is its strip once more.
+ try std.testing.expect(core.executeBuiltinLine(core.active, "Ligatures on"));
+ syncLigatures(&g, core);
+ for (shaped, 0..) |slot, i| try std.testing.expectEqual(slot, cellSlot(&g, &line, i, null, .body));
+}
+
fn glyphKeyAt(g: *Gui, slot: Slot) !GlyphKey {
var it = g.glyphs.iterator();
while (it.next()) |entry| if (std.meta.eql(entry.value_ptr.*, slot)) return entry.key_ptr.*;