mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-07-19 10:16:12 +03:00
font: draw single and double underlines as sprites
This commit is contained in:
@ -289,13 +289,6 @@ pub fn create(alloc: Allocator, app: *App, config: *const Config) !*Window {
|
||||
// Pre-calculate our initial cell size ourselves.
|
||||
const cell_size = try renderer.CellSize.init(alloc, font_group);
|
||||
|
||||
// Setup our box font
|
||||
font_group.group.sprite = font.sprite.Face{
|
||||
.width = @floatToInt(u32, cell_size.width),
|
||||
.height = @floatToInt(u32, cell_size.height),
|
||||
.thickness = 2,
|
||||
};
|
||||
|
||||
// Convert our padding from points to pixels
|
||||
const padding_x = (@intToFloat(f32, config.@"window-padding-x") * x_dpi) / 72;
|
||||
const padding_y = (@intToFloat(f32, config.@"window-padding-y") * y_dpi) / 72;
|
||||
|
@ -11,6 +11,7 @@ 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 Sprite = sprite.Sprite;
|
||||
pub const Descriptor = discovery.Descriptor;
|
||||
pub const Discover = discovery.Discover;
|
||||
|
||||
@ -60,6 +61,9 @@ pub const Presentation = enum(u1) {
|
||||
emoji = 1, // U+FEOF
|
||||
};
|
||||
|
||||
/// A FontIndex that can be used to use the sprite font directly.
|
||||
pub const sprite_index = Group.FontIndex.initSpecial(.sprite);
|
||||
|
||||
test {
|
||||
@import("std").testing.refAllDecls(@This());
|
||||
}
|
||||
|
@ -11,10 +11,11 @@ pub const Face = @import("sprite/Face.zig");
|
||||
/// 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);
|
||||
pub const start: u32 = std.math.maxInt(u21) + 1;
|
||||
pub const end: u32 = std.math.maxInt(u32);
|
||||
|
||||
underline = start,
|
||||
underline_double = start + 1,
|
||||
|
||||
// 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
|
||||
|
@ -117,13 +117,13 @@ pub fn writeAtlas(self: *Canvas, alloc: Allocator, atlas: *Atlas) !Atlas.Region
|
||||
|
||||
/// Draw and fill a rectangle. This is the main primitive for drawing
|
||||
/// lines as well (which are just generally skinny rectangles...)
|
||||
pub fn rect(self: *Canvas, v: Rect, color: pixman.Color) void {
|
||||
pub fn rect(self: *Canvas, v: Rect, color: Color) void {
|
||||
const boxes = &[_]pixman.Box32{
|
||||
.{
|
||||
.x1 = @intCast(i32, v.x),
|
||||
.y1 = @intCast(i32, v.y),
|
||||
.x2 = @intCast(i32, v.width),
|
||||
.y2 = @intCast(i32, v.height),
|
||||
.x2 = @intCast(i32, v.x + v.width),
|
||||
.y2 = @intCast(i32, v.y + v.height),
|
||||
},
|
||||
};
|
||||
|
||||
|
@ -17,8 +17,10 @@ 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 Atlas = @import("../../Atlas.zig");
|
||||
const Box = @import("Box.zig");
|
||||
const underline = @import("underline.zig");
|
||||
|
||||
/// The cell width and height.
|
||||
width: u32,
|
||||
@ -28,6 +30,9 @@ height: u32,
|
||||
/// want to do any DPI scaling, it is expected to be done earlier.
|
||||
thickness: u32,
|
||||
|
||||
/// The position fo the underline.
|
||||
underline_position: u32 = 0,
|
||||
|
||||
/// 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
|
||||
@ -47,25 +52,42 @@ pub fn renderGlyph(
|
||||
if (std.debug.runtime_safety) assert(self.hasCodepoint(cp, null));
|
||||
|
||||
// Safe to ".?" because of the above assertion.
|
||||
switch (Kind.init(cp).?) {
|
||||
.box => {
|
||||
return switch (Kind.init(cp).?) {
|
||||
.box => box: {
|
||||
const f: Box = .{
|
||||
.width = self.width,
|
||||
.height = self.height,
|
||||
.thickness = self.thickness,
|
||||
};
|
||||
|
||||
return try f.renderGlyph(alloc, atlas, cp);
|
||||
break :box try f.renderGlyph(alloc, atlas, cp);
|
||||
},
|
||||
}
|
||||
|
||||
.underline => try underline.renderGlyph(
|
||||
alloc,
|
||||
atlas,
|
||||
@intToEnum(Sprite, cp),
|
||||
self.width,
|
||||
self.height,
|
||||
self.underline_position,
|
||||
self.thickness,
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
/// Kind of sprites we have. Drawing is implemented separately for each kind.
|
||||
const Kind = enum {
|
||||
box,
|
||||
underline,
|
||||
|
||||
pub fn init(cp: u32) ?Kind {
|
||||
return switch (cp) {
|
||||
Sprite.start...Sprite.end => switch (@intToEnum(Sprite, cp)) {
|
||||
.underline,
|
||||
.underline_double,
|
||||
=> .underline,
|
||||
},
|
||||
|
||||
// Box fonts
|
||||
0x2500...0x257F, // "Box Drawing" block
|
||||
0x2580...0x259F, // "Block Elements" block
|
||||
|
119
src/font/sprite/underline.zig
Normal file
119
src/font/sprite/underline.zig
Normal file
@ -0,0 +1,119 @@
|
||||
//! This file renders underline sprites. To draw underlines, we render the
|
||||
//! full cell-width as a sprite and then draw it as a separate pass to the
|
||||
//! text.
|
||||
//!
|
||||
//! We used to render the underlines directly in the GPU shaders but its
|
||||
//! annoying to support multiple types of underlines and its also annoying
|
||||
//! to maintain and debug another set of shaders for each renderer instead of
|
||||
//! just relying on the glyph system we already need to support for text
|
||||
//! anyways.
|
||||
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 Atlas = @import("../../Atlas.zig");
|
||||
|
||||
/// Draw an underline.
|
||||
pub fn renderGlyph(
|
||||
alloc: Allocator,
|
||||
atlas: *Atlas,
|
||||
sprite: Sprite,
|
||||
width: u32,
|
||||
height: u32,
|
||||
line_pos: u32,
|
||||
line_thickness: u32,
|
||||
) !font.Glyph {
|
||||
// Create the canvas we'll use to draw. We draw the underline in
|
||||
// a full cell size and position it according to "pos".
|
||||
var canvas = try font.sprite.Canvas.init(alloc, width, height);
|
||||
defer canvas.deinit(alloc);
|
||||
|
||||
// Perform the actual drawing
|
||||
(Draw{
|
||||
.width = width,
|
||||
.height = height,
|
||||
.pos = line_pos,
|
||||
.thickness = line_thickness,
|
||||
}).draw(&canvas, sprite);
|
||||
|
||||
// Write the drawing to the atlas
|
||||
const region = try canvas.writeAtlas(alloc, atlas);
|
||||
|
||||
// Our coordinates start at the BOTTOM for our renderers so we have to
|
||||
// specify an offset of the full height because we rendered a full size
|
||||
// cell.
|
||||
const offset_y = @intCast(i32, height);
|
||||
|
||||
return font.Glyph{
|
||||
.width = width,
|
||||
.height = height,
|
||||
.offset_x = 0,
|
||||
.offset_y = offset_y,
|
||||
.atlas_x = region.x,
|
||||
.atlas_y = region.y,
|
||||
.advance_x = @intToFloat(f32, width),
|
||||
};
|
||||
}
|
||||
|
||||
/// Stores drawing state.
|
||||
const Draw = struct {
|
||||
width: u32,
|
||||
height: u32,
|
||||
pos: u32,
|
||||
thickness: u32,
|
||||
|
||||
/// Draw a specific underline sprite to the canvas.
|
||||
fn draw(self: Draw, canvas: *font.sprite.Canvas, sprite: Sprite) void {
|
||||
switch (sprite) {
|
||||
.underline => self.drawSingle(canvas),
|
||||
.underline_double => self.drawDouble(canvas),
|
||||
}
|
||||
}
|
||||
|
||||
/// Draw a single underline.
|
||||
fn drawSingle(self: Draw, canvas: *font.sprite.Canvas) void {
|
||||
canvas.rect(.{
|
||||
.x = 0,
|
||||
.y = self.pos,
|
||||
.width = self.width,
|
||||
.height = self.thickness,
|
||||
}, .on);
|
||||
}
|
||||
|
||||
/// Draw a double underline.
|
||||
fn drawDouble(self: Draw, canvas: *font.sprite.Canvas) void {
|
||||
canvas.rect(.{
|
||||
.x = 0,
|
||||
.y = self.pos,
|
||||
.width = self.width,
|
||||
.height = self.thickness,
|
||||
}, .on);
|
||||
|
||||
canvas.rect(.{
|
||||
.x = 0,
|
||||
.y = self.pos + (self.thickness * 2),
|
||||
.width = self.width,
|
||||
.height = self.thickness,
|
||||
}, .on);
|
||||
}
|
||||
};
|
||||
|
||||
test "single" {
|
||||
const testing = std.testing;
|
||||
const alloc = testing.allocator;
|
||||
|
||||
var atlas_greyscale = try Atlas.init(alloc, 512, .greyscale);
|
||||
defer atlas_greyscale.deinit(alloc);
|
||||
|
||||
_ = try renderGlyph(
|
||||
alloc,
|
||||
&atlas_greyscale,
|
||||
.underline,
|
||||
36,
|
||||
18,
|
||||
9,
|
||||
2,
|
||||
);
|
||||
}
|
Reference in New Issue
Block a user