diff options
Diffstat (limited to 'src/gui/gui.zig')
| -rw-r--r-- | src/gui/gui.zig | 60 |
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.*; |
