ghostty/src/font/sprite/Face.zig
2025-02-13 09:08:33 -08:00

285 lines
10 KiB
Zig
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

//! This implements the built-in "sprite face". This font renders
//! the built-in glyphs for the terminal, such as box drawing fonts, as well
//! as specific sprites that are part of our rendering model such as
//! text decorations (underlines).
//!
//! This isn't really a "font face" so much as it is quacks like a font
//! face with regards to how it works with font.Group. We don't use any
//! dynamic dispatch so it isn't truly an interface but the functions
//! and behaviors are close enough to a system face that it makes it easy
//! to integrate with font.Group. This is desirable so that higher level
//! processes such as GroupCache, Shaper, etc. don't need to be aware of
//! special sprite handling and just treat it like a normal font face.
const Face = @This();
const std = @import("std");
const builtin = @import("builtin");
const assert = std.debug.assert;
const Allocator = std.mem.Allocator;
const font = @import("../main.zig");
const Sprite = font.sprite.Sprite;
const Box = @import("Box.zig");
const Powerline = @import("Powerline.zig");
const underline = @import("underline.zig");
const cursor = @import("cursor.zig");
const log = std.log.scoped(.font_sprite);
/// Grid metrics for rendering sprites.
metrics: font.Metrics,
/// Returns true if the codepoint exists in our sprite font.
pub fn hasCodepoint(self: Face, cp: u32, p: ?font.Presentation) bool {
// We ignore presentation. No matter what presentation is requested
// we always provide glyphs for our codepoints.
_ = p;
_ = self;
return Kind.init(cp) != null;
}
/// Render the glyph.
pub fn renderGlyph(
self: Face,
alloc: Allocator,
atlas: *font.Atlas,
cp: u32,
opts: font.face.RenderOptions,
) !font.Glyph {
if (std.debug.runtime_safety) {
if (!self.hasCodepoint(cp, null)) {
log.err("invalid codepoint cp={x}", .{cp});
unreachable; // crash
}
}
const metrics = self.metrics;
// We adjust our sprite width based on the cell width.
const width = switch (opts.cell_width orelse 1) {
0, 1 => metrics.cell_width,
else => |width| metrics.cell_width * width,
};
// It should be impossible for this to be null and we assert that
// in runtime safety modes but in case it is its not worth memory
// corruption so we return a valid, blank glyph.
const kind = Kind.init(cp) orelse return .{
.width = 0,
.height = 0,
.offset_x = 0,
.offset_y = 0,
.atlas_x = 0,
.atlas_y = 0,
.advance_x = 0,
};
// Safe to ".?" because of the above assertion.
return switch (kind) {
.box => (Box{ .metrics = metrics }).renderGlyph(alloc, atlas, cp),
.underline => try underline.renderGlyph(
alloc,
atlas,
@enumFromInt(cp),
width,
metrics.cell_height,
metrics.underline_position,
metrics.underline_thickness,
),
.strikethrough => try underline.renderGlyph(
alloc,
atlas,
@enumFromInt(cp),
width,
metrics.cell_height,
metrics.strikethrough_position,
metrics.strikethrough_thickness,
),
.overline => overline: {
var g = try underline.renderGlyph(
alloc,
atlas,
@enumFromInt(cp),
width,
metrics.cell_height,
0,
metrics.overline_thickness,
);
// We have to manually subtract the overline position
// on the rendered glyph since it can be negative.
g.offset_y -= metrics.overline_position;
break :overline g;
},
.powerline => powerline: {
const f: Powerline = .{
.width = metrics.cell_width,
.height = metrics.cell_height,
.thickness = metrics.box_thickness,
};
break :powerline try f.renderGlyph(alloc, atlas, cp);
},
.cursor => cursor: {
var g = try cursor.renderGlyph(
alloc,
atlas,
@enumFromInt(cp),
width,
metrics.cursor_height,
metrics.cursor_thickness,
);
// Cursors are drawn at their specified height
// and are centered vertically within the cell.
const cursor_height: i32 = @intCast(metrics.cursor_height);
const cell_height: i32 = @intCast(metrics.cell_height);
g.offset_y += @divTrunc(cell_height - cursor_height, 2);
break :cursor g;
},
};
}
/// Kind of sprites we have. Drawing is implemented separately for each kind.
const Kind = enum {
box,
underline,
overline,
strikethrough,
powerline,
cursor,
pub fn init(cp: u32) ?Kind {
return switch (cp) {
Sprite.start...Sprite.end => switch (@as(Sprite, @enumFromInt(cp))) {
.underline,
.underline_double,
.underline_dotted,
.underline_dashed,
.underline_curly,
=> .underline,
.overline,
=> .overline,
.strikethrough,
=> .strikethrough,
.cursor_rect,
.cursor_hollow_rect,
.cursor_bar,
=> .cursor,
},
// == Box fonts ==
// "Box Drawing" block
// ─ ━ │ ┃ ┄ ┅ ┆ ┇ ┈ ┉ ┊ ┋ ┌ ┍ ┎ ┏ ┐ ┑ ┒ ┓ └ ┕ ┖ ┗ ┘ ┙ ┚ ┛ ├ ┝ ┞ ┟ ┠
// ┡ ┢ ┣ ┤ ┥ ┦ ┧ ┨ ┩ ┪ ┫ ┬ ┭ ┮ ┯ ┰ ┱ ┲ ┳ ┴ ┵ ┶ ┷ ┸ ┹ ┺ ┻ ┼ ┽ ┾ ┿ ╀ ╁
// ╂ ╃ ╄ ╅ ╆ ╇ ╈ ╉ ╊ ╋ ╌ ╍ ╎ ╏ ═ ║ ╒ ╓ ╔ ╕ ╖ ╗ ╘ ╙ ╚ ╛ ╜ ╝ ╞ ╟ ╠ ╡ ╢
// ╣ ╤ ╥ ╦ ╧ ╨ ╩ ╪ ╫ ╬ ╭ ╮ ╯ ╰ ╴ ╵ ╶ ╷ ╸ ╹ ╺ ╻ ╼ ╽ ╾ ╿
0x2500...0x257F,
// "Block Elements" block
// ▀ ▁ ▂ ▃ ▄ ▅ ▆ ▇ █ ▉ ▊ ▋ ▌ ▍ ▎ ▏ ▐ ░ ▒ ▓ ▔ ▕ ▖ ▗ ▘ ▙ ▚ ▛ ▜ ▝ ▞ ▟
0x2580...0x259F,
// "Braille" block
0x2800...0x28FF,
// "Symbols for Legacy Computing" block
// (Block Mosaics / "Sextants")
// 🬀 🬁 🬂 🬃 🬄 🬅 🬆 🬇 🬈 🬉 🬊 🬋 🬌 🬍 🬎 🬏 🬐 🬑 🬒 🬓 🬔 🬕 🬖 🬗 🬘 🬙 🬚 🬛 🬜 🬝 🬞 🬟 🬠
// 🬡 🬢 🬣 🬤 🬥 🬦 🬧 🬨 🬩 🬪 🬫 🬬 🬭 🬮 🬯 🬰 🬱 🬲 🬳 🬴 🬵 🬶 🬷 🬸 🬹 🬺 🬻
// (Smooth Mosaics)
// 🬼 🬽 🬾 🬿 🭀 🭁 🭂 🭃 🭄 🭅 🭆
// 🭇 🭈 🭉 🭊 🭋 🭌 🭍 🭎 🭏 🭐 🭑
// 🭒 🭓 🭔 🭕 🭖 🭗 🭘 🭙 🭚 🭛 🭜
// 🭝 🭞 🭟 🭠 🭡 🭢 🭣 🭤 🭥 🭦 🭧
// 🭨 🭩 🭪 🭫 🭬 🭭 🭮 🭯
// (Block Elements)
// 🭰 🭱 🭲 🭳 🭴 🭵 🭶 🭷 🭸 🭹 🭺 🭻
// 🭼 🭽 🭾 🭿 🮀 🮁
// 🮂 🮃 🮄 🮅 🮆
// 🮇 🮈 🮉 🮊 🮋
// (Rectangular Shade Characters)
// 🮌 🮍 🮎 🮏 🮐 🮑 🮒
0x1FB00...0x1FB92,
// (Rectangular Shade Characters)
// 🮔
// (Fill Characters)
// 🮕 🮖 🮗
// (Diagonal Fill Characters)
// 🮘 🮙
// (Smooth Mosaics)
// 🮚 🮛
// (Triangular Shade Characters)
// 🮜 🮝 🮞 🮟
// (Character Cell Diagonals)
// 🮠 🮡 🮢 🮣 🮤 🮥 🮦 🮧 🮨 🮩 🮪 🮫 🮬 🮭 🮮
// (Light Solid Line With Stroke)
// 🮯
0x1FB94...0x1FBAF,
// (Negative Terminal Characters)
// 🮽 🮾 🮿
0x1FBBD...0x1FBBF,
// (Block Elements)
// 🯎 🯏
// (Character Cell Diagonals)
// 🯐 🯑 🯒 🯓 🯔 🯕 🯖 🯗 🯘 🯙 🯚 🯛 🯜 🯝 🯞 🯟
// (Geometric Shapes)
// 🯠 🯡 🯢 🯣 🯤 🯥 🯦 🯧 🯨 🯩 🯪 🯫 🯬 🯭 🯮 🯯
0x1FBCE...0x1FBEF,
// (Octants)
0x1CD00...0x1CDE5,
=> .box,
// Branch drawing character set, used for drawing git-like
// graphs in the terminal. Originally implemented in Kitty.
// Ref:
// - https://github.com/kovidgoyal/kitty/pull/7681
// - https://github.com/kovidgoyal/kitty/pull/7805
// NOTE: Kitty is GPL licensed, and its code was not referenced
// for these characters, only the loose specification of
// the character set in the pull request descriptions.
//
//          
//                    
//                    
//            
0xF5D0...0xF60D => .box,
// Separated Block Quadrants from Symbols for Legacy Computing Supplement
// 𜰡 𜰢 𜰣 𜰤 𜰥 𜰦 𜰧 𜰨 𜰩 𜰪 𜰫 𜰬 𜰭 𜰮 𜰯
0x1CC21...0x1CC2F => .box,
// Powerline fonts
0xE0B0,
0xE0B1,
0xE0B3,
0xE0B4,
0xE0B6,
0xE0B2,
0xE0B8,
0xE0BA,
0xE0BC,
0xE0BE,
0xE0D2,
0xE0D4,
=> .powerline,
else => null,
};
}
};
test {
@import("std").testing.refAllDecls(@This());
}