Files
ghostty/src/renderer/cell.zig
Mitchell Hashimoto f7c558e733 renderer: constrain dingbats category to cell size
Dingbats are generally symbolic glyphs that frequently overflow the
cell. Their defined codepoint width is "1" so they take one cell but
very often overflow to the next.

This extends the previously created logic for Nerd Font symbols such
that when a dingbat is next to whitespace, we allow it to use the full
size, but when a dingbat is next to an occupied cell we constrain it to
the cell size.
2024-01-07 14:54:15 -08:00

88 lines
3.1 KiB
Zig

const ziglyph = @import("ziglyph");
const font = @import("../font/main.zig");
const terminal = @import("../terminal/main.zig");
pub const FgMode = enum {
/// Normal non-colored text rendering. The text can leave the cell
/// size if it is larger than the cell to allow for ligatures.
normal,
/// Colored text rendering, specifically Emoji.
color,
/// Similar to normal but the text must be constrained to the cell
/// size. If a glyph is larger than the cell then it must be resized
/// to fit.
constrained,
};
/// Returns the appropriate foreground mode for the given cell. This is
/// meant to be called from the typical updateCell function within a
/// renderer.
pub fn fgMode(
group: *font.Group,
screen: *terminal.Screen,
cell: terminal.Screen.Cell,
shaper_run: font.shape.TextRun,
x: usize,
y: usize,
) !FgMode {
const presentation = try group.presentationFromIndex(shaper_run.font_index);
return switch (presentation) {
// Emoji is always full size and color.
.emoji => .color,
// If it is text it is slightly more complex. If we are a codepoint
// in the private use area and we are at the end or the next cell
// is not empty, we need to constrain rendering.
//
// We do this specifically so that Nerd Fonts can render their
// icons without overlapping with subsequent characters. But if
// the subsequent character is empty, then we allow it to use
// the full glyph size. See #1071.
.text => text: {
if (!ziglyph.general_category.isPrivateUse(@intCast(cell.char)) and
!ziglyph.blocks.isDingbats(@intCast(cell.char)))
{
break :text .normal;
}
// We exempt the Powerline range from this since they exhibit
// box-drawing behavior and should not be constrained.
if (isPowerline(cell.char)) {
break :text .normal;
}
// If we are at the end of the screen its definitely constrained
if (x == screen.cols - 1) break :text .constrained;
// If we have a previous cell and it was PUA then we need to
// also constrain. This is so that multiple PUA glyphs align.
if (x > 0) {
const prev_cell = screen.getCell(.active, y, x - 1);
if (ziglyph.general_category.isPrivateUse(@intCast(prev_cell.char))) {
break :text .constrained;
}
}
// If the next cell is empty, then we allow it to use the
// full glyph size.
const next_cell = screen.getCell(.active, y, x + 1);
if (next_cell.char == 0 or next_cell.char == ' ') {
break :text .normal;
}
// Must be constrained
break :text .constrained;
},
};
}
// Returns true if the codepoint is a part of the Powerline range.
fn isPowerline(char: u32) bool {
return switch (char) {
0xE0B0...0xE0C8, 0xE0CA, 0xE0CC...0xE0D2, 0xE0D4 => true,
else => false,
};
}