mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-07-16 08:46:08 +03:00
Unified metrics fixes (#2959)
Fix a couple issues that arose from the unified metrics stuff. One is that cursor sprites weren't rendering wide for wide characters- I've extracted cursor rendering to a separate file so it's not mixed in with Box any more, it should be correct now. The other issue is that I was assuming most fonts had sane `sTypo*` metrics for vertical sizing, but I was very wrong, so I've replaced the extraction of the vertical metrics with code that performs a similar process to what FreeType does to determine a font's ascent. This fixes the problem, here's an example with a font with the issue: |Before|After| |-|-| |<img width="752" alt="image" src="https://github.com/user-attachments/assets/71752b0c-35d7-4c35-b5bb-301149d906a7" />|<img width="752" alt="image" src="https://github.com/user-attachments/assets/2f9ae8ce-9f3a-4701-b0fe-e032da7e2246" />|
This commit is contained in:
@ -217,6 +217,7 @@ pub const SfntTag = enum(c_int) {
|
|||||||
.os2 => c.TT_OS2,
|
.os2 => c.TT_OS2,
|
||||||
.head => c.TT_Header,
|
.head => c.TT_Header,
|
||||||
.post => c.TT_Postscript,
|
.post => c.TT_Postscript,
|
||||||
|
.hhea => c.TT_HoriHeader,
|
||||||
else => unreachable, // As-needed...
|
else => unreachable, // As-needed...
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -536,6 +536,7 @@ pub const Face = struct {
|
|||||||
InvalidPostTable,
|
InvalidPostTable,
|
||||||
InvalidOS2Table,
|
InvalidOS2Table,
|
||||||
OS2VersionNotSupported,
|
OS2VersionNotSupported,
|
||||||
|
InvalidHheaTable,
|
||||||
};
|
};
|
||||||
|
|
||||||
fn calcMetrics(ct_font: *macos.text.Font) CalcMetricsError!font.face.Metrics {
|
fn calcMetrics(ct_font: *macos.text.Font) CalcMetricsError!font.face.Metrics {
|
||||||
@ -563,7 +564,7 @@ pub const Face = struct {
|
|||||||
const len = data.getLength();
|
const len = data.getLength();
|
||||||
break :post opentype.Post.init(ptr[0..len]) catch |err| {
|
break :post opentype.Post.init(ptr[0..len]) catch |err| {
|
||||||
return switch (err) {
|
return switch (err) {
|
||||||
error.EndOfStream => error.InvalidOS2Table,
|
error.EndOfStream => error.InvalidPostTable,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
@ -583,13 +584,73 @@ pub const Face = struct {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Read the 'hhea' table out of the font data.
|
||||||
|
const hhea: opentype.Hhea = hhea: {
|
||||||
|
const tag = macos.text.FontTableTag.init("hhea");
|
||||||
|
const data = ct_font.copyTable(tag) orelse return error.CopyTableError;
|
||||||
|
defer data.release();
|
||||||
|
const ptr = data.getPointer();
|
||||||
|
const len = data.getLength();
|
||||||
|
break :hhea opentype.Hhea.init(ptr[0..len]) catch |err| {
|
||||||
|
return switch (err) {
|
||||||
|
error.EndOfStream => error.InvalidHheaTable,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
const units_per_em: f64 = @floatFromInt(head.unitsPerEm);
|
const units_per_em: f64 = @floatFromInt(head.unitsPerEm);
|
||||||
const px_per_em: f64 = ct_font.getSize();
|
const px_per_em: f64 = ct_font.getSize();
|
||||||
const px_per_unit: f64 = px_per_em / units_per_em;
|
const px_per_unit: f64 = px_per_em / units_per_em;
|
||||||
|
|
||||||
const ascent = @as(f64, @floatFromInt(os2.sTypoAscender)) * px_per_unit;
|
const ascent: f64, const descent: f64, const line_gap: f64 = vertical_metrics: {
|
||||||
const descent = @as(f64, @floatFromInt(os2.sTypoDescender)) * px_per_unit;
|
const os2_ascent: f64 = @floatFromInt(os2.sTypoAscender);
|
||||||
const line_gap = @as(f64, @floatFromInt(os2.sTypoLineGap)) * px_per_unit;
|
const os2_descent: f64 = @floatFromInt(os2.sTypoDescender);
|
||||||
|
const os2_line_gap: f64 = @floatFromInt(os2.sTypoLineGap);
|
||||||
|
|
||||||
|
// If the font says to use typo metrics, trust it.
|
||||||
|
if (os2.fsSelection.use_typo_metrics) {
|
||||||
|
break :vertical_metrics .{
|
||||||
|
os2_ascent * px_per_unit,
|
||||||
|
os2_descent * px_per_unit,
|
||||||
|
os2_line_gap * px_per_unit,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise we prefer the height metrics from 'hhea' if they
|
||||||
|
// are available, or else OS/2 sTypo* metrics, and if all else
|
||||||
|
// fails then we use OS/2 usWin* metrics.
|
||||||
|
//
|
||||||
|
// This is not "standard" behavior, but it's our best bet to
|
||||||
|
// account for fonts being... just weird. It's pretty much what
|
||||||
|
// FreeType does to get its generic ascent and descent metrics.
|
||||||
|
|
||||||
|
if (hhea.ascender != 0 or hhea.descender != 0) {
|
||||||
|
const hhea_ascent: f64 = @floatFromInt(hhea.ascender);
|
||||||
|
const hhea_descent: f64 = @floatFromInt(hhea.descender);
|
||||||
|
const hhea_line_gap: f64 = @floatFromInt(hhea.lineGap);
|
||||||
|
break :vertical_metrics .{
|
||||||
|
hhea_ascent * px_per_unit,
|
||||||
|
hhea_descent * px_per_unit,
|
||||||
|
hhea_line_gap * px_per_unit,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (os2_ascent != 0 or os2_descent != 0) {
|
||||||
|
break :vertical_metrics .{
|
||||||
|
os2_ascent * px_per_unit,
|
||||||
|
os2_descent * px_per_unit,
|
||||||
|
os2_line_gap * px_per_unit,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const win_ascent: f64 = @floatFromInt(os2.usWinAscent);
|
||||||
|
const win_descent: f64 = @floatFromInt(os2.usWinDescent);
|
||||||
|
break :vertical_metrics .{
|
||||||
|
win_ascent * px_per_unit,
|
||||||
|
win_descent * px_per_unit,
|
||||||
|
0.0,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
// Some fonts have degenerate 'post' tables where the underline
|
// Some fonts have degenerate 'post' tables where the underline
|
||||||
// thickness (and often position) are 0. We consider them null
|
// thickness (and often position) are 0. We consider them null
|
||||||
|
@ -631,6 +631,9 @@ pub const Face = struct {
|
|||||||
// Read the 'OS/2' table out of the font data.
|
// Read the 'OS/2' table out of the font data.
|
||||||
const os2 = face.getSfntTable(.os2) orelse return error.CopyTableError;
|
const os2 = face.getSfntTable(.os2) orelse return error.CopyTableError;
|
||||||
|
|
||||||
|
// Read the 'hhea' table out of the font data.
|
||||||
|
const hhea = face.getSfntTable(.hhea) orelse return error.CopyTableError;
|
||||||
|
|
||||||
// Some fonts don't actually have an OS/2 table, which
|
// Some fonts don't actually have an OS/2 table, which
|
||||||
// we need in order to do the metrics calculations, in
|
// we need in order to do the metrics calculations, in
|
||||||
// such cases FreeType sets the version to 0xFFFF
|
// such cases FreeType sets the version to 0xFFFF
|
||||||
@ -640,9 +643,56 @@ pub const Face = struct {
|
|||||||
const px_per_em: f64 = @floatFromInt(size_metrics.y_ppem);
|
const px_per_em: f64 = @floatFromInt(size_metrics.y_ppem);
|
||||||
const px_per_unit = px_per_em / @as(f64, @floatFromInt(units_per_em));
|
const px_per_unit = px_per_em / @as(f64, @floatFromInt(units_per_em));
|
||||||
|
|
||||||
const ascent = @as(f64, @floatFromInt(os2.sTypoAscender)) * px_per_unit;
|
const ascent: f64, const descent: f64, const line_gap: f64 = vertical_metrics: {
|
||||||
const descent = @as(f64, @floatFromInt(os2.sTypoDescender)) * px_per_unit;
|
const os2_ascent: f64 = @floatFromInt(os2.sTypoAscender);
|
||||||
const line_gap = @as(f64, @floatFromInt(os2.sTypoLineGap)) * px_per_unit;
|
const os2_descent: f64 = @floatFromInt(os2.sTypoDescender);
|
||||||
|
const os2_line_gap: f64 = @floatFromInt(os2.sTypoLineGap);
|
||||||
|
|
||||||
|
// If the font says to use typo metrics, trust it.
|
||||||
|
// (The USE_TYPO_METRICS bit is bit 7)
|
||||||
|
if (os2.fsSelection & (1 << 7) != 0) {
|
||||||
|
break :vertical_metrics .{
|
||||||
|
os2_ascent * px_per_unit,
|
||||||
|
os2_descent * px_per_unit,
|
||||||
|
os2_line_gap * px_per_unit,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise we prefer the height metrics from 'hhea' if they
|
||||||
|
// are available, or else OS/2 sTypo* metrics, and if all else
|
||||||
|
// fails then we use OS/2 usWin* metrics.
|
||||||
|
//
|
||||||
|
// This is not "standard" behavior, but it's our best bet to
|
||||||
|
// account for fonts being... just weird. It's pretty much what
|
||||||
|
// FreeType does to get its generic ascent and descent metrics.
|
||||||
|
|
||||||
|
if (hhea.Ascender != 0 or hhea.Descender != 0) {
|
||||||
|
const hhea_ascent: f64 = @floatFromInt(hhea.Ascender);
|
||||||
|
const hhea_descent: f64 = @floatFromInt(hhea.Descender);
|
||||||
|
const hhea_line_gap: f64 = @floatFromInt(hhea.Line_Gap);
|
||||||
|
break :vertical_metrics .{
|
||||||
|
hhea_ascent * px_per_unit,
|
||||||
|
hhea_descent * px_per_unit,
|
||||||
|
hhea_line_gap * px_per_unit,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (os2_ascent != 0 or os2_descent != 0) {
|
||||||
|
break :vertical_metrics .{
|
||||||
|
os2_ascent * px_per_unit,
|
||||||
|
os2_descent * px_per_unit,
|
||||||
|
os2_line_gap * px_per_unit,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const win_ascent: f64 = @floatFromInt(os2.usWinAscent);
|
||||||
|
const win_descent: f64 = @floatFromInt(os2.usWinDescent);
|
||||||
|
break :vertical_metrics .{
|
||||||
|
win_ascent * px_per_unit,
|
||||||
|
win_descent * px_per_unit,
|
||||||
|
0.0,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
// Some fonts have degenerate 'post' tables where the underline
|
// Some fonts have degenerate 'post' tables where the underline
|
||||||
// thickness (and often position) are 0. We consider them null
|
// thickness (and often position) are 0. We consider them null
|
||||||
|
@ -214,26 +214,11 @@ pub fn renderGlyph(
|
|||||||
) !font.Glyph {
|
) !font.Glyph {
|
||||||
const metrics = self.metrics;
|
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
|
// Create the canvas we'll use to draw
|
||||||
var canvas = try font.sprite.Canvas.init(
|
var canvas = try font.sprite.Canvas.init(
|
||||||
alloc,
|
alloc,
|
||||||
metrics.cell_width,
|
metrics.cell_width,
|
||||||
height,
|
metrics.cell_height,
|
||||||
);
|
);
|
||||||
defer canvas.deinit(alloc);
|
defer canvas.deinit(alloc);
|
||||||
|
|
||||||
@ -246,15 +231,11 @@ pub fn renderGlyph(
|
|||||||
// Our coordinates start at the BOTTOM for our renderers so we have to
|
// 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
|
// specify an offset of the full height because we rendered a full size
|
||||||
// cell.
|
// cell.
|
||||||
//
|
const offset_y = @as(i32, @intCast(metrics.cell_height));
|
||||||
// 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));
|
|
||||||
|
|
||||||
return font.Glyph{
|
return font.Glyph{
|
||||||
.width = metrics.cell_width,
|
.width = metrics.cell_width,
|
||||||
.height = height,
|
.height = metrics.cell_height,
|
||||||
.offset_x = 0,
|
.offset_x = 0,
|
||||||
.offset_y = offset_y,
|
.offset_y = offset_y,
|
||||||
.atlas_x = region.x,
|
.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 {
|
fn draw(self: Box, alloc: Allocator, canvas: *font.sprite.Canvas, cp: u32) !void {
|
||||||
_ = alloc;
|
_ = alloc;
|
||||||
switch (cp) {
|
switch (cp) {
|
||||||
@ -1656,12 +1624,6 @@ fn draw(self: Box, alloc: Allocator, canvas: *font.sprite.Canvas, cp: u32) !void
|
|||||||
.right = true,
|
.right = true,
|
||||||
}, .light),
|
}, .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,
|
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 {
|
fn vline_middle(self: Box, canvas: *font.sprite.Canvas, thickness: Thickness) void {
|
||||||
const thick_px = thickness.height(self.metrics.box_thickness);
|
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);
|
self.vline(canvas, 0, self.metrics.cell_height, (self.metrics.cell_width -| thick_px) / 2, thick_px);
|
||||||
|
@ -21,6 +21,7 @@ const Sprite = font.sprite.Sprite;
|
|||||||
const Box = @import("Box.zig");
|
const Box = @import("Box.zig");
|
||||||
const Powerline = @import("Powerline.zig");
|
const Powerline = @import("Powerline.zig");
|
||||||
const underline = @import("underline.zig");
|
const underline = @import("underline.zig");
|
||||||
|
const cursor = @import("cursor.zig");
|
||||||
|
|
||||||
const log = std.log.scoped(.font_sprite);
|
const log = std.log.scoped(.font_sprite);
|
||||||
|
|
||||||
@ -123,6 +124,35 @@ pub fn renderGlyph(
|
|||||||
|
|
||||||
break :powerline try f.renderGlyph(alloc, atlas, cp);
|
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,
|
overline,
|
||||||
strikethrough,
|
strikethrough,
|
||||||
powerline,
|
powerline,
|
||||||
|
cursor,
|
||||||
|
|
||||||
pub fn init(cp: u32) ?Kind {
|
pub fn init(cp: u32) ?Kind {
|
||||||
return switch (cp) {
|
return switch (cp) {
|
||||||
@ -153,7 +184,7 @@ const Kind = enum {
|
|||||||
.cursor_rect,
|
.cursor_rect,
|
||||||
.cursor_hollow_rect,
|
.cursor_hollow_rect,
|
||||||
.cursor_bar,
|
.cursor_bar,
|
||||||
=> .box,
|
=> .cursor,
|
||||||
},
|
},
|
||||||
|
|
||||||
// == Box fonts ==
|
// == Box fonts ==
|
||||||
|
61
src/font/sprite/cursor.zig
Normal file
61
src/font/sprite/cursor.zig
Normal 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),
|
||||||
|
};
|
||||||
|
}
|
Reference in New Issue
Block a user