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.
|
// Pre-calculate our initial cell size ourselves.
|
||||||
const cell_size = try renderer.CellSize.init(alloc, font_group);
|
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
|
// Convert our padding from points to pixels
|
||||||
const padding_x = (@intToFloat(f32, config.@"window-padding-x") * x_dpi) / 72;
|
const padding_x = (@intToFloat(f32, config.@"window-padding-x") * x_dpi) / 72;
|
||||||
const padding_y = (@intToFloat(f32, config.@"window-padding-y") * y_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 Library = @import("Library.zig");
|
||||||
pub const Shaper = @import("Shaper.zig");
|
pub const Shaper = @import("Shaper.zig");
|
||||||
pub const sprite = @import("sprite.zig");
|
pub const sprite = @import("sprite.zig");
|
||||||
|
pub const Sprite = sprite.Sprite;
|
||||||
pub const Descriptor = discovery.Descriptor;
|
pub const Descriptor = discovery.Descriptor;
|
||||||
pub const Discover = discovery.Discover;
|
pub const Discover = discovery.Discover;
|
||||||
|
|
||||||
@ -60,6 +61,9 @@ pub const Presentation = enum(u1) {
|
|||||||
emoji = 1, // U+FEOF
|
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 {
|
test {
|
||||||
@import("std").testing.refAllDecls(@This());
|
@import("std").testing.refAllDecls(@This());
|
||||||
}
|
}
|
||||||
|
@ -11,10 +11,11 @@ pub const Face = @import("sprite/Face.zig");
|
|||||||
/// Area of Unicode.
|
/// Area of Unicode.
|
||||||
pub const Sprite = enum(u32) {
|
pub const Sprite = enum(u32) {
|
||||||
// Start 1 above the maximum Unicode codepoint.
|
// Start 1 above the maximum Unicode codepoint.
|
||||||
const start: u32 = std.math.maxInt(u21) + 1;
|
pub const start: u32 = std.math.maxInt(u21) + 1;
|
||||||
const end: u32 = std.math.maxInt(u32);
|
pub const end: u32 = std.math.maxInt(u32);
|
||||||
|
|
||||||
underline = start,
|
underline = start,
|
||||||
|
underline_double = start + 1,
|
||||||
|
|
||||||
// Note: we don't currently put the box drawing glyphs in here because
|
// 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
|
// 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
|
/// Draw and fill a rectangle. This is the main primitive for drawing
|
||||||
/// lines as well (which are just generally skinny rectangles...)
|
/// 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{
|
const boxes = &[_]pixman.Box32{
|
||||||
.{
|
.{
|
||||||
.x1 = @intCast(i32, v.x),
|
.x1 = @intCast(i32, v.x),
|
||||||
.y1 = @intCast(i32, v.y),
|
.y1 = @intCast(i32, v.y),
|
||||||
.x2 = @intCast(i32, v.width),
|
.x2 = @intCast(i32, v.x + v.width),
|
||||||
.y2 = @intCast(i32, v.height),
|
.y2 = @intCast(i32, v.y + v.height),
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -17,8 +17,10 @@ const builtin = @import("builtin");
|
|||||||
const assert = std.debug.assert;
|
const assert = std.debug.assert;
|
||||||
const Allocator = std.mem.Allocator;
|
const Allocator = std.mem.Allocator;
|
||||||
const font = @import("../main.zig");
|
const font = @import("../main.zig");
|
||||||
|
const Sprite = font.sprite.Sprite;
|
||||||
const Atlas = @import("../../Atlas.zig");
|
const Atlas = @import("../../Atlas.zig");
|
||||||
const Box = @import("Box.zig");
|
const Box = @import("Box.zig");
|
||||||
|
const underline = @import("underline.zig");
|
||||||
|
|
||||||
/// The cell width and height.
|
/// The cell width and height.
|
||||||
width: u32,
|
width: u32,
|
||||||
@ -28,6 +30,9 @@ height: u32,
|
|||||||
/// want to do any DPI scaling, it is expected to be done earlier.
|
/// want to do any DPI scaling, it is expected to be done earlier.
|
||||||
thickness: u32,
|
thickness: u32,
|
||||||
|
|
||||||
|
/// The position fo the underline.
|
||||||
|
underline_position: u32 = 0,
|
||||||
|
|
||||||
/// Returns true if the codepoint exists in our sprite font.
|
/// Returns true if the codepoint exists in our sprite font.
|
||||||
pub fn hasCodepoint(self: Face, cp: u32, p: ?font.Presentation) bool {
|
pub fn hasCodepoint(self: Face, cp: u32, p: ?font.Presentation) bool {
|
||||||
// We ignore presentation. No matter what presentation is requested
|
// 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));
|
if (std.debug.runtime_safety) assert(self.hasCodepoint(cp, null));
|
||||||
|
|
||||||
// Safe to ".?" because of the above assertion.
|
// Safe to ".?" because of the above assertion.
|
||||||
switch (Kind.init(cp).?) {
|
return switch (Kind.init(cp).?) {
|
||||||
.box => {
|
.box => box: {
|
||||||
const f: Box = .{
|
const f: Box = .{
|
||||||
.width = self.width,
|
.width = self.width,
|
||||||
.height = self.height,
|
.height = self.height,
|
||||||
.thickness = self.thickness,
|
.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.
|
/// Kind of sprites we have. Drawing is implemented separately for each kind.
|
||||||
const Kind = enum {
|
const Kind = enum {
|
||||||
box,
|
box,
|
||||||
|
underline,
|
||||||
|
|
||||||
pub fn init(cp: u32) ?Kind {
|
pub fn init(cp: u32) ?Kind {
|
||||||
return switch (cp) {
|
return switch (cp) {
|
||||||
|
Sprite.start...Sprite.end => switch (@intToEnum(Sprite, cp)) {
|
||||||
|
.underline,
|
||||||
|
.underline_double,
|
||||||
|
=> .underline,
|
||||||
|
},
|
||||||
|
|
||||||
// Box fonts
|
// Box fonts
|
||||||
0x2500...0x257F, // "Box Drawing" block
|
0x2500...0x257F, // "Box Drawing" block
|
||||||
0x2580...0x259F, // "Block Elements" 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