mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-07-14 15:56:13 +03:00
rearrange box rendering to prepare for more sprite drawing
This commit is contained in:
@ -290,7 +290,7 @@ pub fn create(alloc: Allocator, app: *App, config: *const Config) !*Window {
|
||||
const cell_size = try renderer.CellSize.init(alloc, font_group);
|
||||
|
||||
// Setup our box font
|
||||
font_group.group.box_font = font.BoxFont{
|
||||
font_group.group.sprite = font.sprite.Face{
|
||||
.width = @floatToInt(u32, cell_size.width),
|
||||
.height = @floatToInt(u32, cell_size.height),
|
||||
.thickness = 2,
|
||||
|
@ -50,10 +50,11 @@ faces: StyleArray,
|
||||
/// the codepoint. This can be set after initialization.
|
||||
discover: ?font.Discover = null,
|
||||
|
||||
/// Set this to a non-null value to enable box font glyph drawing. If this
|
||||
/// Set this to a non-null value to enable sprite glyph drawing. If this
|
||||
/// isn't enabled we'll just fall through to trying to use regular fonts
|
||||
/// to render box glyphs.
|
||||
box_font: ?font.BoxFont = null,
|
||||
/// to render sprite glyphs. But more than likely, if this isn't set then
|
||||
/// terminal rendering will look wrong.
|
||||
sprite: ?font.sprite.Face = null,
|
||||
|
||||
pub fn init(
|
||||
alloc: Allocator,
|
||||
@ -129,8 +130,8 @@ pub const FontIndex = packed struct {
|
||||
// We start all special fonts at this index so they can be detected.
|
||||
pub const start = std.math.maxInt(IndexInt);
|
||||
|
||||
/// Box drawing, this is rendered JIT using 2D graphics APIs.
|
||||
box = start,
|
||||
/// Sprite drawing, this is rendered JIT using 2D graphics APIs.
|
||||
sprite = start,
|
||||
};
|
||||
|
||||
style: Style = .regular,
|
||||
@ -178,50 +179,11 @@ pub fn indexForCodepoint(
|
||||
style: Style,
|
||||
p: ?Presentation,
|
||||
) ?FontIndex {
|
||||
// If this is a box drawing glyph, we use the special font index. This
|
||||
// will force special logic where we'll render this ourselves. If we don't
|
||||
// have a box font set, then we just try to use regular fonts.
|
||||
if (self.box_font != null) {
|
||||
if (switch (cp) {
|
||||
// "Box Drawing" block
|
||||
0x2500...0x257F => true,
|
||||
|
||||
// "Block Elements" block
|
||||
0x2580...0x259f => true,
|
||||
|
||||
// "Braille" block
|
||||
0x2800...0x28FF => true,
|
||||
|
||||
// "Symbols for Legacy Computing" block
|
||||
0x1FB00...0x1FB3B => true,
|
||||
|
||||
0x1FB3C...0x1FB40,
|
||||
0x1FB47...0x1FB4B,
|
||||
0x1FB57...0x1FB5B,
|
||||
0x1FB62...0x1FB66,
|
||||
0x1FB6C...0x1FB6F,
|
||||
=> true,
|
||||
|
||||
0x1FB41...0x1FB45,
|
||||
0x1FB4C...0x1FB50,
|
||||
0x1FB52...0x1FB56,
|
||||
0x1FB5D...0x1FB61,
|
||||
0x1FB68...0x1FB6B,
|
||||
=> true,
|
||||
|
||||
0x1FB46,
|
||||
0x1FB51,
|
||||
0x1FB5C,
|
||||
0x1FB67,
|
||||
0x1FB9A,
|
||||
0x1FB9B,
|
||||
=> true,
|
||||
|
||||
0x1FB70...0x1FB8B => true,
|
||||
|
||||
else => false,
|
||||
}) {
|
||||
return FontIndex.initSpecial(.box);
|
||||
// If we have sprite drawing enabled, check if our sprite face can
|
||||
// handle this.
|
||||
if (self.sprite) |sprite| {
|
||||
if (sprite.hasCodepoint(cp, p)) {
|
||||
return FontIndex.initSpecial(.sprite);
|
||||
}
|
||||
}
|
||||
|
||||
@ -275,7 +237,7 @@ fn indexForCodepointExact(self: Group, cp: u32, style: Style, p: ?Presentation)
|
||||
/// determining what atlas is needed.
|
||||
pub fn presentationFromIndex(self: Group, index: FontIndex) !font.Presentation {
|
||||
if (index.special()) |sp| switch (sp) {
|
||||
.box => return .text,
|
||||
.sprite => return .text,
|
||||
};
|
||||
|
||||
const face = try self.faceFromIndex(index);
|
||||
@ -312,7 +274,7 @@ pub fn renderGlyph(
|
||||
) !Glyph {
|
||||
// Special-case fonts are rendered directly.
|
||||
if (index.special()) |sp| switch (sp) {
|
||||
.box => return try self.box_font.?.renderGlyph(
|
||||
.sprite => return try self.sprite.?.renderGlyph(
|
||||
alloc,
|
||||
atlas,
|
||||
glyph_index,
|
||||
@ -402,12 +364,12 @@ test "box glyph" {
|
||||
defer group.deinit();
|
||||
|
||||
// Set box font
|
||||
group.box_font = font.BoxFont{ .width = 18, .height = 36, .thickness = 2 };
|
||||
group.sprite = font.sprite.Face{ .width = 18, .height = 36, .thickness = 2 };
|
||||
|
||||
// Should find a box glyph
|
||||
const idx = group.indexForCodepoint(0x2500, .regular, null).?;
|
||||
try testing.expectEqual(Style.regular, idx.style);
|
||||
try testing.expectEqual(@enumToInt(FontIndex.Special.box), idx.idx);
|
||||
try testing.expectEqual(@enumToInt(FontIndex.Special.sprite), idx.idx);
|
||||
|
||||
// Should render it
|
||||
const glyph = try group.renderGlyph(
|
||||
|
@ -582,7 +582,7 @@ test "shape box glyphs" {
|
||||
defer testdata.deinit();
|
||||
|
||||
// Setup the box font
|
||||
testdata.cache.group.box_font = font.BoxFont{
|
||||
testdata.cache.group.sprite = font.sprite.Face{
|
||||
.width = 18,
|
||||
.height = 36,
|
||||
.thickness = 2,
|
||||
|
@ -1,7 +1,6 @@
|
||||
const std = @import("std");
|
||||
const build_options = @import("build_options");
|
||||
|
||||
pub const BoxFont = @import("BoxFont.zig");
|
||||
pub const discovery = @import("discovery.zig");
|
||||
pub const face = @import("face.zig");
|
||||
pub const DeferredFace = @import("DeferredFace.zig");
|
||||
@ -11,6 +10,7 @@ pub const GroupCache = @import("GroupCache.zig");
|
||||
pub const Glyph = @import("Glyph.zig");
|
||||
pub const Library = @import("Library.zig");
|
||||
pub const Shaper = @import("Shaper.zig");
|
||||
pub const sprite = @import("sprite.zig");
|
||||
pub const Descriptor = discovery.Descriptor;
|
||||
pub const Discover = discovery.Discover;
|
||||
|
||||
|
27
src/font/sprite.zig
Normal file
27
src/font/sprite.zig
Normal file
@ -0,0 +1,27 @@
|
||||
const std = @import("std");
|
||||
pub const Face = @import("sprite/Face.zig");
|
||||
|
||||
/// Sprites are represented as special codepoints outside of the Unicode
|
||||
/// codepoint range. Unicode maxes out at U+10FFFF (21 bits), and we use the
|
||||
/// high 11 bits to hide our special characters.
|
||||
///
|
||||
/// These characters are ONLY used for rendering and NEVER used written to
|
||||
/// text files or any other exported format, so we don't use the Private Use
|
||||
/// Area of Unicode.
|
||||
pub const Sprite = enum(u32) {
|
||||
// Start 1 above the maximum Unicode codepoint.
|
||||
const start: u32 = std.math.maxInt(u21) + 1;
|
||||
const end: u32 = std.math.maxInt(u32);
|
||||
|
||||
underline = start,
|
||||
|
||||
// Note: we don't currently put the box drawing glyphs in here because
|
||||
// there are a LOT and I'm lazy. What I want to do is spend more time
|
||||
// studying the patterns to see if we can programmatically build our
|
||||
// enum perhaps and comptime generate the drawing code at the same time.
|
||||
// I'm not sure if that's advisable yet though.
|
||||
};
|
||||
|
||||
test {
|
||||
@import("std").testing.refAllDecls(@This());
|
||||
}
|
File diff suppressed because it is too large
Load Diff
96
src/font/sprite/Face.zig
Normal file
96
src/font/sprite/Face.zig
Normal file
@ -0,0 +1,96 @@
|
||||
//! 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 Atlas = @import("../../Atlas.zig");
|
||||
const Box = @import("Box.zig");
|
||||
|
||||
/// The cell width and height.
|
||||
width: u32,
|
||||
height: u32,
|
||||
|
||||
/// Base thickness value for lines of sprites. This is in pixels. If you
|
||||
/// want to do any DPI scaling, it is expected to be done earlier.
|
||||
thickness: u32,
|
||||
|
||||
/// 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: *Atlas,
|
||||
cp: u32,
|
||||
) !font.Glyph {
|
||||
if (std.debug.runtime_safety) assert(self.hasCodepoint(cp, null));
|
||||
|
||||
// Safe to ".?" because of the above assertion.
|
||||
switch (Kind.init(cp).?) {
|
||||
.box => {
|
||||
const f: Box = .{
|
||||
.width = self.width,
|
||||
.height = self.height,
|
||||
.thickness = self.thickness,
|
||||
};
|
||||
|
||||
return try f.renderGlyph(alloc, atlas, cp);
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/// Kind of sprites we have. Drawing is implemented separately for each kind.
|
||||
const Kind = enum {
|
||||
box,
|
||||
|
||||
pub fn init(cp: u32) ?Kind {
|
||||
return switch (cp) {
|
||||
// Box fonts
|
||||
0x2500...0x257F, // "Box Drawing" block
|
||||
0x2580...0x259F, // "Block Elements" block
|
||||
0x2800...0x28FF, // "Braille" block
|
||||
0x1FB00...0x1FB3B, // "Symbols for Legacy Computing" block
|
||||
0x1FB3C...0x1FB40,
|
||||
0x1FB47...0x1FB4B,
|
||||
0x1FB57...0x1FB5B,
|
||||
0x1FB62...0x1FB66,
|
||||
0x1FB6C...0x1FB6F,
|
||||
0x1FB41...0x1FB45,
|
||||
0x1FB4C...0x1FB50,
|
||||
0x1FB52...0x1FB56,
|
||||
0x1FB5D...0x1FB61,
|
||||
0x1FB68...0x1FB6B,
|
||||
0x1FB70...0x1FB8B,
|
||||
0x1FB46,
|
||||
0x1FB51,
|
||||
0x1FB5C,
|
||||
0x1FB67,
|
||||
0x1FB9A,
|
||||
0x1FB9B,
|
||||
=> .box,
|
||||
|
||||
else => null,
|
||||
};
|
||||
}
|
||||
};
|
@ -409,9 +409,9 @@ pub fn setFontSize(self: *Metal, size: font.face.DesiredSize) !void {
|
||||
self.cell_size = new_cell_size;
|
||||
|
||||
// Set the cell size of the box font
|
||||
if (self.font_group.group.box_font) |*box| {
|
||||
box.width = @floatToInt(u32, self.cell_size.width);
|
||||
box.height = @floatToInt(u32, self.cell_size.height);
|
||||
if (self.font_group.group.sprite) |*sprite| {
|
||||
sprite.width = @floatToInt(u32, self.cell_size.width);
|
||||
sprite.height = @floatToInt(u32, self.cell_size.height);
|
||||
}
|
||||
|
||||
// Notify the window that the cell size changed.
|
||||
|
@ -502,9 +502,9 @@ pub fn setFontSize(self: *OpenGL, size: font.face.DesiredSize) !void {
|
||||
self.cell_size = new_cell_size;
|
||||
|
||||
// Set the cell size of the box font
|
||||
if (self.font_group.group.box_font) |*box| {
|
||||
box.width = @floatToInt(u32, self.cell_size.width);
|
||||
box.height = @floatToInt(u32, self.cell_size.height);
|
||||
if (self.font_group.group.sprite) |*sprite| {
|
||||
sprite.width = @floatToInt(u32, self.cell_size.width);
|
||||
sprite.height = @floatToInt(u32, self.cell_size.height);
|
||||
}
|
||||
|
||||
// Notify the window that the cell size changed.
|
||||
|
Reference in New Issue
Block a user