//! 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()); }