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