font/sprite: separate out cursor rendering from Box

(Fixes width handling when hovering wide chars)
This commit is contained in:
Qwerasd
2024-12-13 12:16:15 -05:00
parent 0d0d06e50a
commit 13dd4bd897
3 changed files with 96 additions and 78 deletions

View File

@ -214,26 +214,11 @@ pub fn renderGlyph(
) !font.Glyph {
const metrics = self.metrics;
// Some codepoints (such as a few cursors) should not
// grow when the cell height is adjusted to be larger.
// And we also will need to adjust the vertical position.
const height, const dy = adjust: {
const h = metrics.cell_height;
if (unadjustedCodepoint(cp)) {
if (metrics.original_cell_height) |original| {
if (h > original) {
break :adjust .{ original, (h - original) / 2 };
}
}
}
break :adjust .{ h, 0 };
};
// Create the canvas we'll use to draw
var canvas = try font.sprite.Canvas.init(
alloc,
metrics.cell_width,
height,
metrics.cell_height,
);
defer canvas.deinit(alloc);
@ -246,15 +231,11 @@ pub fn renderGlyph(
// 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.
//
// If we have an adjustment (see above) to the cell height that we need
// to account for, we subtract half the difference (dy) to keep the glyph
// centered.
const offset_y = @as(i32, @intCast(metrics.cell_height - dy));
const offset_y = @as(i32, @intCast(metrics.cell_height));
return font.Glyph{
.width = metrics.cell_width,
.height = height,
.height = metrics.cell_height,
.offset_x = 0,
.offset_y = offset_y,
.atlas_x = region.x,
@ -263,19 +244,6 @@ pub fn renderGlyph(
};
}
/// Returns true if this codepoint should be rendered with the
/// width/height set to unadjusted values.
pub fn unadjustedCodepoint(cp: u32) bool {
return switch (cp) {
@intFromEnum(Sprite.cursor_rect),
@intFromEnum(Sprite.cursor_hollow_rect),
@intFromEnum(Sprite.cursor_bar),
=> true,
else => false,
};
}
fn draw(self: Box, alloc: Allocator, canvas: *font.sprite.Canvas, cp: u32) !void {
_ = alloc;
switch (cp) {
@ -1656,12 +1624,6 @@ fn draw(self: Box, alloc: Allocator, canvas: *font.sprite.Canvas, cp: u32) !void
.right = true,
}, .light),
// Not official box characters but special characters we hide
// in the high bits of a unicode codepoint.
@intFromEnum(Sprite.cursor_rect) => self.draw_cursor_rect(canvas),
@intFromEnum(Sprite.cursor_hollow_rect) => self.draw_cursor_hollow_rect(canvas),
@intFromEnum(Sprite.cursor_bar) => self.draw_cursor_bar(canvas),
else => return error.InvalidCodepoint,
}
}
@ -2842,42 +2804,6 @@ fn draw_dash_vertical(
}
}
fn draw_cursor_rect(self: Box, canvas: *font.sprite.Canvas) void {
// The cursor should fit itself to the canvas it's given, since if
// the cell height is adjusted upwards it will be given a canvas
// with the original un-adjusted height, so we can't use the height
// from the metrics.
const height: u32 = @intCast(canvas.sfc.getHeight());
self.rect(canvas, 0, 0, self.metrics.cell_width, height);
}
fn draw_cursor_hollow_rect(self: Box, canvas: *font.sprite.Canvas) void {
// The cursor should fit itself to the canvas it's given, since if
// the cell height is adjusted upwards it will be given a canvas
// with the original un-adjusted height, so we can't use the height
// from the metrics.
const height: u32 = @intCast(canvas.sfc.getHeight());
const thick_px = Thickness.super_light.height(self.metrics.cursor_thickness);
self.rect(canvas, 0, 0, self.metrics.cell_width, thick_px);
self.rect(canvas, 0, 0, thick_px, height);
self.rect(canvas, self.metrics.cell_width -| thick_px, 0, self.metrics.cell_width, height);
self.rect(canvas, 0, height -| thick_px, self.metrics.cell_width, height);
}
fn draw_cursor_bar(self: Box, canvas: *font.sprite.Canvas) void {
// The cursor should fit itself to the canvas it's given, since if
// the cell height is adjusted upwards it will be given a canvas
// with the original un-adjusted height, so we can't use the height
// from the metrics.
const height: u32 = @intCast(canvas.sfc.getHeight());
const thick_px = Thickness.light.height(self.metrics.cursor_thickness);
self.rect(canvas, 0, 0, thick_px, height);
}
fn vline_middle(self: Box, canvas: *font.sprite.Canvas, thickness: Thickness) void {
const thick_px = thickness.height(self.metrics.box_thickness);
self.vline(canvas, 0, self.metrics.cell_height, (self.metrics.cell_width -| thick_px) / 2, thick_px);

View File

@ -21,6 +21,7 @@ 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);
@ -123,6 +124,35 @@ pub fn renderGlyph(
break :powerline try f.renderGlyph(alloc, atlas, cp);
},
.cursor => cursor: {
// Cursors should be drawn with the original cell height if
// it has been adjusted larger, so they don't get stretched.
const height, const dy = adjust: {
const h = metrics.cell_height;
if (metrics.original_cell_height) |original| {
if (h > original) {
break :adjust .{ original, (h - original) / 2 };
}
}
break :adjust .{ h, 0 };
};
var g = try cursor.renderGlyph(
alloc,
atlas,
@enumFromInt(cp),
width,
height,
metrics.cursor_thickness,
);
// Keep the cursor centered in the cell if it's shorter.
g.offset_y += @intCast(dy);
break :cursor g;
},
};
}
@ -133,6 +163,7 @@ const Kind = enum {
overline,
strikethrough,
powerline,
cursor,
pub fn init(cp: u32) ?Kind {
return switch (cp) {
@ -153,7 +184,7 @@ const Kind = enum {
.cursor_rect,
.cursor_hollow_rect,
.cursor_bar,
=> .box,
=> .cursor,
},
// == Box fonts ==

View File

@ -0,0 +1,61 @@
//! This file renders cursor sprites.
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;
/// Draw a cursor.
pub fn renderGlyph(
alloc: Allocator,
atlas: *font.Atlas,
sprite: Sprite,
width: u32,
height: u32,
thickness: u32,
) !font.Glyph {
// Make a canvas of the desired size
var canvas = try font.sprite.Canvas.init(alloc, width, height);
defer canvas.deinit(alloc);
// Draw the appropriate sprite
switch (sprite) {
Sprite.cursor_rect => canvas.rect(.{
.x = 0,
.y = 0,
.width = width,
.height = height,
}, .on),
Sprite.cursor_hollow_rect => {
// left
canvas.rect(.{ .x = 0, .y = 0, .width = thickness, .height = height }, .on);
// right
canvas.rect(.{ .x = width -| thickness, .y = 0, .width = thickness, .height = height }, .on);
// top
canvas.rect(.{ .x = 0, .y = 0, .width = width, .height = thickness }, .on);
// bottom
canvas.rect(.{ .x = 0, .y = height -| thickness, .width = width, .height = thickness }, .on);
},
Sprite.cursor_bar => canvas.rect(.{
.x = 0,
.y = 0,
.width = thickness,
.height = height,
}, .on),
else => unreachable,
}
// Write the drawing to the atlas
const region = try canvas.writeAtlas(alloc, atlas);
return font.Glyph{
.width = width,
.height = height,
.offset_x = 0,
.offset_y = @intCast(height),
.atlas_x = region.x,
.atlas_y = region.y,
.advance_x = @floatFromInt(width),
};
}