mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-07-14 15:56:13 +03:00
font: turn rasterization options into a struct, add thicken
This commit is contained in:
@ -34,6 +34,10 @@ pub const Config = struct {
|
||||
else => 12,
|
||||
},
|
||||
|
||||
/// Draw fonts with a thicker stroke, if supported. This is only supported
|
||||
/// currently on macOS.
|
||||
@"font-thicken": bool = false,
|
||||
|
||||
/// Background color for the window.
|
||||
background: Color = .{ .r = 0x28, .g = 0x2C, .b = 0x34 },
|
||||
|
||||
|
@ -269,7 +269,7 @@ pub fn renderGlyph(
|
||||
atlas: *font.Atlas,
|
||||
index: FontIndex,
|
||||
glyph_index: u32,
|
||||
max_height: ?u16,
|
||||
opts: font.face.RenderOptions,
|
||||
) !Glyph {
|
||||
// Special-case fonts are rendered directly.
|
||||
if (index.special()) |sp| switch (sp) {
|
||||
@ -282,7 +282,7 @@ pub fn renderGlyph(
|
||||
|
||||
const face = &self.faces.get(index.style).items[@intCast(index.idx)];
|
||||
try face.load(self.lib, self.size);
|
||||
const glyph = try face.face.?.renderGlyph(alloc, atlas, glyph_index, max_height);
|
||||
const glyph = try face.face.?.renderGlyph(alloc, atlas, glyph_index, opts);
|
||||
// log.warn("GLYPH={}", .{glyph});
|
||||
return glyph;
|
||||
}
|
||||
@ -383,7 +383,9 @@ pub const Wasm = struct {
|
||||
) !*Glyph {
|
||||
const idx = @as(FontIndex, @bitCast(@as(u8, @intCast(idx_))));
|
||||
const max_height = if (max_height_ <= 0) null else max_height_;
|
||||
const glyph = try self.renderGlyph(alloc, atlas, idx, cp, max_height);
|
||||
const glyph = try self.renderGlyph(alloc, atlas, idx, cp, .{
|
||||
.max_height = max_height,
|
||||
});
|
||||
|
||||
var result = try alloc.create(Glyph);
|
||||
errdefer alloc.destroy(result);
|
||||
@ -427,7 +429,7 @@ test {
|
||||
&atlas_greyscale,
|
||||
idx,
|
||||
glyph_index,
|
||||
null,
|
||||
.{},
|
||||
);
|
||||
}
|
||||
|
||||
@ -483,7 +485,7 @@ test "box glyph" {
|
||||
&atlas_greyscale,
|
||||
idx,
|
||||
0x2500,
|
||||
null,
|
||||
.{},
|
||||
);
|
||||
try testing.expectEqual(@as(u32, 36), glyph.height);
|
||||
}
|
||||
@ -514,7 +516,7 @@ test "resize" {
|
||||
&atlas_greyscale,
|
||||
idx,
|
||||
glyph_index,
|
||||
null,
|
||||
.{},
|
||||
);
|
||||
|
||||
try testing.expectEqual(@as(u32, 11), glyph.height);
|
||||
@ -531,7 +533,7 @@ test "resize" {
|
||||
&atlas_greyscale,
|
||||
idx,
|
||||
glyph_index,
|
||||
null,
|
||||
.{},
|
||||
);
|
||||
|
||||
try testing.expectEqual(@as(u32, 21), glyph.height);
|
||||
@ -574,7 +576,7 @@ test "discover monospace with fontconfig and freetype" {
|
||||
&atlas_greyscale,
|
||||
idx,
|
||||
glyph_index,
|
||||
null,
|
||||
.{},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -122,7 +122,7 @@ pub fn renderGlyph(
|
||||
alloc: Allocator,
|
||||
index: Group.FontIndex,
|
||||
glyph_index: u32,
|
||||
max_height: ?u16,
|
||||
opts: font.face.RenderOptions,
|
||||
) !Glyph {
|
||||
const key: GlyphKey = .{ .index = index, .glyph = glyph_index };
|
||||
const gop = try self.glyphs.getOrPut(alloc, key);
|
||||
@ -140,7 +140,7 @@ pub fn renderGlyph(
|
||||
atlas,
|
||||
index,
|
||||
glyph_index,
|
||||
max_height,
|
||||
opts,
|
||||
) catch |err| switch (err) {
|
||||
// If the atlas is full, we resize it
|
||||
error.AtlasFull => blk: {
|
||||
@ -150,7 +150,7 @@ pub fn renderGlyph(
|
||||
atlas,
|
||||
index,
|
||||
glyph_index,
|
||||
max_height,
|
||||
opts,
|
||||
);
|
||||
},
|
||||
|
||||
@ -203,7 +203,7 @@ test {
|
||||
alloc,
|
||||
idx,
|
||||
glyph_index,
|
||||
null,
|
||||
.{},
|
||||
);
|
||||
}
|
||||
|
||||
@ -225,7 +225,7 @@ test {
|
||||
alloc,
|
||||
idx,
|
||||
glyph_index,
|
||||
null,
|
||||
.{},
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -300,7 +300,9 @@ pub const Wasm = struct {
|
||||
) !*Glyph {
|
||||
const idx = @as(Group.FontIndex, @bitCast(@as(u8, @intCast(idx_))));
|
||||
const max_height = if (max_height_ <= 0) null else max_height_;
|
||||
const glyph = try self.renderGlyph(alloc, idx, cp, max_height);
|
||||
const glyph = try self.renderGlyph(alloc, idx, cp, .{
|
||||
.max_height = max_height,
|
||||
});
|
||||
|
||||
var result = try alloc.create(Glyph);
|
||||
errdefer alloc.destroy(result);
|
||||
@ -352,7 +354,7 @@ test "resize" {
|
||||
alloc,
|
||||
idx,
|
||||
glyph_index,
|
||||
null,
|
||||
.{},
|
||||
);
|
||||
|
||||
try testing.expectEqual(@as(u32, 11), glyph.height);
|
||||
@ -368,7 +370,7 @@ test "resize" {
|
||||
alloc,
|
||||
idx,
|
||||
glyph_index,
|
||||
null,
|
||||
.{},
|
||||
);
|
||||
|
||||
try testing.expectEqual(@as(u32, 21), glyph.height);
|
||||
|
@ -58,6 +58,20 @@ pub const Metrics = struct {
|
||||
strikethrough_thickness: f32,
|
||||
};
|
||||
|
||||
/// Additional options for rendering glyphs.
|
||||
pub const RenderOptions = struct {
|
||||
/// The maximum height of the glyph. If this is set, then any glyph
|
||||
/// larger than this height will be shrunk to this height. The scaling
|
||||
/// is typically naive, but ultimately up to the rasterizer.
|
||||
max_height: ?u16 = null,
|
||||
|
||||
/// Thicken the glyph. This draws the glyph with a thicker stroke width.
|
||||
/// This is purely an aesthetic setting.
|
||||
///
|
||||
/// This only works with CoreText currently.
|
||||
thicken: bool = false,
|
||||
};
|
||||
|
||||
pub const Foo = if (options.backend == .coretext) coretext.Face else void;
|
||||
|
||||
test {
|
||||
|
@ -102,10 +102,8 @@ pub const Face = struct {
|
||||
alloc: Allocator,
|
||||
atlas: *font.Atlas,
|
||||
glyph_index: u32,
|
||||
max_height: ?u16,
|
||||
opts: font.face.RenderOptions,
|
||||
) !font.Glyph {
|
||||
_ = max_height;
|
||||
|
||||
var glyphs = [_]macos.graphics.Glyph{@intCast(glyph_index)};
|
||||
|
||||
// Get the bounding rect for rendering this glyph.
|
||||
@ -205,7 +203,7 @@ pub const Face = struct {
|
||||
});
|
||||
|
||||
ctx.setAllowsFontSmoothing(true);
|
||||
ctx.setShouldSmoothFonts(false); // The amadeus "enthicken"
|
||||
ctx.setShouldSmoothFonts(opts.thicken); // The amadeus "enthicken"
|
||||
ctx.setAllowsFontSubpixelQuantization(true);
|
||||
ctx.setShouldSubpixelQuantizeFonts(true);
|
||||
ctx.setAllowsFontSubpixelPositioning(true);
|
||||
@ -479,7 +477,7 @@ test {
|
||||
var i: u8 = 32;
|
||||
while (i < 127) : (i += 1) {
|
||||
try testing.expect(face.glyphIndex(i) != null);
|
||||
_ = try face.renderGlyph(alloc, &atlas, face.glyphIndex(i).?, null);
|
||||
_ = try face.renderGlyph(alloc, &atlas, face.glyphIndex(i).?, .{});
|
||||
}
|
||||
}
|
||||
|
||||
@ -523,6 +521,6 @@ test "in-memory" {
|
||||
var i: u8 = 32;
|
||||
while (i < 127) : (i += 1) {
|
||||
try testing.expect(face.glyphIndex(i) != null);
|
||||
_ = try face.renderGlyph(alloc, &atlas, face.glyphIndex(i).?, null);
|
||||
_ = try face.renderGlyph(alloc, &atlas, face.glyphIndex(i).?, .{});
|
||||
}
|
||||
}
|
||||
|
@ -124,7 +124,7 @@ pub const Face = struct {
|
||||
alloc: Allocator,
|
||||
atlas: *font.Atlas,
|
||||
glyph_index: u32,
|
||||
max_height: ?u16,
|
||||
opts: font.face.RenderOptions,
|
||||
) !Glyph {
|
||||
// If our glyph has color, we want to render the color
|
||||
try self.face.loadGlyph(glyph_index, .{
|
||||
@ -188,7 +188,7 @@ pub const Face = struct {
|
||||
// and copy the atlas.
|
||||
const bitmap_original = bitmap_converted orelse bitmap_ft;
|
||||
const bitmap_resized: ?freetype.c.struct_FT_Bitmap_ = resized: {
|
||||
const max = max_height orelse break :resized null;
|
||||
const max = opts.max_height orelse break :resized null;
|
||||
const bm = bitmap_original;
|
||||
if (bm.rows <= max) break :resized null;
|
||||
|
||||
@ -522,16 +522,16 @@ test {
|
||||
// Generate all visible ASCII
|
||||
var i: u8 = 32;
|
||||
while (i < 127) : (i += 1) {
|
||||
_ = try ft_font.renderGlyph(alloc, &atlas, ft_font.glyphIndex(i).?, null);
|
||||
_ = try ft_font.renderGlyph(alloc, &atlas, ft_font.glyphIndex(i).?, .{});
|
||||
}
|
||||
|
||||
// Test resizing
|
||||
{
|
||||
const g1 = try ft_font.renderGlyph(alloc, &atlas, ft_font.glyphIndex('A').?, null);
|
||||
const g1 = try ft_font.renderGlyph(alloc, &atlas, ft_font.glyphIndex('A').?, .{});
|
||||
try testing.expectEqual(@as(u32, 11), g1.height);
|
||||
|
||||
try ft_font.setSize(.{ .points = 24, .xdpi = 96, .ydpi = 96 });
|
||||
const g2 = try ft_font.renderGlyph(alloc, &atlas, ft_font.glyphIndex('A').?, null);
|
||||
const g2 = try ft_font.renderGlyph(alloc, &atlas, ft_font.glyphIndex('A').?, .{});
|
||||
try testing.expectEqual(@as(u32, 21), g2.height);
|
||||
}
|
||||
}
|
||||
@ -551,11 +551,13 @@ test "color emoji" {
|
||||
|
||||
try testing.expectEqual(Presentation.emoji, ft_font.presentation);
|
||||
|
||||
_ = try ft_font.renderGlyph(alloc, &atlas, ft_font.glyphIndex('🥸').?, null);
|
||||
_ = try ft_font.renderGlyph(alloc, &atlas, ft_font.glyphIndex('🥸').?, .{});
|
||||
|
||||
// resize
|
||||
{
|
||||
const glyph = try ft_font.renderGlyph(alloc, &atlas, ft_font.glyphIndex('🥸').?, 24);
|
||||
const glyph = try ft_font.renderGlyph(alloc, &atlas, ft_font.glyphIndex('🥸').?, .{
|
||||
.max_height = 24,
|
||||
});
|
||||
try testing.expectEqual(@as(u32, 24), glyph.height);
|
||||
}
|
||||
}
|
||||
@ -610,5 +612,5 @@ test "mono to rgba" {
|
||||
defer ft_font.deinit();
|
||||
|
||||
// glyph 3 is mono in Noto
|
||||
_ = try ft_font.renderGlyph(alloc, &atlas, 3, null);
|
||||
_ = try ft_font.renderGlyph(alloc, &atlas, 3, .{});
|
||||
}
|
||||
|
@ -190,9 +190,9 @@ pub const Face = struct {
|
||||
alloc: Allocator,
|
||||
atlas: *font.Atlas,
|
||||
glyph_index: u32,
|
||||
max_height: ?u16,
|
||||
opts: font.face.RenderOptions,
|
||||
) !font.Glyph {
|
||||
_ = max_height;
|
||||
_ = opts;
|
||||
|
||||
var render = try self.renderGlyphInternal(alloc, glyph_index);
|
||||
defer render.deinit();
|
||||
@ -551,7 +551,7 @@ pub const Wasm = struct {
|
||||
}
|
||||
|
||||
fn face_render_glyph_(face: *Face, atlas: *font.Atlas, codepoint: u32) !*font.Glyph {
|
||||
const glyph = try face.renderGlyph(alloc, atlas, codepoint, null);
|
||||
const glyph = try face.renderGlyph(alloc, atlas, codepoint, .{});
|
||||
|
||||
const result = try alloc.create(font.Glyph);
|
||||
errdefer alloc.destroy(result);
|
||||
|
@ -996,7 +996,9 @@ pub fn updateCell(
|
||||
self.alloc,
|
||||
shaper_run.font_index,
|
||||
shaper_cell.glyph_index,
|
||||
@intFromFloat(@ceil(self.cell_size.height)),
|
||||
.{
|
||||
.max_height = @intFromFloat(@ceil(self.cell_size.height)),
|
||||
},
|
||||
);
|
||||
|
||||
// If we're rendering a color font, we use the color atlas
|
||||
@ -1031,7 +1033,7 @@ pub fn updateCell(
|
||||
self.alloc,
|
||||
font.sprite_index,
|
||||
@intFromEnum(sprite),
|
||||
null,
|
||||
.{},
|
||||
);
|
||||
|
||||
const color = if (cell.attrs.underline_color) cell.underline_fg else colors.fg;
|
||||
@ -1083,7 +1085,7 @@ fn addCursor(self: *Metal, screen: *terminal.Screen) void {
|
||||
self.alloc,
|
||||
font.sprite_index,
|
||||
@intFromEnum(sprite),
|
||||
null,
|
||||
.{},
|
||||
) catch |err| {
|
||||
log.warn("error rendering cursor glyph err={}", .{err});
|
||||
return;
|
||||
|
@ -991,7 +991,7 @@ fn addCursor(self: *OpenGL, screen: *terminal.Screen) void {
|
||||
self.alloc,
|
||||
font.sprite_index,
|
||||
@intFromEnum(sprite),
|
||||
null,
|
||||
.{},
|
||||
) catch |err| {
|
||||
log.warn("error rendering cursor glyph err={}", .{err});
|
||||
return;
|
||||
@ -1144,7 +1144,7 @@ pub fn updateCell(
|
||||
self.alloc,
|
||||
shaper_run.font_index,
|
||||
shaper_cell.glyph_index,
|
||||
@intFromFloat(@ceil(self.cell_size.height)),
|
||||
.{ .max_height = @intFromFloat(@ceil(self.cell_size.height)) },
|
||||
);
|
||||
|
||||
// If we're rendering a color font, we use the color atlas
|
||||
@ -1190,7 +1190,7 @@ pub fn updateCell(
|
||||
self.alloc,
|
||||
font.sprite_index,
|
||||
@intFromEnum(sprite),
|
||||
null,
|
||||
.{},
|
||||
);
|
||||
|
||||
const color = if (cell.attrs.underline_color) cell.underline_fg else colors.fg;
|
||||
|
Reference in New Issue
Block a user