mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-07-16 16:56:09 +03:00
font: introduce GroupCacheSet, use it for descriptors to start
This commit is contained in:
101
src/Surface.zig
101
src/Surface.zig
@ -320,6 +320,12 @@ pub fn init(
|
|||||||
.ydpi = @intFromFloat(y_dpi),
|
.ydpi = @intFromFloat(y_dpi),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Create our font group key. This is used to determine if we have
|
||||||
|
// a cached font group we can use already. Otherwise, this can be
|
||||||
|
// used to build the group.
|
||||||
|
var font_group_key = try font.GroupCacheSet.Key.init(alloc, config);
|
||||||
|
defer font_group_key.deinit();
|
||||||
|
|
||||||
// Find all the fonts for this surface
|
// Find all the fonts for this surface
|
||||||
//
|
//
|
||||||
// Future: we can share the font group amongst all surfaces to save
|
// Future: we can share the font group amongst all surfaces to save
|
||||||
@ -368,84 +374,41 @@ pub fn init(
|
|||||||
// A buffer we use to store the font names for logging.
|
// A buffer we use to store the font names for logging.
|
||||||
var name_buf: [256]u8 = undefined;
|
var name_buf: [256]u8 = undefined;
|
||||||
|
|
||||||
for (config.@"font-family".list.items) |family| {
|
inline for (@typeInfo(font.Style).Enum.fields) |field| {
|
||||||
var disco_it = try disco.discover(alloc, .{
|
const style = @field(font.Style, field.name);
|
||||||
.family = family,
|
for (font_group_key.descriptorsForStyle(style)) |desc| {
|
||||||
.style = config.@"font-style".nameValue(),
|
var disco_it = try disco.discover(alloc, desc);
|
||||||
.size = font_size.points,
|
defer disco_it.deinit();
|
||||||
.variations = config.@"font-variation".list.items,
|
if (try disco_it.next()) |face| {
|
||||||
});
|
log.info("font {s}: {s}", .{
|
||||||
defer disco_it.deinit();
|
field.name,
|
||||||
if (try disco_it.next()) |face| {
|
try face.name(&name_buf),
|
||||||
log.info("font regular: {s}", .{try face.name(&name_buf)});
|
});
|
||||||
_ = try group.addFace(.regular, .{ .deferred = face });
|
_ = try group.addFace(style, .{ .deferred = face });
|
||||||
} else log.warn("font-family not found: {s}", .{family});
|
} else log.warn("font-family {s} not found: {s}", .{
|
||||||
}
|
field.name,
|
||||||
|
desc.family.?,
|
||||||
// In all the styled cases below, we prefer to specify an exact
|
});
|
||||||
// style via the `font-style` configuration. If a style is not
|
}
|
||||||
// specified, we use the discovery mechanism to search for a
|
|
||||||
// style category such as bold, italic, etc. We can't specify both
|
|
||||||
// because the latter will restrict the search to only that. If
|
|
||||||
// a user says `font-style = italic` for the bold face for example,
|
|
||||||
// no results would be found if we restrict to ALSO searching for
|
|
||||||
// italic.
|
|
||||||
for (config.@"font-family-bold".list.items) |family| {
|
|
||||||
const style = config.@"font-style-bold".nameValue();
|
|
||||||
var disco_it = try disco.discover(alloc, .{
|
|
||||||
.family = family,
|
|
||||||
.style = style,
|
|
||||||
.size = font_size.points,
|
|
||||||
.bold = style == null,
|
|
||||||
.variations = config.@"font-variation-bold".list.items,
|
|
||||||
});
|
|
||||||
defer disco_it.deinit();
|
|
||||||
if (try disco_it.next()) |face| {
|
|
||||||
log.info("font bold: {s}", .{try face.name(&name_buf)});
|
|
||||||
_ = try group.addFace(.bold, .{ .deferred = face });
|
|
||||||
} else log.warn("font-family-bold not found: {s}", .{family});
|
|
||||||
}
|
|
||||||
for (config.@"font-family-italic".list.items) |family| {
|
|
||||||
const style = config.@"font-style-italic".nameValue();
|
|
||||||
var disco_it = try disco.discover(alloc, .{
|
|
||||||
.family = family,
|
|
||||||
.style = style,
|
|
||||||
.size = font_size.points,
|
|
||||||
.italic = style == null,
|
|
||||||
.variations = config.@"font-variation-italic".list.items,
|
|
||||||
});
|
|
||||||
defer disco_it.deinit();
|
|
||||||
if (try disco_it.next()) |face| {
|
|
||||||
log.info("font italic: {s}", .{try face.name(&name_buf)});
|
|
||||||
_ = try group.addFace(.italic, .{ .deferred = face });
|
|
||||||
} else log.warn("font-family-italic not found: {s}", .{family});
|
|
||||||
}
|
|
||||||
for (config.@"font-family-bold-italic".list.items) |family| {
|
|
||||||
const style = config.@"font-style-bold-italic".nameValue();
|
|
||||||
var disco_it = try disco.discover(alloc, .{
|
|
||||||
.family = family,
|
|
||||||
.style = style,
|
|
||||||
.size = font_size.points,
|
|
||||||
.bold = style == null,
|
|
||||||
.italic = style == null,
|
|
||||||
.variations = config.@"font-variation-bold-italic".list.items,
|
|
||||||
});
|
|
||||||
defer disco_it.deinit();
|
|
||||||
if (try disco_it.next()) |face| {
|
|
||||||
log.info("font bold+italic: {s}", .{try face.name(&name_buf)});
|
|
||||||
_ = try group.addFace(.bold_italic, .{ .deferred = face });
|
|
||||||
} else log.warn("font-family-bold-italic not found: {s}", .{family});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Our built-in font will be used as a backup
|
// Our built-in font will be used as a backup
|
||||||
_ = try group.addFace(
|
_ = try group.addFace(
|
||||||
.regular,
|
.regular,
|
||||||
.{ .fallback_loaded = try font.Face.init(font_lib, face_ttf, group.faceOptions()) },
|
.{ .fallback_loaded = try font.Face.init(
|
||||||
|
font_lib,
|
||||||
|
face_ttf,
|
||||||
|
group.faceOptions(),
|
||||||
|
) },
|
||||||
);
|
);
|
||||||
_ = try group.addFace(
|
_ = try group.addFace(
|
||||||
.bold,
|
.bold,
|
||||||
.{ .fallback_loaded = try font.Face.init(font_lib, face_bold_ttf, group.faceOptions()) },
|
.{ .fallback_loaded = try font.Face.init(
|
||||||
|
font_lib,
|
||||||
|
face_bold_ttf,
|
||||||
|
group.faceOptions(),
|
||||||
|
) },
|
||||||
);
|
);
|
||||||
|
|
||||||
// Auto-italicize if we have to.
|
// Auto-italicize if we have to.
|
||||||
|
164
src/font/GroupCacheSet.zig
Normal file
164
src/font/GroupCacheSet.zig
Normal file
@ -0,0 +1,164 @@
|
|||||||
|
//! This structure contains a set of GroupCache instances keyed by
|
||||||
|
//! unique font configuration.
|
||||||
|
//!
|
||||||
|
//! Most terminals (surfaces) will share the same font configuration.
|
||||||
|
//! This structure allows expensive font information such as
|
||||||
|
//! the font atlas, glyph cache, font faces, etc. to be shared.
|
||||||
|
//!
|
||||||
|
//! The Ghostty renderers which use this information run on their
|
||||||
|
//! own threads so this structure is thread-safe. It expects that
|
||||||
|
//! the case where all glyphs are cached is the common case and
|
||||||
|
//! optimizes for that. When a glyph is not cached, all renderers
|
||||||
|
//! that share the same font configuration will be blocked until
|
||||||
|
//! the glyph is cached.
|
||||||
|
const GroupCacheSet = @This();
|
||||||
|
|
||||||
|
const std = @import("std");
|
||||||
|
const assert = std.debug.assert;
|
||||||
|
const Allocator = std.mem.Allocator;
|
||||||
|
const ArenaAllocator = std.heap.ArenaAllocator;
|
||||||
|
const Style = @import("main.zig").Style;
|
||||||
|
const discovery = @import("discovery.zig");
|
||||||
|
const configpkg = @import("../config.zig");
|
||||||
|
const Config = configpkg.Config;
|
||||||
|
|
||||||
|
/// The key used to uniquely identify a font configuration.
|
||||||
|
pub const Key = struct {
|
||||||
|
arena: ArenaAllocator,
|
||||||
|
|
||||||
|
/// The descriptors used for all the fonts added to the
|
||||||
|
/// initial group, including all styles. This is hashed
|
||||||
|
/// in order so the order matters. All users of the struct
|
||||||
|
/// should ensure that the order is consistent.
|
||||||
|
descriptors: []const discovery.Descriptor,
|
||||||
|
|
||||||
|
/// These are the offsets into the descriptors array for
|
||||||
|
/// each style. For example, bold is from
|
||||||
|
/// offsets[@intFromEnum(.bold) - 1] to
|
||||||
|
/// offsets[@intFromEnum(.bold)].
|
||||||
|
style_offsets: StyleOffsets = .{0} ** style_offsets_len,
|
||||||
|
|
||||||
|
const style_offsets_len = std.enums.directEnumArrayLen(Style, 0);
|
||||||
|
const StyleOffsets = [style_offsets_len]usize;
|
||||||
|
|
||||||
|
comptime {
|
||||||
|
// We assume this throughout this structure. If this changes
|
||||||
|
// we may need to change this structure.
|
||||||
|
assert(@intFromEnum(Style.regular) == 0);
|
||||||
|
assert(@intFromEnum(Style.bold) == 1);
|
||||||
|
assert(@intFromEnum(Style.italic) == 2);
|
||||||
|
assert(@intFromEnum(Style.bold_italic) == 3);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn init(
|
||||||
|
alloc_gpa: Allocator,
|
||||||
|
config: *const Config,
|
||||||
|
) !Key {
|
||||||
|
var arena = ArenaAllocator.init(alloc_gpa);
|
||||||
|
errdefer arena.deinit();
|
||||||
|
const alloc = arena.allocator();
|
||||||
|
|
||||||
|
var descriptors = std.ArrayList(discovery.Descriptor).init(alloc);
|
||||||
|
defer descriptors.deinit();
|
||||||
|
for (config.@"font-family".list.items) |family| {
|
||||||
|
try descriptors.append(.{
|
||||||
|
.family = family,
|
||||||
|
.style = config.@"font-style".nameValue(),
|
||||||
|
.size = config.@"font-size",
|
||||||
|
.variations = config.@"font-variation".list.items,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// In all the styled cases below, we prefer to specify an exact
|
||||||
|
// style via the `font-style` configuration. If a style is not
|
||||||
|
// specified, we use the discovery mechanism to search for a
|
||||||
|
// style category such as bold, italic, etc. We can't specify both
|
||||||
|
// because the latter will restrict the search to only that. If
|
||||||
|
// a user says `font-style = italic` for the bold face for example,
|
||||||
|
// no results would be found if we restrict to ALSO searching for
|
||||||
|
// italic.
|
||||||
|
for (config.@"font-family-bold".list.items) |family| {
|
||||||
|
const style = config.@"font-style-bold".nameValue();
|
||||||
|
try descriptors.append(.{
|
||||||
|
.family = family,
|
||||||
|
.style = style,
|
||||||
|
.size = config.@"font-size",
|
||||||
|
.bold = style == null,
|
||||||
|
.variations = config.@"font-variation".list.items,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
for (config.@"font-family-italic".list.items) |family| {
|
||||||
|
const style = config.@"font-style-italic".nameValue();
|
||||||
|
try descriptors.append(.{
|
||||||
|
.family = family,
|
||||||
|
.style = style,
|
||||||
|
.size = config.@"font-size",
|
||||||
|
.italic = style == null,
|
||||||
|
.variations = config.@"font-variation".list.items,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
for (config.@"font-family-bold-italic".list.items) |family| {
|
||||||
|
const style = config.@"font-style-bold-italic".nameValue();
|
||||||
|
try descriptors.append(.{
|
||||||
|
.family = family,
|
||||||
|
.style = style,
|
||||||
|
.size = config.@"font-size",
|
||||||
|
.bold = style == null,
|
||||||
|
.italic = style == null,
|
||||||
|
.variations = config.@"font-variation".list.items,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return .{
|
||||||
|
.arena = arena,
|
||||||
|
.descriptors = try descriptors.toOwnedSlice(),
|
||||||
|
.style_offsets = .{
|
||||||
|
config.@"font-family".list.items.len,
|
||||||
|
config.@"font-family-bold".list.items.len,
|
||||||
|
config.@"font-family-italic".list.items.len,
|
||||||
|
config.@"font-family-bold-italic".list.items.len,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deinit(self: *Key) void {
|
||||||
|
self.arena.deinit();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the descriptors for the given font style that can be
|
||||||
|
/// used with discovery.
|
||||||
|
pub fn descriptorsForStyle(
|
||||||
|
self: Key,
|
||||||
|
style: Style,
|
||||||
|
) []const discovery.Descriptor {
|
||||||
|
const idx = @intFromEnum(style);
|
||||||
|
const start: usize = if (idx == 0) 0 else self.style_offsets[idx - 1];
|
||||||
|
const end = self.style_offsets[idx];
|
||||||
|
return self.descriptors[start..end];
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Hash the key with the given hasher.
|
||||||
|
pub fn hash(self: Key, hasher: anytype) void {
|
||||||
|
const autoHash = std.hash.autoHash;
|
||||||
|
autoHash(hasher, self.descriptors.len);
|
||||||
|
for (self.descriptors) |d| d.hash(hasher);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns a hash code that can be used to uniquely identify this
|
||||||
|
/// action.
|
||||||
|
pub fn hashcode(self: Key) u64 {
|
||||||
|
var hasher = std.hash.Wyhash.init(0);
|
||||||
|
self.hash(&hasher);
|
||||||
|
return hasher.final();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
test "Key" {
|
||||||
|
const testing = std.testing;
|
||||||
|
const alloc = testing.allocator;
|
||||||
|
var cfg = try Config.default(alloc);
|
||||||
|
defer cfg.deinit();
|
||||||
|
|
||||||
|
var k = try Key.init(alloc, &cfg);
|
||||||
|
defer k.deinit();
|
||||||
|
}
|
@ -10,6 +10,7 @@ pub const DeferredFace = @import("DeferredFace.zig");
|
|||||||
pub const Face = face.Face;
|
pub const Face = face.Face;
|
||||||
pub const Group = @import("Group.zig");
|
pub const Group = @import("Group.zig");
|
||||||
pub const GroupCache = @import("GroupCache.zig");
|
pub const GroupCache = @import("GroupCache.zig");
|
||||||
|
pub const GroupCacheSet = @import("GroupCacheSet.zig");
|
||||||
pub const Glyph = @import("Glyph.zig");
|
pub const Glyph = @import("Glyph.zig");
|
||||||
pub const shape = @import("shape.zig");
|
pub const shape = @import("shape.zig");
|
||||||
pub const Shaper = shape.Shaper;
|
pub const Shaper = shape.Shaper;
|
||||||
|
Reference in New Issue
Block a user