mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-07-20 02:36:22 +03:00
Merge pull request #544 from mitchellh/font-style-disable
Ability to disable font styles
This commit is contained in:
@ -229,6 +229,11 @@ pub fn init(
|
||||
group.codepoint_map = config.@"font-codepoint-map".map;
|
||||
}
|
||||
|
||||
// Set our styles
|
||||
group.styles.set(.bold, config.@"font-style-bold" != .false);
|
||||
group.styles.set(.italic, config.@"font-style-italic" != .false);
|
||||
group.styles.set(.bold_italic, config.@"font-style-bold-italic" != .false);
|
||||
|
||||
// Search for fonts
|
||||
if (font.Discover != void) discover: {
|
||||
const disco = try app.fontDiscover() orelse {
|
||||
@ -243,7 +248,7 @@ pub fn init(
|
||||
if (config.@"font-family") |family| {
|
||||
var disco_it = try disco.discover(.{
|
||||
.family = family,
|
||||
.style = config.@"font-style",
|
||||
.style = config.@"font-style".nameValue(),
|
||||
.size = font_size.points,
|
||||
.variations = config.@"font-variation".list.items,
|
||||
});
|
||||
@ -256,7 +261,7 @@ pub fn init(
|
||||
if (config.@"font-family-bold") |family| {
|
||||
var disco_it = try disco.discover(.{
|
||||
.family = family,
|
||||
.style = config.@"font-style-bold",
|
||||
.style = config.@"font-style-bold".nameValue(),
|
||||
.size = font_size.points,
|
||||
.bold = true,
|
||||
.variations = config.@"font-variation-bold".list.items,
|
||||
@ -270,7 +275,7 @@ pub fn init(
|
||||
if (config.@"font-family-italic") |family| {
|
||||
var disco_it = try disco.discover(.{
|
||||
.family = family,
|
||||
.style = config.@"font-style-italic",
|
||||
.style = config.@"font-style-italic".nameValue(),
|
||||
.size = font_size.points,
|
||||
.italic = true,
|
||||
.variations = config.@"font-variation-italic".list.items,
|
||||
@ -284,7 +289,7 @@ pub fn init(
|
||||
if (config.@"font-family-bold-italic") |family| {
|
||||
var disco_it = try disco.discover(.{
|
||||
.family = family,
|
||||
.style = config.@"font-style-bold-italic",
|
||||
.style = config.@"font-style-bold-italic".nameValue(),
|
||||
.size = font_size.points,
|
||||
.bold = true,
|
||||
.italic = true,
|
||||
|
@ -154,10 +154,11 @@ fn parseIntoField(
|
||||
else => field.type,
|
||||
};
|
||||
|
||||
// If we are a struct and have parseCLI, we call that and use
|
||||
// that to set the value.
|
||||
switch (@typeInfo(Field)) {
|
||||
.Struct => if (@hasDecl(Field, "parseCLI")) {
|
||||
// If we are a type that can have decls and have a parseCLI decl,
|
||||
// we call that and use that to set the value.
|
||||
const fieldInfo = @typeInfo(Field);
|
||||
if (fieldInfo == .Struct or fieldInfo == .Union or fieldInfo == .Enum) {
|
||||
if (@hasDecl(Field, "parseCLI")) {
|
||||
const fnInfo = @typeInfo(@TypeOf(Field.parseCLI)).Fn;
|
||||
switch (fnInfo.params.len) {
|
||||
// 1 arg = (input) => output
|
||||
@ -182,8 +183,10 @@ fn parseIntoField(
|
||||
}
|
||||
|
||||
return;
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
switch (fieldInfo) {
|
||||
.Enum => {
|
||||
@field(dst, field.name) = std.meta.stringToEnum(
|
||||
Field,
|
||||
|
@ -33,12 +33,19 @@ const c = @cImport({
|
||||
/// styles. This looks up the style based on the font style string advertised
|
||||
/// by the font itself. For example, "Iosevka Heavy" has a style of "Heavy".
|
||||
///
|
||||
/// You can also use these fields to completely disable a font style. If
|
||||
/// you set the value of the configuration below to literal "false" then
|
||||
/// that font style will be disabled. If the running program in the terminal
|
||||
/// requests a disabled font style, the regular font style will be used
|
||||
/// instead.
|
||||
///
|
||||
/// These are only valid if there is an exact font-family also specified.
|
||||
/// If no font-family is specified, then the font-style is ignored.
|
||||
@"font-style": ?[:0]const u8 = null,
|
||||
@"font-style-bold": ?[:0]const u8 = null,
|
||||
@"font-style-italic": ?[:0]const u8 = null,
|
||||
@"font-style-bold-italic": ?[:0]const u8 = null,
|
||||
/// If no font-family is specified, then the font-style is ignored unless
|
||||
/// you're disabling the font style.
|
||||
@"font-style": FontStyle = .{ .default = {} },
|
||||
@"font-style-bold": FontStyle = .{ .default = {} },
|
||||
@"font-style-italic": FontStyle = .{ .default = {} },
|
||||
@"font-style-bold-italic": FontStyle = .{ .default = {} },
|
||||
|
||||
/// Apply a font feature. This can be repeated multiple times to enable
|
||||
/// multiple font features. You can NOT set multiple font features with
|
||||
@ -1006,12 +1013,20 @@ fn cloneValue(alloc: Allocator, comptime T: type, src: T) !T {
|
||||
else => {},
|
||||
}
|
||||
|
||||
// If we're a type that can have decls and we have clone, then
|
||||
// call clone and be done.
|
||||
const t = @typeInfo(T);
|
||||
if (t == .Struct or t == .Enum or t == .Union) {
|
||||
if (@hasDecl(T, "clone")) return try src.clone(alloc);
|
||||
}
|
||||
|
||||
// Back into types of types
|
||||
switch (@typeInfo(T)) {
|
||||
switch (t) {
|
||||
inline .Bool,
|
||||
.Int,
|
||||
.Float,
|
||||
.Enum,
|
||||
.Union,
|
||||
=> return src,
|
||||
|
||||
.Optional => |info| return try cloneValue(
|
||||
@ -1705,6 +1720,63 @@ pub const RepeatableCodepointMap = struct {
|
||||
}
|
||||
};
|
||||
|
||||
pub const FontStyle = union(enum) {
|
||||
const Self = @This();
|
||||
|
||||
/// Use the default font style that font discovery finds.
|
||||
default: void,
|
||||
|
||||
/// Disable this font style completely. This will fall back to using
|
||||
/// the regular font when this style is encountered.
|
||||
false: void,
|
||||
|
||||
/// A specific named font style to use for this style.
|
||||
name: [:0]const u8,
|
||||
|
||||
pub fn parseCLI(self: *Self, alloc: Allocator, input: ?[]const u8) !void {
|
||||
const value = input orelse return error.ValueRequired;
|
||||
|
||||
if (std.mem.eql(u8, value, "default")) {
|
||||
self.* = .{ .default = {} };
|
||||
return;
|
||||
}
|
||||
|
||||
if (std.mem.eql(u8, value, "false")) {
|
||||
self.* = .{ .false = {} };
|
||||
return;
|
||||
}
|
||||
|
||||
const nameZ = try alloc.dupeZ(u8, value);
|
||||
self.* = .{ .name = nameZ };
|
||||
}
|
||||
|
||||
/// Returns the string name value that can be used with a font
|
||||
/// descriptor.
|
||||
pub fn nameValue(self: Self) ?[:0]const u8 {
|
||||
return switch (self) {
|
||||
.default, .false => null,
|
||||
.name => self.name,
|
||||
};
|
||||
}
|
||||
|
||||
test "parseCLI" {
|
||||
const testing = std.testing;
|
||||
var arena = ArenaAllocator.init(testing.allocator);
|
||||
defer arena.deinit();
|
||||
const alloc = arena.allocator();
|
||||
|
||||
var p: Self = .{ .default = {} };
|
||||
try p.parseCLI(alloc, "default");
|
||||
try testing.expectEqual(Self{ .default = {} }, p);
|
||||
|
||||
try p.parseCLI(alloc, "false");
|
||||
try testing.expectEqual(Self{ .false = {} }, p);
|
||||
|
||||
try p.parseCLI(alloc, "bold");
|
||||
try testing.expectEqualStrings("bold", p.name);
|
||||
}
|
||||
};
|
||||
|
||||
/// Options for copy on select behavior.
|
||||
pub const CopyOnSelect = enum {
|
||||
/// Disables copy on select entirely.
|
||||
|
@ -33,6 +33,9 @@ const log = std.log.scoped(.font_group);
|
||||
// to the user so we can change this later.
|
||||
const StyleArray = std.EnumArray(Style, std.ArrayListUnmanaged(GroupFace));
|
||||
|
||||
/// Packed array of booleans to indicate if a style is enabled or not.
|
||||
pub const StyleStatus = std.EnumArray(Style, bool);
|
||||
|
||||
/// Map of descriptors to faces. This is used with manual codepoint maps
|
||||
/// to ensure that we don't load the same font multiple times.
|
||||
///
|
||||
@ -72,6 +75,12 @@ size: font.face.DesiredSize,
|
||||
/// Instead, use the functions available on Group.
|
||||
faces: StyleArray,
|
||||
|
||||
/// The set of statuses and whether they're enabled or not. This defaults
|
||||
/// to true. This can be changed at runtime with no ill effect. If you
|
||||
/// change this at runtime and are using a GroupCache, the GroupCache
|
||||
/// must be reset.
|
||||
styles: StyleStatus = StyleStatus.initFill(true),
|
||||
|
||||
/// If discovery is available, we'll look up fonts where we can't find
|
||||
/// the codepoint. This can be set after initialization.
|
||||
discover: ?*font.Discover = null,
|
||||
@ -150,13 +159,6 @@ pub fn addFace(self: *Group, style: Style, face: GroupFace) !FontIndex {
|
||||
return .{ .style = style, .idx = @intCast(idx) };
|
||||
}
|
||||
|
||||
/// Returns true if we have a face for the given style, though the face may
|
||||
/// not be loaded yet.
|
||||
pub fn hasFaceForStyle(self: Group, style: Style) bool {
|
||||
const list = self.faces.get(style);
|
||||
return list.items.len > 0;
|
||||
}
|
||||
|
||||
/// This will automatically create an italicized font from the regular
|
||||
/// font face if we don't have any italicized fonts.
|
||||
pub fn italicize(self: *Group) !void {
|
||||
@ -278,6 +280,11 @@ pub fn indexForCodepoint(
|
||||
style: Style,
|
||||
p: ?Presentation,
|
||||
) ?FontIndex {
|
||||
// If we've disabled a font style, then fall back to regular.
|
||||
if (style != .regular and !self.styles.get(style)) {
|
||||
return self.indexForCodepoint(cp, .regular, p);
|
||||
}
|
||||
|
||||
// Codepoint overrides.
|
||||
if (self.indexForCodepointOverride(cp)) |idx_| {
|
||||
if (idx_) |idx| return idx;
|
||||
@ -669,6 +676,50 @@ test {
|
||||
}
|
||||
}
|
||||
|
||||
test "disabled font style" {
|
||||
const testing = std.testing;
|
||||
const alloc = testing.allocator;
|
||||
const testFont = @import("test.zig").fontRegular;
|
||||
|
||||
var atlas_greyscale = try font.Atlas.init(alloc, 512, .greyscale);
|
||||
defer atlas_greyscale.deinit(alloc);
|
||||
|
||||
var lib = try Library.init();
|
||||
defer lib.deinit();
|
||||
|
||||
var group = try init(alloc, lib, .{ .points = 12 });
|
||||
defer group.deinit();
|
||||
|
||||
// Disable bold
|
||||
group.styles.set(.bold, false);
|
||||
|
||||
// Same font but we can test the style in the index
|
||||
_ = try group.addFace(.regular, .{ .loaded = try Face.init(lib, testFont, .{ .points = 12 }) });
|
||||
_ = try group.addFace(.bold, .{ .loaded = try Face.init(lib, testFont, .{ .points = 12 }) });
|
||||
_ = try group.addFace(.italic, .{ .loaded = try Face.init(lib, testFont, .{ .points = 12 }) });
|
||||
|
||||
// Regular should work fine
|
||||
{
|
||||
const idx = group.indexForCodepoint('A', .regular, null).?;
|
||||
try testing.expectEqual(Style.regular, idx.style);
|
||||
try testing.expectEqual(@as(FontIndex.IndexInt, 0), idx.idx);
|
||||
}
|
||||
|
||||
// Bold should go to regular
|
||||
{
|
||||
const idx = group.indexForCodepoint('A', .bold, null).?;
|
||||
try testing.expectEqual(Style.regular, idx.style);
|
||||
try testing.expectEqual(@as(FontIndex.IndexInt, 0), idx.idx);
|
||||
}
|
||||
|
||||
// Italic should still work
|
||||
{
|
||||
const idx = group.indexForCodepoint('A', .italic, null).?;
|
||||
try testing.expectEqual(Style.italic, idx.style);
|
||||
try testing.expectEqual(@as(FontIndex.IndexInt, 0), idx.idx);
|
||||
}
|
||||
}
|
||||
|
||||
test "face count limit" {
|
||||
const testing = std.testing;
|
||||
const alloc = testing.allocator;
|
||||
|
@ -102,6 +102,7 @@ texture_color: objc.Object, // MTLTexture
|
||||
pub const DerivedConfig = struct {
|
||||
font_thicken: bool,
|
||||
font_features: std.ArrayList([]const u8),
|
||||
font_styles: font.Group.StyleStatus,
|
||||
cursor_color: ?terminal.color.RGB,
|
||||
cursor_text: ?terminal.color.RGB,
|
||||
background: terminal.color.RGB,
|
||||
@ -121,10 +122,17 @@ pub const DerivedConfig = struct {
|
||||
};
|
||||
errdefer font_features.deinit();
|
||||
|
||||
// Get our font styles
|
||||
var font_styles = font.Group.StyleStatus.initFill(true);
|
||||
font_styles.set(.bold, config.@"font-style-bold" != .false);
|
||||
font_styles.set(.italic, config.@"font-style-italic" != .false);
|
||||
font_styles.set(.bold_italic, config.@"font-style-bold-italic" != .false);
|
||||
|
||||
return .{
|
||||
.background_opacity = @max(0, @min(1, config.@"background-opacity")),
|
||||
.font_thicken = config.@"font-thicken",
|
||||
.font_features = font_features,
|
||||
.font_styles = font_styles,
|
||||
|
||||
.cursor_color = if (config.@"cursor-color") |col|
|
||||
col.toTerminalRGB()
|
||||
@ -975,13 +983,15 @@ fn prepKittyGraphics(
|
||||
|
||||
/// Update the configuration.
|
||||
pub fn changeConfig(self: *Metal, config: *DerivedConfig) !void {
|
||||
// If font thickening settings change, we need to reset our
|
||||
// font texture completely because we need to re-render the glyphs.
|
||||
if (self.config.font_thicken != config.font_thicken) {
|
||||
self.font_group.reset();
|
||||
self.font_group.atlas_greyscale.clear();
|
||||
self.font_group.atlas_color.clear();
|
||||
}
|
||||
// On configuration change we always reset our font group. There
|
||||
// are a variety of configurations that can change font settings
|
||||
// so to be safe we just always reset it. This has a performance hit
|
||||
// when its not necessary but config reloading shouldn't be so
|
||||
// common to cause a problem.
|
||||
self.font_group.reset();
|
||||
self.font_group.group.styles = config.font_styles;
|
||||
self.font_group.atlas_greyscale.clear();
|
||||
self.font_group.atlas_color.clear();
|
||||
|
||||
// We always redo the font shaper in case font features changed. We
|
||||
// could check to see if there was an actual config change but this is
|
||||
|
@ -216,6 +216,7 @@ const GPUCellMode = enum(u8) {
|
||||
pub const DerivedConfig = struct {
|
||||
font_thicken: bool,
|
||||
font_features: std.ArrayList([]const u8),
|
||||
font_styles: font.Group.StyleStatus,
|
||||
cursor_color: ?terminal.color.RGB,
|
||||
cursor_text: ?terminal.color.RGB,
|
||||
background: terminal.color.RGB,
|
||||
@ -235,10 +236,17 @@ pub const DerivedConfig = struct {
|
||||
};
|
||||
errdefer font_features.deinit();
|
||||
|
||||
// Get our font styles
|
||||
var font_styles = font.Group.StyleStatus.initFill(true);
|
||||
font_styles.set(.bold, config.@"font-style-bold" != .false);
|
||||
font_styles.set(.italic, config.@"font-style-italic" != .false);
|
||||
font_styles.set(.bold_italic, config.@"font-style-bold-italic" != .false);
|
||||
|
||||
return .{
|
||||
.background_opacity = @max(0, @min(1, config.@"background-opacity")),
|
||||
.font_thicken = config.@"font-thicken",
|
||||
.font_features = font_features,
|
||||
.font_styles = font_styles,
|
||||
|
||||
.cursor_color = if (config.@"cursor-color") |col|
|
||||
col.toTerminalRGB()
|
||||
@ -1205,13 +1213,15 @@ fn gridSize(self: *const OpenGL, screen_size: renderer.ScreenSize) renderer.Grid
|
||||
|
||||
/// Update the configuration.
|
||||
pub fn changeConfig(self: *OpenGL, config: *DerivedConfig) !void {
|
||||
// If font thickening settings change, we need to reset our
|
||||
// font texture completely because we need to re-render the glyphs.
|
||||
if (self.config.font_thicken != config.font_thicken) {
|
||||
self.font_group.reset();
|
||||
self.font_group.atlas_greyscale.clear();
|
||||
self.font_group.atlas_color.clear();
|
||||
}
|
||||
// On configuration change we always reset our font group. There
|
||||
// are a variety of configurations that can change font settings
|
||||
// so to be safe we just always reset it. This has a performance hit
|
||||
// when its not necessary but config reloading shouldn't be so
|
||||
// common to cause a problem.
|
||||
self.font_group.reset();
|
||||
self.font_group.group.styles = config.font_styles;
|
||||
self.font_group.atlas_greyscale.clear();
|
||||
self.font_group.atlas_color.clear();
|
||||
|
||||
// We always redo the font shaper in case font features changed. We
|
||||
// could check to see if there was an actual config change but this is
|
||||
|
Reference in New Issue
Block a user