mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-07-17 17:26:09 +03:00
quirks: Menlo/Monaco should disable ligatures by default (#331)
* font: disable default font features for Menlo and Monaco Both of these fonts have a default ligature on "fi" which makes terminal rendering super ugly. The easiest thing to do is special-case these fonts and disable ligatures. It appears other terminals do the same thing.
This commit is contained in:

committed by
GitHub

parent
63386e4a22
commit
ea3b957bc7
@ -83,6 +83,18 @@ pub const Face = struct {
|
|||||||
@intFromEnum(tag),
|
@intFromEnum(tag),
|
||||||
)));
|
)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Retrieve the number of name strings in the SFNT ‘name’ table.
|
||||||
|
pub fn getSfntNameCount(self: Face) usize {
|
||||||
|
return @intCast(c.FT_Get_Sfnt_Name_Count(self.handle));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Retrieve a string of the SFNT ‘name’ table for a given index.
|
||||||
|
pub fn getSfntName(self: Face, i: usize) Error!c.FT_SfntName {
|
||||||
|
var name: c.FT_SfntName = undefined;
|
||||||
|
const res = c.FT_Get_Sfnt_Name(self.handle, @intCast(i), &name);
|
||||||
|
return if (intToError(res)) |_| name else |err| err;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/// An enumeration to specify indices of SFNT tables loaded and parsed by
|
/// An enumeration to specify indices of SFNT tables loaded and parsed by
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
#include <ft2build.h>
|
#include <ft2build.h>
|
||||||
#include FT_FREETYPE_H
|
#include FT_FREETYPE_H
|
||||||
#include FT_TRUETYPE_TABLES_H
|
#include FT_TRUETYPE_TABLES_H
|
||||||
|
#include <freetype/ftsnames.h>
|
||||||
|
#include <freetype/ttnameid.h>
|
||||||
|
@ -87,6 +87,19 @@ pub const Face = struct {
|
|||||||
return try initFontCopy(ct_font, .{ .points = 0 });
|
return try initFontCopy(ct_font, .{ .points = 0 });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns the font name. If allocation is required, buf will be used,
|
||||||
|
/// but sometimes allocation isn't required and a static string is
|
||||||
|
/// returned.
|
||||||
|
pub fn name(self: *const Face, buf: []u8) Allocator.Error![]const u8 {
|
||||||
|
const display_name = self.font.copyDisplayName();
|
||||||
|
if (display_name.cstringPtr(.utf8)) |str| return str;
|
||||||
|
|
||||||
|
// "NULL if the internal storage of theString does not allow
|
||||||
|
// this to be returned efficiently." In this case, we need
|
||||||
|
// to allocate.
|
||||||
|
return display_name.cstring(buf, .utf8) orelse error.OutOfMemory;
|
||||||
|
}
|
||||||
|
|
||||||
/// Resize the font in-place. If this succeeds, the caller is responsible
|
/// Resize the font in-place. If this succeeds, the caller is responsible
|
||||||
/// for clearing any glyph caches, font atlas data, etc.
|
/// for clearing any glyph caches, font atlas data, etc.
|
||||||
pub fn setSize(self: *Face, size: font.face.DesiredSize) !void {
|
pub fn setSize(self: *Face, size: font.face.DesiredSize) !void {
|
||||||
|
@ -70,6 +70,27 @@ pub const Face = struct {
|
|||||||
self.* = undefined;
|
self.* = undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns the font name. If allocation is required, buf will be used,
|
||||||
|
/// but sometimes allocation isn't required and a static string is
|
||||||
|
/// returned.
|
||||||
|
pub fn name(self: *const Face, buf: []u8) Allocator.Error![]const u8 {
|
||||||
|
// We don't use this today but its possible the table below
|
||||||
|
// returns UTF-16 in which case we'd want to use this for conversion.
|
||||||
|
_ = buf;
|
||||||
|
|
||||||
|
const count = self.face.getSfntNameCount();
|
||||||
|
|
||||||
|
// We look for the font family entry.
|
||||||
|
for (0..count) |i| {
|
||||||
|
const entry = self.face.getSfntName(i) catch continue;
|
||||||
|
if (entry.name_id == freetype.c.TT_NAME_ID_FONT_FAMILY) {
|
||||||
|
return entry.string[0..entry.string_len];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
/// Resize the font in-place. If this succeeds, the caller is responsible
|
/// Resize the font in-place. If this succeeds, the caller is responsible
|
||||||
/// for clearing any glyph caches, font atlas data, etc.
|
/// for clearing any glyph caches, font atlas data, etc.
|
||||||
pub fn setSize(self: *Face, size: font.face.DesiredSize) !void {
|
pub fn setSize(self: *Face, size: font.face.DesiredSize) !void {
|
||||||
|
@ -12,6 +12,7 @@ const Library = font.Library;
|
|||||||
const Style = font.Style;
|
const Style = font.Style;
|
||||||
const Presentation = font.Presentation;
|
const Presentation = font.Presentation;
|
||||||
const terminal = @import("../../terminal/main.zig");
|
const terminal = @import("../../terminal/main.zig");
|
||||||
|
const quirks = @import("../../quirks.zig");
|
||||||
|
|
||||||
const log = std.log.scoped(.font_shaper);
|
const log = std.log.scoped(.font_shaper);
|
||||||
|
|
||||||
@ -29,15 +30,15 @@ pub const Shaper = struct {
|
|||||||
|
|
||||||
const FeatureList = std.ArrayList(harfbuzz.Feature);
|
const FeatureList = std.ArrayList(harfbuzz.Feature);
|
||||||
|
|
||||||
|
// These features are hardcoded to always be on by default. Users
|
||||||
|
// can turn them off by setting the features to "-liga" for example.
|
||||||
|
const hardcoded_features = [_][]const u8{ "dlig", "liga" };
|
||||||
|
|
||||||
/// The cell_buf argument is the buffer to use for storing shaped results.
|
/// The cell_buf argument is the buffer to use for storing shaped results.
|
||||||
/// This should be at least the number of columns in the terminal.
|
/// This should be at least the number of columns in the terminal.
|
||||||
pub fn init(alloc: Allocator, opts: font.shape.Options) !Shaper {
|
pub fn init(alloc: Allocator, opts: font.shape.Options) !Shaper {
|
||||||
// Parse all the features we want to use. We use
|
// Parse all the features we want to use. We use
|
||||||
var hb_feats = hb_feats: {
|
var hb_feats = hb_feats: {
|
||||||
// These features are hardcoded to always be on by default. Users
|
|
||||||
// can turn them off by setting the features to "-liga" for example.
|
|
||||||
const hardcoded_features = [_][]const u8{ "dlig", "liga" };
|
|
||||||
|
|
||||||
var list = try FeatureList.initCapacity(alloc, opts.features.len + hardcoded_features.len);
|
var list = try FeatureList.initCapacity(alloc, opts.features.len + hardcoded_features.len);
|
||||||
errdefer list.deinit();
|
errdefer list.deinit();
|
||||||
|
|
||||||
@ -107,7 +108,14 @@ pub const Shaper = struct {
|
|||||||
// fonts, the codepoint == glyph_index so we don't need to run any shaping.
|
// fonts, the codepoint == glyph_index so we don't need to run any shaping.
|
||||||
if (run.font_index.special() == null) {
|
if (run.font_index.special() == null) {
|
||||||
const face = try run.group.group.faceFromIndex(run.font_index);
|
const face = try run.group.group.faceFromIndex(run.font_index);
|
||||||
harfbuzz.shape(face.hb_font, self.hb_buf, self.hb_feats.items);
|
const i = if (!quirks.disableDefaultFontFeatures(face)) 0 else i: {
|
||||||
|
// If we are disabling default font features we just offset
|
||||||
|
// our features by the hardcoded items because always
|
||||||
|
// add those at the beginning.
|
||||||
|
break :i hardcoded_features.len;
|
||||||
|
};
|
||||||
|
|
||||||
|
harfbuzz.shape(face.hb_font, self.hb_buf, self.hb_feats.items[i..]);
|
||||||
}
|
}
|
||||||
|
|
||||||
// If our buffer is empty, we short-circuit the rest of the work
|
// If our buffer is empty, we short-circuit the rest of the work
|
||||||
|
27
src/quirks.zig
Normal file
27
src/quirks.zig
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
//! Inspired by WebKit's quirks.cpp[1], this file centralizes all our
|
||||||
|
//! sad environment-specific hacks that we have to do to make things work.
|
||||||
|
//! This is a last resort; if we can find a general solution to a problem,
|
||||||
|
//! we of course prefer that, but sometimes other software, fonts, etc. are
|
||||||
|
//! just broken or weird and we have to work around it.
|
||||||
|
//!
|
||||||
|
//! [1]: https://github.com/WebKit/WebKit/blob/main/Source/WebCore/page/Quirks.cpp
|
||||||
|
|
||||||
|
const std = @import("std");
|
||||||
|
|
||||||
|
const font = @import("font/main.zig");
|
||||||
|
|
||||||
|
/// If true, the default font features should be disabled for the given face.
|
||||||
|
pub fn disableDefaultFontFeatures(face: *const font.Face) bool {
|
||||||
|
var buf: [64]u8 = undefined;
|
||||||
|
const name = face.name(&buf) catch |err| switch (err) {
|
||||||
|
// If the name doesn't fit in buf we know this will be false
|
||||||
|
// because we have no quirks fonts that are longer than buf!
|
||||||
|
error.OutOfMemory => return false,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Menlo and Monaco both have a default ligature of "fi" that looks
|
||||||
|
// really bad in terminal grids, so we want to disable ligatures
|
||||||
|
// by default for these faces.
|
||||||
|
return std.mem.eql(u8, name, "Menlo") or
|
||||||
|
std.mem.eql(u8, name, "Monaco");
|
||||||
|
}
|
Reference in New Issue
Block a user