renderer/metal: constrain PUA glyphs if they aren't next to space

This commit is contained in:
Mitchell Hashimoto
2023-12-16 20:07:25 -08:00
parent 2da5ec2c28
commit 0b658c8217
4 changed files with 88 additions and 5 deletions

View File

@ -11,6 +11,7 @@ const objc = @import("objc");
const macos = @import("macos");
const imgui = @import("imgui");
const glslang = @import("glslang");
const ziglyph = @import("ziglyph");
const apprt = @import("../apprt.zig");
const configpkg = @import("../config.zig");
const font = @import("../font/main.zig");
@ -19,6 +20,7 @@ const renderer = @import("../renderer.zig");
const math = @import("../math.zig");
const Surface = @import("../Surface.zig");
const link = @import("link.zig");
const fgMode = @import("cell.zig").fgMode;
const shadertoy = @import("shadertoy.zig");
const assert = std.debug.assert;
const Allocator = std.mem.Allocator;
@ -1780,11 +1782,17 @@ pub fn updateCell(
},
);
// If we're rendering a color font, we use the color atlas
const presentation = try self.font_group.group.presentationFromIndex(shaper_run.font_index);
const mode: mtl_shaders.Cell.Mode = switch (presentation) {
.text => .fg,
.emoji => .fg_color,
const mode: mtl_shaders.Cell.Mode = switch (try fgMode(
&self.font_group.group,
screen,
cell,
shaper_run,
x,
y,
)) {
.normal => .fg,
.color => .fg_color,
.constrained => .fg_constrained,
};
self.cells.appendAssumeCapacity(.{

61
src/renderer/cell.zig Normal file
View File

@ -0,0 +1,61 @@
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))) {
break :text .normal;
}
// If the next cell is empty, then we allow it to use the
// full glyph size.
if (x < screen.cols - 1) {
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;
},
};
}

View File

@ -101,6 +101,7 @@ pub const Cell = extern struct {
pub const Mode = enum(u8) {
bg = 1,
fg = 2,
fg_constrained = 3,
fg_color = 7,
strikethrough = 8,
};

View File

@ -4,6 +4,7 @@ using namespace metal;
enum Mode : uint8_t {
MODE_BG = 1u,
MODE_FG = 2u,
MODE_FG_CONSTRAINED = 3u,
MODE_FG_COLOR = 7u,
MODE_STRIKETHROUGH = 8u,
};
@ -150,6 +151,7 @@ vertex VertexOut uber_vertex(
break;
case MODE_FG:
case MODE_FG_CONSTRAINED:
case MODE_FG_COLOR: {
float2 glyph_size = float2(input.glyph_size);
float2 glyph_offset = float2(input.glyph_offset);
@ -159,6 +161,16 @@ vertex VertexOut uber_vertex(
// So we flip it with `cell_size.y - glyph_offset.y`.
glyph_offset.y = cell_size_scaled.y - glyph_offset.y;
// If we're constrained then we need to scale the glyph.
if (input.mode == MODE_FG_CONSTRAINED) {
if (glyph_size.x > cell_size_scaled.x) {
float new_y = glyph_size.y * (cell_size_scaled.x / glyph_size.x);
glyph_offset.y += glyph_size.y - new_y;
glyph_size.y = new_y;
glyph_size.x = cell_size_scaled.x;
}
}
// Calculate the final position of the cell which uses our glyph size
// and glyph offset to create the correct bounding box for the glyph.
cell_pos = cell_pos + glyph_size * position + glyph_offset;
@ -211,6 +223,7 @@ fragment float4 uber_fragment(
case MODE_BG:
return in.color;
case MODE_FG_CONSTRAINED:
case MODE_FG: {
// Normalize the texture coordinates to [0,1]
float2 size = float2(textureGreyscale.get_width(), textureGreyscale.get_height());