mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-07-14 15:56:13 +03:00

This adds a new configuration "font-synthetic-style" to enable or disable synthetic styles. This is different from "font-style-*" which specifies a named style or disables a style completely. Instead, "font-synthetic-style" will disable only the creation of synthetic styles in the case a font does not support a given style. This is useful for users who want to obviously know when a font doesn't support a given style or a user who wants to explicitly only use the styles that were designed by the font designer. The default value is to enable all synthetic styles.
728 lines
26 KiB
Zig
728 lines
26 KiB
Zig
//! This structure contains a set of SharedGrid structures 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.
|
|
//!
|
|
//! This structure is thread-safe when the operations are documented
|
|
//! as thread-safe.
|
|
const SharedGridSet = @This();
|
|
|
|
const std = @import("std");
|
|
const builtin = @import("builtin");
|
|
const assert = std.debug.assert;
|
|
const Allocator = std.mem.Allocator;
|
|
const ArenaAllocator = std.heap.ArenaAllocator;
|
|
const font = @import("main.zig");
|
|
const CodepointResolver = font.CodepointResolver;
|
|
const Collection = font.Collection;
|
|
const Discover = font.Discover;
|
|
const Style = font.Style;
|
|
const Library = font.Library;
|
|
const Metrics = font.face.Metrics;
|
|
const CodepointMap = font.CodepointMap;
|
|
const DesiredSize = font.face.DesiredSize;
|
|
const Face = font.Face;
|
|
const SharedGrid = font.SharedGrid;
|
|
const discovery = @import("discovery.zig");
|
|
const configpkg = @import("../config.zig");
|
|
const Config = configpkg.Config;
|
|
|
|
const log = std.log.scoped(.font_shared_grid_set);
|
|
|
|
/// The allocator to use for all heap allocations.
|
|
alloc: Allocator,
|
|
|
|
/// The map of font configurations to SharedGrid instances.
|
|
map: Map = .{},
|
|
|
|
/// The font library that is used for all font groups.
|
|
font_lib: Library,
|
|
|
|
/// Font discovery mechanism.
|
|
font_discover: ?Discover = null,
|
|
|
|
/// Lock to protect multi-threaded access to the map.
|
|
lock: std.Thread.Mutex = .{},
|
|
|
|
/// Initialize a new SharedGridSet.
|
|
pub fn init(alloc: Allocator) !SharedGridSet {
|
|
var font_lib = try Library.init();
|
|
errdefer font_lib.deinit();
|
|
|
|
return .{
|
|
.alloc = alloc,
|
|
.map = .{},
|
|
.font_lib = font_lib,
|
|
};
|
|
}
|
|
|
|
pub fn deinit(self: *SharedGridSet) void {
|
|
var it = self.map.iterator();
|
|
while (it.next()) |entry| {
|
|
entry.key_ptr.deinit();
|
|
const v = entry.value_ptr.*;
|
|
v.grid.deinit(self.alloc);
|
|
self.alloc.destroy(v.grid);
|
|
}
|
|
self.map.deinit(self.alloc);
|
|
|
|
if (comptime Discover != void) {
|
|
if (self.font_discover) |*v| v.deinit();
|
|
}
|
|
|
|
self.font_lib.deinit();
|
|
}
|
|
|
|
/// Returns the number of cached grids.
|
|
pub fn count(self: *SharedGridSet) usize {
|
|
self.lock.lock();
|
|
defer self.lock.unlock();
|
|
return self.map.count();
|
|
}
|
|
|
|
/// Initialize a SharedGrid for the given font configuration. If the
|
|
/// SharedGrid is not present it will be initialized with a ref count of
|
|
/// 1. If it is present, the ref count will be incremented.
|
|
///
|
|
/// This is NOT thread-safe.
|
|
///
|
|
/// The returned data (key and grid) should never be freed. The memory is
|
|
/// owned by the set and will be freed when the ref count reaches zero.
|
|
pub fn ref(
|
|
self: *SharedGridSet,
|
|
config: *const DerivedConfig,
|
|
font_size: DesiredSize,
|
|
) !struct { Key, *SharedGrid } {
|
|
var key = try Key.init(self.alloc, config, font_size);
|
|
errdefer key.deinit();
|
|
|
|
self.lock.lock();
|
|
defer self.lock.unlock();
|
|
|
|
const gop = try self.map.getOrPut(self.alloc, key);
|
|
if (gop.found_existing) {
|
|
log.debug("found cached grid for font config", .{});
|
|
|
|
// We can deinit the key because we found a cached value.
|
|
key.deinit();
|
|
|
|
// Increment our ref count and return the cache
|
|
gop.value_ptr.ref += 1;
|
|
return .{ gop.key_ptr.*, gop.value_ptr.grid };
|
|
}
|
|
errdefer self.map.removeByPtr(gop.key_ptr);
|
|
|
|
log.debug("initializing new grid for font config", .{});
|
|
|
|
// A new font config, initialize the cache.
|
|
const grid = try self.alloc.create(SharedGrid);
|
|
errdefer self.alloc.destroy(grid);
|
|
gop.value_ptr.* = .{
|
|
.grid = grid,
|
|
.ref = 1,
|
|
};
|
|
|
|
grid.* = try SharedGrid.init(self.alloc, resolver: {
|
|
// Build our collection. This is the expensive operation that
|
|
// involves finding fonts, loading them (maybe, some are deferred),
|
|
// etc.
|
|
var c = try self.collection(&key, font_size, config);
|
|
errdefer c.deinit(self.alloc);
|
|
|
|
// Setup our enabled/disabled styles
|
|
var styles = CodepointResolver.StyleStatus.initFill(true);
|
|
styles.set(.bold, config.@"font-style-bold" != .false);
|
|
styles.set(.italic, config.@"font-style-italic" != .false);
|
|
styles.set(.bold_italic, config.@"font-style-bold-italic" != .false);
|
|
|
|
// Init our resolver which just requires setting fields.
|
|
break :resolver .{
|
|
.collection = c,
|
|
.styles = styles,
|
|
.discover = try self.discover(),
|
|
.codepoint_map = key.codepoint_map,
|
|
};
|
|
});
|
|
errdefer grid.deinit(self.alloc);
|
|
|
|
return .{ gop.key_ptr.*, gop.value_ptr.grid };
|
|
}
|
|
|
|
/// Builds the Collection for the given configuration key and
|
|
/// initial font size.
|
|
fn collection(
|
|
self: *SharedGridSet,
|
|
key: *const Key,
|
|
size: DesiredSize,
|
|
config: *const DerivedConfig,
|
|
) !Collection {
|
|
// A quick note on memory management:
|
|
// - font_lib is owned by the SharedGridSet
|
|
// - metric_modifiers is owned by the key which is freed only when
|
|
// the ref count for this grid reaches zero.
|
|
const load_options: Collection.LoadOptions = .{
|
|
.library = self.font_lib,
|
|
.size = size,
|
|
.metric_modifiers = key.metric_modifiers,
|
|
};
|
|
|
|
var c = Collection.init();
|
|
errdefer c.deinit(self.alloc);
|
|
c.load_options = load_options;
|
|
|
|
// Search for fonts
|
|
if (Discover != void) discover: {
|
|
const disco = try self.discover() orelse {
|
|
log.warn(
|
|
"font discovery not available, cannot search for fonts",
|
|
.{},
|
|
);
|
|
break :discover;
|
|
};
|
|
|
|
// A buffer we use to store the font names for logging.
|
|
var name_buf: [256]u8 = undefined;
|
|
|
|
inline for (@typeInfo(Style).Enum.fields) |field| {
|
|
const style = @field(Style, field.name);
|
|
for (key.descriptorsForStyle(style)) |desc| {
|
|
{
|
|
var disco_it = try disco.discover(self.alloc, desc);
|
|
defer disco_it.deinit();
|
|
if (try disco_it.next()) |face| {
|
|
log.info("font {s}: {s}", .{
|
|
field.name,
|
|
try face.name(&name_buf),
|
|
});
|
|
|
|
_ = try c.add(
|
|
self.alloc,
|
|
style,
|
|
.{ .deferred = face },
|
|
);
|
|
|
|
continue;
|
|
}
|
|
}
|
|
|
|
// If there are variation configurations and we didn't find
|
|
// the font, then we retry the discovery with all stylistic
|
|
// bits set to false. This is because some fonts may not
|
|
// set the stylistic bit in their table but still support
|
|
// axes to mimic the style. At the time of writing, Berkeley
|
|
// Mono Variable is like this. See #2140.
|
|
if (style != .regular and desc.variations.len > 0) {
|
|
var disco_it = try disco.discover(self.alloc, desc: {
|
|
var copy = desc;
|
|
copy.bold = false;
|
|
copy.italic = false;
|
|
break :desc copy;
|
|
});
|
|
defer disco_it.deinit();
|
|
if (try disco_it.next()) |face| {
|
|
log.info("font {s}: {s}", .{
|
|
field.name,
|
|
try face.name(&name_buf),
|
|
});
|
|
|
|
_ = try c.add(
|
|
self.alloc,
|
|
style,
|
|
.{ .deferred = face },
|
|
);
|
|
|
|
continue;
|
|
}
|
|
}
|
|
|
|
log.warn("font-family {s} not found: {s}", .{
|
|
field.name,
|
|
desc.family.?,
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
// Our built-in font will be used as a backup
|
|
_ = try c.add(
|
|
self.alloc,
|
|
.regular,
|
|
.{ .fallback_loaded = try Face.init(
|
|
self.font_lib,
|
|
face_ttf,
|
|
load_options.faceOptions(),
|
|
) },
|
|
);
|
|
|
|
// On macOS, always search for and add the Apple Emoji font
|
|
// as our preferred emoji font for fallback. We do this in case
|
|
// people add other emoji fonts to their system, we always want to
|
|
// prefer the official one. Users can override this by explicitly
|
|
// specifying a font-family for emoji.
|
|
if (comptime builtin.target.isDarwin() and Discover != void) apple_emoji: {
|
|
const disco = try self.discover() orelse break :apple_emoji;
|
|
var disco_it = try disco.discover(self.alloc, .{
|
|
.family = "Apple Color Emoji",
|
|
});
|
|
defer disco_it.deinit();
|
|
if (try disco_it.next()) |face| {
|
|
_ = try c.add(
|
|
self.alloc,
|
|
.regular,
|
|
.{ .fallback_deferred = face },
|
|
);
|
|
}
|
|
}
|
|
|
|
// Emoji fallback. We don't include this on Mac since Mac is expected
|
|
// to always have the Apple Emoji available on the system.
|
|
if (comptime !builtin.target.isDarwin() or Discover == void) {
|
|
_ = try c.add(
|
|
self.alloc,
|
|
.regular,
|
|
.{ .fallback_loaded = try Face.init(
|
|
self.font_lib,
|
|
face_emoji_ttf,
|
|
load_options.faceOptions(),
|
|
) },
|
|
);
|
|
_ = try c.add(
|
|
self.alloc,
|
|
.regular,
|
|
.{ .fallback_loaded = try Face.init(
|
|
self.font_lib,
|
|
face_emoji_text_ttf,
|
|
load_options.faceOptions(),
|
|
) },
|
|
);
|
|
}
|
|
|
|
// Complete our styles to ensure we have something to satisfy every
|
|
// possible style request.
|
|
try c.completeStyles(self.alloc, config.@"font-synthetic-style");
|
|
|
|
return c;
|
|
}
|
|
|
|
/// Decrement the ref count for the given key. If the ref count is zero,
|
|
/// the grid will be deinitialized and removed from the map.j:w
|
|
pub fn deref(self: *SharedGridSet, key: Key) void {
|
|
self.lock.lock();
|
|
defer self.lock.unlock();
|
|
|
|
const entry = self.map.getEntry(key) orelse return;
|
|
assert(entry.value_ptr.ref >= 1);
|
|
|
|
// If we have more than one reference, decrement and return.
|
|
if (entry.value_ptr.ref > 1) {
|
|
entry.value_ptr.ref -= 1;
|
|
return;
|
|
}
|
|
|
|
// We are at a zero ref count so deinit the group and remove.
|
|
entry.key_ptr.deinit();
|
|
entry.value_ptr.grid.deinit(self.alloc);
|
|
self.alloc.destroy(entry.value_ptr.grid);
|
|
self.map.removeByPtr(entry.key_ptr);
|
|
}
|
|
|
|
/// Map of font configurations to grid instances. The grid
|
|
/// instances are pointers that are heap allocated so that they're
|
|
/// stable pointers across hash map resizes.
|
|
pub const Map = std.HashMapUnmanaged(
|
|
Key,
|
|
ReffedGrid,
|
|
struct {
|
|
const KeyType = Key;
|
|
|
|
pub fn hash(ctx: @This(), k: KeyType) u64 {
|
|
_ = ctx;
|
|
return k.hashcode();
|
|
}
|
|
|
|
pub fn eql(ctx: @This(), a: KeyType, b: KeyType) bool {
|
|
return ctx.hash(a) == ctx.hash(b);
|
|
}
|
|
},
|
|
std.hash_map.default_max_load_percentage,
|
|
);
|
|
|
|
/// Initialize once and return the font discovery mechanism. This remains
|
|
/// initialized throughout the lifetime of the application because some
|
|
/// font discovery mechanisms (i.e. fontconfig) are unsafe to reinit.
|
|
fn discover(self: *SharedGridSet) !?*Discover {
|
|
// If we're built without a font discovery mechanism, return null
|
|
if (comptime Discover == void) return null;
|
|
|
|
// If we initialized, use it
|
|
if (self.font_discover) |*v| return v;
|
|
|
|
self.font_discover = Discover.init();
|
|
return &self.font_discover.?;
|
|
}
|
|
|
|
/// Ref-counted SharedGrid.
|
|
const ReffedGrid = struct {
|
|
grid: *SharedGrid,
|
|
ref: u32 = 0,
|
|
};
|
|
|
|
/// This is the configuration required to create a key without having
|
|
/// to keep the full Ghostty configuration around.
|
|
pub const DerivedConfig = struct {
|
|
arena: ArenaAllocator,
|
|
|
|
@"font-family": configpkg.RepeatableString,
|
|
@"font-family-bold": configpkg.RepeatableString,
|
|
@"font-family-italic": configpkg.RepeatableString,
|
|
@"font-family-bold-italic": configpkg.RepeatableString,
|
|
@"font-style": configpkg.FontStyle,
|
|
@"font-style-bold": configpkg.FontStyle,
|
|
@"font-style-italic": configpkg.FontStyle,
|
|
@"font-style-bold-italic": configpkg.FontStyle,
|
|
@"font-variation": configpkg.RepeatableFontVariation,
|
|
@"font-variation-bold": configpkg.RepeatableFontVariation,
|
|
@"font-variation-italic": configpkg.RepeatableFontVariation,
|
|
@"font-variation-bold-italic": configpkg.RepeatableFontVariation,
|
|
@"font-codepoint-map": configpkg.RepeatableCodepointMap,
|
|
@"font-synthetic-style": configpkg.FontSyntheticStyle,
|
|
@"adjust-cell-width": ?Metrics.Modifier,
|
|
@"adjust-cell-height": ?Metrics.Modifier,
|
|
@"adjust-font-baseline": ?Metrics.Modifier,
|
|
@"adjust-underline-position": ?Metrics.Modifier,
|
|
@"adjust-underline-thickness": ?Metrics.Modifier,
|
|
@"adjust-strikethrough-position": ?Metrics.Modifier,
|
|
@"adjust-strikethrough-thickness": ?Metrics.Modifier,
|
|
@"adjust-cursor-thickness": ?Metrics.Modifier,
|
|
|
|
/// Initialize a DerivedConfig. The config should be either a
|
|
/// config.Config or another DerivedConfig to clone from.
|
|
pub fn init(alloc_gpa: Allocator, config: anytype) !DerivedConfig {
|
|
var arena = ArenaAllocator.init(alloc_gpa);
|
|
errdefer arena.deinit();
|
|
const alloc = arena.allocator();
|
|
|
|
return .{
|
|
.@"font-family" = try config.@"font-family".clone(alloc),
|
|
.@"font-family-bold" = try config.@"font-family-bold".clone(alloc),
|
|
.@"font-family-italic" = try config.@"font-family-italic".clone(alloc),
|
|
.@"font-family-bold-italic" = try config.@"font-family-bold-italic".clone(alloc),
|
|
.@"font-style" = try config.@"font-style".clone(alloc),
|
|
.@"font-style-bold" = try config.@"font-style-bold".clone(alloc),
|
|
.@"font-style-italic" = try config.@"font-style-italic".clone(alloc),
|
|
.@"font-style-bold-italic" = try config.@"font-style-bold-italic".clone(alloc),
|
|
.@"font-variation" = try config.@"font-variation".clone(alloc),
|
|
.@"font-variation-bold" = try config.@"font-variation-bold".clone(alloc),
|
|
.@"font-variation-italic" = try config.@"font-variation-italic".clone(alloc),
|
|
.@"font-variation-bold-italic" = try config.@"font-variation-bold-italic".clone(alloc),
|
|
.@"font-codepoint-map" = try config.@"font-codepoint-map".clone(alloc),
|
|
.@"font-synthetic-style" = config.@"font-synthetic-style",
|
|
.@"adjust-cell-width" = config.@"adjust-cell-width",
|
|
.@"adjust-cell-height" = config.@"adjust-cell-height",
|
|
.@"adjust-font-baseline" = config.@"adjust-font-baseline",
|
|
.@"adjust-underline-position" = config.@"adjust-underline-position",
|
|
.@"adjust-underline-thickness" = config.@"adjust-underline-thickness",
|
|
.@"adjust-strikethrough-position" = config.@"adjust-strikethrough-position",
|
|
.@"adjust-strikethrough-thickness" = config.@"adjust-strikethrough-thickness",
|
|
.@"adjust-cursor-thickness" = config.@"adjust-cursor-thickness",
|
|
|
|
// This must be last so the arena contains all our allocations
|
|
// from above since Zig does assignment in order.
|
|
.arena = arena,
|
|
};
|
|
}
|
|
|
|
pub fn deinit(self: *DerivedConfig) void {
|
|
self.arena.deinit();
|
|
}
|
|
};
|
|
|
|
/// 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,
|
|
|
|
/// The codepoint map configuration.
|
|
codepoint_map: CodepointMap = .{},
|
|
|
|
/// The metric modifier set configuration.
|
|
metric_modifiers: Metrics.ModifierSet = .{},
|
|
|
|
/// The configured font size for this key. We don't use this
|
|
/// directly but it is used as part of the hash for the
|
|
/// font grid.
|
|
font_size: DesiredSize = .{ .points = 12 },
|
|
|
|
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_src: *const DerivedConfig,
|
|
font_size: DesiredSize,
|
|
) !Key {
|
|
var arena = ArenaAllocator.init(alloc_gpa);
|
|
errdefer arena.deinit();
|
|
const alloc = arena.allocator();
|
|
|
|
// Clone our configuration. We need to do this because the lifetime
|
|
// of the derived config is usually shorter than that of a key
|
|
// and we use pointers into the derived config for the key. We
|
|
// can remove this if we wanted by dupe-ing the memory we use
|
|
// from DerivedConfig below.
|
|
var config = try DerivedConfig.init(alloc, config_src);
|
|
|
|
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 = font_size.points,
|
|
.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 = font_size.points,
|
|
.bold = style == null,
|
|
.variations = config.@"font-variation-bold".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 = font_size.points,
|
|
.italic = style == null,
|
|
.variations = config.@"font-variation-italic".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 = font_size.points,
|
|
.bold = style == null,
|
|
.italic = style == null,
|
|
.variations = config.@"font-variation-bold-italic".list.items,
|
|
});
|
|
}
|
|
|
|
// Setup the codepoint map
|
|
const codepoint_map: CodepointMap = map: {
|
|
const map = config.@"font-codepoint-map";
|
|
if (map.map.list.len == 0) break :map .{};
|
|
const clone = try config.@"font-codepoint-map".clone(alloc);
|
|
break :map clone.map;
|
|
};
|
|
|
|
// Metric modifiers
|
|
const metric_modifiers: Metrics.ModifierSet = set: {
|
|
var set: Metrics.ModifierSet = .{};
|
|
if (config.@"adjust-cell-width") |m| try set.put(alloc, .cell_width, m);
|
|
if (config.@"adjust-cell-height") |m| try set.put(alloc, .cell_height, m);
|
|
if (config.@"adjust-font-baseline") |m| try set.put(alloc, .cell_baseline, m);
|
|
if (config.@"adjust-underline-position") |m| try set.put(alloc, .underline_position, m);
|
|
if (config.@"adjust-underline-thickness") |m| try set.put(alloc, .underline_thickness, m);
|
|
if (config.@"adjust-strikethrough-position") |m| try set.put(alloc, .strikethrough_position, m);
|
|
if (config.@"adjust-strikethrough-thickness") |m| try set.put(alloc, .strikethrough_thickness, m);
|
|
if (config.@"adjust-cursor-thickness") |m| try set.put(alloc, .cursor_thickness, m);
|
|
break :set set;
|
|
};
|
|
|
|
const regular_offset = config.@"font-family".list.items.len;
|
|
const bold_offset = regular_offset + config.@"font-family-bold".list.items.len;
|
|
const italic_offset = bold_offset + config.@"font-family-italic".list.items.len;
|
|
const bold_italic_offset = italic_offset + config.@"font-family-bold-italic".list.items.len;
|
|
|
|
return .{
|
|
.arena = arena,
|
|
.descriptors = try descriptors.toOwnedSlice(),
|
|
.style_offsets = .{
|
|
regular_offset,
|
|
bold_offset,
|
|
italic_offset,
|
|
bold_italic_offset,
|
|
},
|
|
.codepoint_map = codepoint_map,
|
|
.metric_modifiers = metric_modifiers,
|
|
.font_size = font_size,
|
|
};
|
|
}
|
|
|
|
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, @as(u32, @bitCast(self.font_size.points)));
|
|
autoHash(hasher, self.font_size.xdpi);
|
|
autoHash(hasher, self.font_size.ydpi);
|
|
autoHash(hasher, self.descriptors.len);
|
|
for (self.descriptors) |d| d.hash(hasher);
|
|
self.codepoint_map.hash(hasher);
|
|
autoHash(hasher, self.metric_modifiers.count());
|
|
if (self.metric_modifiers.count() > 0) {
|
|
inline for (@typeInfo(Metrics.Key).Enum.fields) |field| {
|
|
const key = @field(Metrics.Key, field.name);
|
|
if (self.metric_modifiers.get(key)) |value| {
|
|
autoHash(hasher, key);
|
|
value.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();
|
|
}
|
|
};
|
|
|
|
const face_ttf = @embedFile("res/JetBrainsMono-Regular.ttf");
|
|
const face_bold_ttf = @embedFile("res/JetBrainsMono-Bold.ttf");
|
|
const face_emoji_ttf = @embedFile("res/NotoColorEmoji.ttf");
|
|
const face_emoji_text_ttf = @embedFile("res/NotoEmoji-Regular.ttf");
|
|
|
|
test "Key" {
|
|
const testing = std.testing;
|
|
const alloc = testing.allocator;
|
|
var cfg = try Config.default(alloc);
|
|
defer cfg.deinit();
|
|
|
|
var keycfg = try DerivedConfig.init(alloc, &cfg);
|
|
defer keycfg.deinit();
|
|
|
|
var k = try Key.init(alloc, &keycfg, .{ .points = 12 });
|
|
defer k.deinit();
|
|
|
|
var k2 = try Key.init(alloc, &keycfg, .{ .points = 12 });
|
|
defer k2.deinit();
|
|
|
|
try testing.expect(k.hashcode() > 0);
|
|
try testing.expectEqual(k.hashcode(), k2.hashcode());
|
|
}
|
|
|
|
test "Key different font points" {
|
|
const testing = std.testing;
|
|
const alloc = testing.allocator;
|
|
var cfg = try Config.default(alloc);
|
|
defer cfg.deinit();
|
|
|
|
var keycfg = try DerivedConfig.init(alloc, &cfg);
|
|
defer keycfg.deinit();
|
|
|
|
var k = try Key.init(alloc, &keycfg, .{ .points = 12 });
|
|
defer k.deinit();
|
|
|
|
var k2 = try Key.init(alloc, &keycfg, .{ .points = 16 });
|
|
defer k2.deinit();
|
|
|
|
try testing.expect(k.hashcode() != k2.hashcode());
|
|
}
|
|
|
|
test "Key different font DPI" {
|
|
const testing = std.testing;
|
|
const alloc = testing.allocator;
|
|
var cfg = try Config.default(alloc);
|
|
defer cfg.deinit();
|
|
|
|
var keycfg = try DerivedConfig.init(alloc, &cfg);
|
|
defer keycfg.deinit();
|
|
|
|
var k = try Key.init(alloc, &keycfg, .{ .points = 12, .xdpi = 1 });
|
|
defer k.deinit();
|
|
|
|
var k2 = try Key.init(alloc, &keycfg, .{ .points = 12, .xdpi = 2 });
|
|
defer k2.deinit();
|
|
|
|
try testing.expect(k.hashcode() != k2.hashcode());
|
|
}
|
|
|
|
test SharedGridSet {
|
|
const testing = std.testing;
|
|
const alloc = testing.allocator;
|
|
|
|
var set = try SharedGridSet.init(alloc);
|
|
defer set.deinit();
|
|
|
|
var cfg = try Config.default(alloc);
|
|
defer cfg.deinit();
|
|
|
|
var keycfg = try DerivedConfig.init(alloc, &cfg);
|
|
defer keycfg.deinit();
|
|
|
|
// Get a grid for the given config
|
|
const key1, const grid1 = try set.ref(&keycfg, .{ .points = 12 });
|
|
try testing.expectEqual(@as(usize, 1), set.count());
|
|
|
|
// Get another
|
|
const key2, const grid2 = try set.ref(&keycfg, .{ .points = 12 });
|
|
try testing.expectEqual(@as(usize, 1), set.count());
|
|
|
|
// They should be pointer equivalent
|
|
try testing.expectEqual(@intFromPtr(grid1), @intFromPtr(grid2));
|
|
|
|
// If I deref grid2 then we should still have a count of 1
|
|
set.deref(key2);
|
|
try testing.expectEqual(@as(usize, 1), set.count());
|
|
|
|
// If I deref grid1 then we should have a count of 0
|
|
set.deref(key1);
|
|
try testing.expectEqual(@as(usize, 0), set.count());
|
|
}
|