mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-07-14 15:56:13 +03:00
Merge pull request #1110 from mitchellh/shape
renderer: characters in the Unicode PUA (private use area) are scaled if not followed by whitespace
This commit is contained in:
@ -19,6 +19,7 @@ const renderer = @import("../renderer.zig");
|
|||||||
const math = @import("../math.zig");
|
const math = @import("../math.zig");
|
||||||
const Surface = @import("../Surface.zig");
|
const Surface = @import("../Surface.zig");
|
||||||
const link = @import("link.zig");
|
const link = @import("link.zig");
|
||||||
|
const fgMode = @import("cell.zig").fgMode;
|
||||||
const shadertoy = @import("shadertoy.zig");
|
const shadertoy = @import("shadertoy.zig");
|
||||||
const assert = std.debug.assert;
|
const assert = std.debug.assert;
|
||||||
const Allocator = std.mem.Allocator;
|
const Allocator = std.mem.Allocator;
|
||||||
@ -1780,11 +1781,17 @@ pub fn updateCell(
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
// If we're rendering a color font, we use the color atlas
|
const mode: mtl_shaders.Cell.Mode = switch (try fgMode(
|
||||||
const presentation = try self.font_group.group.presentationFromIndex(shaper_run.font_index);
|
&self.font_group.group,
|
||||||
const mode: mtl_shaders.Cell.Mode = switch (presentation) {
|
screen,
|
||||||
.text => .fg,
|
cell,
|
||||||
.emoji => .fg_color,
|
shaper_run,
|
||||||
|
x,
|
||||||
|
y,
|
||||||
|
)) {
|
||||||
|
.normal => .fg,
|
||||||
|
.color => .fg_color,
|
||||||
|
.constrained => .fg_constrained,
|
||||||
};
|
};
|
||||||
|
|
||||||
self.cells.appendAssumeCapacity(.{
|
self.cells.appendAssumeCapacity(.{
|
||||||
|
@ -9,6 +9,7 @@ const testing = std.testing;
|
|||||||
const Allocator = std.mem.Allocator;
|
const Allocator = std.mem.Allocator;
|
||||||
const ArenaAllocator = std.heap.ArenaAllocator;
|
const ArenaAllocator = std.heap.ArenaAllocator;
|
||||||
const link = @import("link.zig");
|
const link = @import("link.zig");
|
||||||
|
const fgMode = @import("cell.zig").fgMode;
|
||||||
const shadertoy = @import("shadertoy.zig");
|
const shadertoy = @import("shadertoy.zig");
|
||||||
const apprt = @import("../apprt.zig");
|
const apprt = @import("../apprt.zig");
|
||||||
const configpkg = @import("../config.zig");
|
const configpkg = @import("../config.zig");
|
||||||
@ -1489,10 +1490,17 @@ pub fn updateCell(
|
|||||||
);
|
);
|
||||||
|
|
||||||
// If we're rendering a color font, we use the color atlas
|
// 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: CellProgram.CellMode = switch (try fgMode(
|
||||||
const mode: CellProgram.CellMode = switch (presentation) {
|
&self.font_group.group,
|
||||||
.text => .fg,
|
screen,
|
||||||
.emoji => .fg_color,
|
cell,
|
||||||
|
shaper_run,
|
||||||
|
x,
|
||||||
|
y,
|
||||||
|
)) {
|
||||||
|
.normal => .fg,
|
||||||
|
.color => .fg_color,
|
||||||
|
.constrained => .fg_constrained,
|
||||||
};
|
};
|
||||||
|
|
||||||
self.cells.appendAssumeCapacity(.{
|
self.cells.appendAssumeCapacity(.{
|
||||||
|
71
src/renderer/cell.zig
Normal file
71
src/renderer/cell.zig
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
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 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;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
@ -101,6 +101,7 @@ pub const Cell = extern struct {
|
|||||||
pub const Mode = enum(u8) {
|
pub const Mode = enum(u8) {
|
||||||
bg = 1,
|
bg = 1,
|
||||||
fg = 2,
|
fg = 2,
|
||||||
|
fg_constrained = 3,
|
||||||
fg_color = 7,
|
fg_color = 7,
|
||||||
strikethrough = 8,
|
strikethrough = 8,
|
||||||
};
|
};
|
||||||
|
@ -51,6 +51,7 @@ pub const Cell = extern struct {
|
|||||||
pub const CellMode = enum(u8) {
|
pub const CellMode = enum(u8) {
|
||||||
bg = 1,
|
bg = 1,
|
||||||
fg = 2,
|
fg = 2,
|
||||||
|
fg_constrained = 3,
|
||||||
fg_color = 7,
|
fg_color = 7,
|
||||||
strikethrough = 8,
|
strikethrough = 8,
|
||||||
|
|
||||||
|
@ -26,6 +26,7 @@ uniform vec2 cell_size;
|
|||||||
// See vertex shader
|
// See vertex shader
|
||||||
const uint MODE_BG = 1u;
|
const uint MODE_BG = 1u;
|
||||||
const uint MODE_FG = 2u;
|
const uint MODE_FG = 2u;
|
||||||
|
const uint MODE_FG_CONSTRAINED = 3u;
|
||||||
const uint MODE_FG_COLOR = 7u;
|
const uint MODE_FG_COLOR = 7u;
|
||||||
const uint MODE_STRIKETHROUGH = 8u;
|
const uint MODE_STRIKETHROUGH = 8u;
|
||||||
|
|
||||||
@ -38,6 +39,7 @@ void main() {
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case MODE_FG:
|
case MODE_FG:
|
||||||
|
case MODE_FG_CONSTRAINED:
|
||||||
a = texture(text, glyph_tex_coords).r;
|
a = texture(text, glyph_tex_coords).r;
|
||||||
vec3 premult = color.rgb * color.a;
|
vec3 premult = color.rgb * color.a;
|
||||||
out_FragColor = vec4(premult.rgb*a, a);
|
out_FragColor = vec4(premult.rgb*a, a);
|
||||||
|
@ -4,6 +4,7 @@ using namespace metal;
|
|||||||
enum Mode : uint8_t {
|
enum Mode : uint8_t {
|
||||||
MODE_BG = 1u,
|
MODE_BG = 1u,
|
||||||
MODE_FG = 2u,
|
MODE_FG = 2u,
|
||||||
|
MODE_FG_CONSTRAINED = 3u,
|
||||||
MODE_FG_COLOR = 7u,
|
MODE_FG_COLOR = 7u,
|
||||||
MODE_STRIKETHROUGH = 8u,
|
MODE_STRIKETHROUGH = 8u,
|
||||||
};
|
};
|
||||||
@ -150,6 +151,7 @@ vertex VertexOut uber_vertex(
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case MODE_FG:
|
case MODE_FG:
|
||||||
|
case MODE_FG_CONSTRAINED:
|
||||||
case MODE_FG_COLOR: {
|
case MODE_FG_COLOR: {
|
||||||
float2 glyph_size = float2(input.glyph_size);
|
float2 glyph_size = float2(input.glyph_size);
|
||||||
float2 glyph_offset = float2(input.glyph_offset);
|
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`.
|
// So we flip it with `cell_size.y - glyph_offset.y`.
|
||||||
glyph_offset.y = cell_size_scaled.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
|
// Calculate the final position of the cell which uses our glyph size
|
||||||
// and glyph offset to create the correct bounding box for the glyph.
|
// and glyph offset to create the correct bounding box for the glyph.
|
||||||
cell_pos = cell_pos + glyph_size * position + glyph_offset;
|
cell_pos = cell_pos + glyph_size * position + glyph_offset;
|
||||||
@ -211,6 +223,7 @@ fragment float4 uber_fragment(
|
|||||||
case MODE_BG:
|
case MODE_BG:
|
||||||
return in.color;
|
return in.color;
|
||||||
|
|
||||||
|
case MODE_FG_CONSTRAINED:
|
||||||
case MODE_FG: {
|
case MODE_FG: {
|
||||||
// Normalize the texture coordinates to [0,1]
|
// Normalize the texture coordinates to [0,1]
|
||||||
float2 size = float2(textureGreyscale.get_width(), textureGreyscale.get_height());
|
float2 size = float2(textureGreyscale.get_width(), textureGreyscale.get_height());
|
||||||
|
@ -6,6 +6,7 @@
|
|||||||
// NOTE: this must be kept in sync with the fragment shader
|
// NOTE: this must be kept in sync with the fragment shader
|
||||||
const uint MODE_BG = 1u;
|
const uint MODE_BG = 1u;
|
||||||
const uint MODE_FG = 2u;
|
const uint MODE_FG = 2u;
|
||||||
|
const uint MODE_FG_CONSTRAINED = 3u;
|
||||||
const uint MODE_FG_COLOR = 7u;
|
const uint MODE_FG_COLOR = 7u;
|
||||||
const uint MODE_STRIKETHROUGH = 8u;
|
const uint MODE_STRIKETHROUGH = 8u;
|
||||||
|
|
||||||
@ -179,6 +180,7 @@ void main() {
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case MODE_FG:
|
case MODE_FG:
|
||||||
|
case MODE_FG_CONSTRAINED:
|
||||||
case MODE_FG_COLOR:
|
case MODE_FG_COLOR:
|
||||||
vec2 glyph_offset_calc = glyph_offset;
|
vec2 glyph_offset_calc = glyph_offset;
|
||||||
|
|
||||||
@ -187,8 +189,19 @@ void main() {
|
|||||||
// So we flip it with `cell_size.y - glyph_offset.y`.
|
// So we flip it with `cell_size.y - glyph_offset.y`.
|
||||||
glyph_offset_calc.y = cell_size_scaled.y - glyph_offset_calc.y;
|
glyph_offset_calc.y = cell_size_scaled.y - glyph_offset_calc.y;
|
||||||
|
|
||||||
|
// If this is a constrained mode, we need to constrain it!
|
||||||
|
vec2 glyph_size_calc = glyph_size;
|
||||||
|
if (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_calc.y = glyph_offset_calc.y + (glyph_size.y - new_y);
|
||||||
|
glyph_size_calc.y = new_y;
|
||||||
|
glyph_size_calc.x = cell_size_scaled.x;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Calculate the final position of the cell.
|
// Calculate the final position of the cell.
|
||||||
cell_pos = cell_pos + (glyph_size * position) + glyph_offset_calc;
|
cell_pos = cell_pos + (glyph_size_calc * position) + glyph_offset_calc;
|
||||||
gl_Position = projection * vec4(cell_pos, cell_z, 1.0);
|
gl_Position = projection * vec4(cell_pos, cell_z, 1.0);
|
||||||
|
|
||||||
// We need to convert our texture position and size to normalized
|
// We need to convert our texture position and size to normalized
|
||||||
|
Reference in New Issue
Block a user