mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-07-15 08:16:13 +03:00
font: remove old files
This commit is contained in:
1179
src/font/Group.zig
1179
src/font/Group.zig
File diff suppressed because it is too large
Load Diff
@ -1,381 +0,0 @@
|
|||||||
//! A glyph cache sits on top of a Group and caches the results from it.
|
|
||||||
const GroupCache = @This();
|
|
||||||
|
|
||||||
const std = @import("std");
|
|
||||||
const assert = std.debug.assert;
|
|
||||||
const Allocator = std.mem.Allocator;
|
|
||||||
|
|
||||||
const font = @import("main.zig");
|
|
||||||
const Face = @import("main.zig").Face;
|
|
||||||
const DeferredFace = @import("main.zig").DeferredFace;
|
|
||||||
const Library = @import("main.zig").Library;
|
|
||||||
const Glyph = @import("main.zig").Glyph;
|
|
||||||
const Style = @import("main.zig").Style;
|
|
||||||
const Group = @import("main.zig").Group;
|
|
||||||
const Presentation = @import("main.zig").Presentation;
|
|
||||||
|
|
||||||
const log = std.log.scoped(.font_groupcache);
|
|
||||||
|
|
||||||
/// Cache for codepoints to font indexes in a group.
|
|
||||||
codepoints: std.AutoHashMapUnmanaged(CodepointKey, ?Group.FontIndex) = .{},
|
|
||||||
|
|
||||||
/// Cache for glyph renders.
|
|
||||||
glyphs: std.AutoHashMapUnmanaged(GlyphKey, Glyph) = .{},
|
|
||||||
|
|
||||||
/// The underlying font group. Users are expected to use this directly
|
|
||||||
/// to setup the group or make changes. Beware some changes require a reset
|
|
||||||
/// (see reset).
|
|
||||||
group: Group,
|
|
||||||
|
|
||||||
/// The texture atlas to store renders in. The GroupCache has to store these
|
|
||||||
/// because the cached Glyph result is dependent on the Atlas.
|
|
||||||
atlas_greyscale: font.Atlas,
|
|
||||||
atlas_color: font.Atlas,
|
|
||||||
|
|
||||||
const CodepointKey = struct {
|
|
||||||
style: Style,
|
|
||||||
codepoint: u32,
|
|
||||||
presentation: ?Presentation,
|
|
||||||
};
|
|
||||||
|
|
||||||
const GlyphKey = struct {
|
|
||||||
index: Group.FontIndex,
|
|
||||||
glyph: u32,
|
|
||||||
opts: font.face.RenderOptions,
|
|
||||||
};
|
|
||||||
|
|
||||||
/// The GroupCache takes ownership of Group and will free it.
|
|
||||||
pub fn init(alloc: Allocator, group: Group) !GroupCache {
|
|
||||||
var atlas_greyscale = try font.Atlas.init(alloc, 512, .greyscale);
|
|
||||||
errdefer atlas_greyscale.deinit(alloc);
|
|
||||||
var atlas_color = try font.Atlas.init(alloc, 512, .rgba);
|
|
||||||
errdefer atlas_color.deinit(alloc);
|
|
||||||
|
|
||||||
var result: GroupCache = .{
|
|
||||||
.group = group,
|
|
||||||
.atlas_greyscale = atlas_greyscale,
|
|
||||||
.atlas_color = atlas_color,
|
|
||||||
};
|
|
||||||
|
|
||||||
// We set an initial capacity that can fit a good number of characters.
|
|
||||||
// This number was picked empirically based on my own terminal usage.
|
|
||||||
try result.codepoints.ensureTotalCapacity(alloc, 128);
|
|
||||||
try result.glyphs.ensureTotalCapacity(alloc, 128);
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn deinit(self: *GroupCache, alloc: Allocator) void {
|
|
||||||
self.codepoints.deinit(alloc);
|
|
||||||
self.glyphs.deinit(alloc);
|
|
||||||
self.atlas_greyscale.deinit(alloc);
|
|
||||||
self.atlas_color.deinit(alloc);
|
|
||||||
self.group.deinit();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Reset the cache. This should be called:
|
|
||||||
///
|
|
||||||
/// - If an Atlas was reset
|
|
||||||
/// - If a font group font size was changed
|
|
||||||
/// - If a font group font set was changed
|
|
||||||
///
|
|
||||||
pub fn reset(self: *GroupCache) void {
|
|
||||||
self.codepoints.clearRetainingCapacity();
|
|
||||||
self.glyphs.clearRetainingCapacity();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Resize the fonts in the group. This will clear the cache.
|
|
||||||
pub fn setSize(self: *GroupCache, size: font.face.DesiredSize) !void {
|
|
||||||
try self.group.setSize(size);
|
|
||||||
|
|
||||||
// Reset our internal state
|
|
||||||
self.reset();
|
|
||||||
|
|
||||||
// Clear our atlases
|
|
||||||
self.atlas_greyscale.clear();
|
|
||||||
self.atlas_color.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get the font index for a given codepoint. This is cached.
|
|
||||||
pub fn indexForCodepoint(
|
|
||||||
self: *GroupCache,
|
|
||||||
alloc: Allocator,
|
|
||||||
cp: u32,
|
|
||||||
style: Style,
|
|
||||||
p: ?Presentation,
|
|
||||||
) !?Group.FontIndex {
|
|
||||||
const key: CodepointKey = .{ .style = style, .codepoint = cp, .presentation = p };
|
|
||||||
const gop = try self.codepoints.getOrPut(alloc, key);
|
|
||||||
|
|
||||||
// If it is in the cache, use it.
|
|
||||||
if (gop.found_existing) return gop.value_ptr.*;
|
|
||||||
|
|
||||||
// Load a value and cache it. This even caches negative matches.
|
|
||||||
const value = self.group.indexForCodepoint(cp, style, p);
|
|
||||||
gop.value_ptr.* = value;
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Render a glyph. This automatically determines the correct texture
|
|
||||||
/// atlas to use and caches the result.
|
|
||||||
pub fn renderGlyph(
|
|
||||||
self: *GroupCache,
|
|
||||||
alloc: Allocator,
|
|
||||||
index: Group.FontIndex,
|
|
||||||
glyph_index: u32,
|
|
||||||
opts: font.face.RenderOptions,
|
|
||||||
) !Glyph {
|
|
||||||
const key: GlyphKey = .{ .index = index, .glyph = glyph_index, .opts = opts };
|
|
||||||
const gop = try self.glyphs.getOrPut(alloc, key);
|
|
||||||
|
|
||||||
// If it is in the cache, use it.
|
|
||||||
if (gop.found_existing) return gop.value_ptr.*;
|
|
||||||
|
|
||||||
// Uncached, render it
|
|
||||||
const atlas: *font.Atlas = switch (try self.group.presentationFromIndex(index)) {
|
|
||||||
.text => &self.atlas_greyscale,
|
|
||||||
.emoji => &self.atlas_color,
|
|
||||||
};
|
|
||||||
const glyph = self.group.renderGlyph(
|
|
||||||
alloc,
|
|
||||||
atlas,
|
|
||||||
index,
|
|
||||||
glyph_index,
|
|
||||||
opts,
|
|
||||||
) catch |err| switch (err) {
|
|
||||||
// If the atlas is full, we resize it
|
|
||||||
error.AtlasFull => blk: {
|
|
||||||
try atlas.grow(alloc, atlas.size * 2);
|
|
||||||
break :blk try self.group.renderGlyph(
|
|
||||||
alloc,
|
|
||||||
atlas,
|
|
||||||
index,
|
|
||||||
glyph_index,
|
|
||||||
opts,
|
|
||||||
);
|
|
||||||
},
|
|
||||||
|
|
||||||
else => return err,
|
|
||||||
};
|
|
||||||
|
|
||||||
// Cache and return
|
|
||||||
gop.value_ptr.* = glyph;
|
|
||||||
return glyph;
|
|
||||||
}
|
|
||||||
|
|
||||||
test {
|
|
||||||
const testing = std.testing;
|
|
||||||
const alloc = testing.allocator;
|
|
||||||
const testFont = @import("test.zig").fontRegular;
|
|
||||||
// const testEmoji = @import("test.zig").fontEmoji;
|
|
||||||
|
|
||||||
var atlas_greyscale = try font.Atlas.init(alloc, 512, .greyscale);
|
|
||||||
defer atlas_greyscale.deinit(alloc);
|
|
||||||
|
|
||||||
var lib = try Library.init();
|
|
||||||
defer lib.deinit();
|
|
||||||
|
|
||||||
var cache = try init(alloc, try Group.init(
|
|
||||||
alloc,
|
|
||||||
lib,
|
|
||||||
.{ .points = 12 },
|
|
||||||
));
|
|
||||||
defer cache.deinit(alloc);
|
|
||||||
|
|
||||||
// Setup group
|
|
||||||
_ = try cache.group.addFace(
|
|
||||||
.regular,
|
|
||||||
.{ .loaded = try Face.init(lib, testFont, .{ .size = .{ .points = 12 } }) },
|
|
||||||
);
|
|
||||||
var group = cache.group;
|
|
||||||
|
|
||||||
// Visible ASCII. Do it twice to verify cache.
|
|
||||||
var i: u32 = 32;
|
|
||||||
while (i < 127) : (i += 1) {
|
|
||||||
const idx = (try cache.indexForCodepoint(alloc, i, .regular, null)).?;
|
|
||||||
try testing.expectEqual(Style.regular, idx.style);
|
|
||||||
try testing.expectEqual(@as(Group.FontIndex.IndexInt, 0), idx.idx);
|
|
||||||
|
|
||||||
// Render
|
|
||||||
const face = try cache.group.faceFromIndex(idx);
|
|
||||||
const glyph_index = face.glyphIndex(i).?;
|
|
||||||
_ = try cache.renderGlyph(
|
|
||||||
alloc,
|
|
||||||
idx,
|
|
||||||
glyph_index,
|
|
||||||
.{},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Do it again, but reset the group so that we know for sure its not hitting it
|
|
||||||
{
|
|
||||||
cache.group = undefined;
|
|
||||||
defer cache.group = group;
|
|
||||||
|
|
||||||
i = 32;
|
|
||||||
while (i < 127) : (i += 1) {
|
|
||||||
const idx = (try cache.indexForCodepoint(alloc, i, .regular, null)).?;
|
|
||||||
try testing.expectEqual(Style.regular, idx.style);
|
|
||||||
try testing.expectEqual(@as(Group.FontIndex.IndexInt, 0), idx.idx);
|
|
||||||
|
|
||||||
// Render
|
|
||||||
const face = try group.faceFromIndex(idx);
|
|
||||||
const glyph_index = face.glyphIndex(i).?;
|
|
||||||
_ = try cache.renderGlyph(
|
|
||||||
alloc,
|
|
||||||
idx,
|
|
||||||
glyph_index,
|
|
||||||
.{},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The wasm-compatible API.
|
|
||||||
pub const Wasm = struct {
|
|
||||||
const wasm = @import("../os/wasm.zig");
|
|
||||||
const alloc = wasm.alloc;
|
|
||||||
|
|
||||||
export fn group_cache_new(group: *Group) ?*GroupCache {
|
|
||||||
return group_cache_new_(group) catch null;
|
|
||||||
}
|
|
||||||
|
|
||||||
fn group_cache_new_(group: *Group) !*GroupCache {
|
|
||||||
var gc = try GroupCache.init(alloc, group.*);
|
|
||||||
errdefer gc.deinit(alloc);
|
|
||||||
|
|
||||||
const result = try alloc.create(GroupCache);
|
|
||||||
errdefer alloc.destroy(result);
|
|
||||||
result.* = gc;
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
export fn group_cache_free(ptr: ?*GroupCache) void {
|
|
||||||
if (ptr) |v| {
|
|
||||||
v.deinit(alloc);
|
|
||||||
alloc.destroy(v);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export fn group_cache_set_size(self: *GroupCache, size: u16) void {
|
|
||||||
return self.setSize(.{ .points = size }) catch |err| {
|
|
||||||
log.warn("error setting group cache size err={}", .{err});
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Presentation is negative for doesn't matter.
|
|
||||||
export fn group_cache_index_for_codepoint(self: *GroupCache, cp: u32, style: u16, p: i16) i16 {
|
|
||||||
const presentation: ?Presentation = if (p < 0) null else @enumFromInt(p);
|
|
||||||
if (self.indexForCodepoint(
|
|
||||||
alloc,
|
|
||||||
cp,
|
|
||||||
@enumFromInt(style),
|
|
||||||
presentation,
|
|
||||||
)) |idx| {
|
|
||||||
return @intCast(@as(u8, @bitCast(idx orelse return -1)));
|
|
||||||
} else |err| {
|
|
||||||
log.warn("error getting index for codepoint from group cache size err={}", .{err});
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export fn group_cache_render_glyph(
|
|
||||||
self: *GroupCache,
|
|
||||||
idx: i16,
|
|
||||||
cp: u32,
|
|
||||||
max_height: u16,
|
|
||||||
) ?*Glyph {
|
|
||||||
return group_cache_render_glyph_(self, idx, cp, max_height) catch |err| {
|
|
||||||
log.warn("error rendering group cache glyph err={}", .{err});
|
|
||||||
return null;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
fn group_cache_render_glyph_(
|
|
||||||
self: *GroupCache,
|
|
||||||
idx_: i16,
|
|
||||||
cp: u32,
|
|
||||||
max_height_: u16,
|
|
||||||
) !*Glyph {
|
|
||||||
const idx = @as(Group.FontIndex, @bitCast(@as(u8, @intCast(idx_))));
|
|
||||||
const max_height = if (max_height_ <= 0) null else max_height_;
|
|
||||||
const glyph = try self.renderGlyph(alloc, idx, cp, .{
|
|
||||||
.max_height = max_height,
|
|
||||||
});
|
|
||||||
|
|
||||||
const result = try alloc.create(Glyph);
|
|
||||||
errdefer alloc.destroy(result);
|
|
||||||
result.* = glyph;
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
export fn group_cache_atlas_greyscale(self: *GroupCache) *font.Atlas {
|
|
||||||
return &self.atlas_greyscale;
|
|
||||||
}
|
|
||||||
|
|
||||||
export fn group_cache_atlas_color(self: *GroupCache) *font.Atlas {
|
|
||||||
return &self.atlas_color;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
test "resize" {
|
|
||||||
const testing = std.testing;
|
|
||||||
const alloc = testing.allocator;
|
|
||||||
const testFont = @import("test.zig").fontRegular;
|
|
||||||
// const testEmoji = @import("test.zig").fontEmoji;
|
|
||||||
|
|
||||||
var atlas_greyscale = try font.Atlas.init(alloc, 512, .greyscale);
|
|
||||||
defer atlas_greyscale.deinit(alloc);
|
|
||||||
|
|
||||||
var lib = try Library.init();
|
|
||||||
defer lib.deinit();
|
|
||||||
|
|
||||||
var cache = try init(alloc, try Group.init(
|
|
||||||
alloc,
|
|
||||||
lib,
|
|
||||||
.{ .points = 12 },
|
|
||||||
));
|
|
||||||
defer cache.deinit(alloc);
|
|
||||||
|
|
||||||
// Setup group
|
|
||||||
_ = try cache.group.addFace(
|
|
||||||
.regular,
|
|
||||||
.{ .loaded = try Face.init(
|
|
||||||
lib,
|
|
||||||
testFont,
|
|
||||||
.{ .size = .{ .points = 12, .xdpi = 96, .ydpi = 96 } },
|
|
||||||
) },
|
|
||||||
);
|
|
||||||
|
|
||||||
// Load a letter
|
|
||||||
{
|
|
||||||
const idx = (try cache.indexForCodepoint(alloc, 'A', .regular, null)).?;
|
|
||||||
const face = try cache.group.faceFromIndex(idx);
|
|
||||||
const glyph_index = face.glyphIndex('A').?;
|
|
||||||
const glyph = try cache.renderGlyph(
|
|
||||||
alloc,
|
|
||||||
idx,
|
|
||||||
glyph_index,
|
|
||||||
.{},
|
|
||||||
);
|
|
||||||
|
|
||||||
try testing.expectEqual(@as(u32, 11), glyph.height);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Resize
|
|
||||||
try cache.setSize(.{ .points = 24, .xdpi = 96, .ydpi = 96 });
|
|
||||||
{
|
|
||||||
const idx = (try cache.indexForCodepoint(alloc, 'A', .regular, null)).?;
|
|
||||||
const face = try cache.group.faceFromIndex(idx);
|
|
||||||
const glyph_index = face.glyphIndex('A').?;
|
|
||||||
const glyph = try cache.renderGlyph(
|
|
||||||
alloc,
|
|
||||||
idx,
|
|
||||||
glyph_index,
|
|
||||||
.{},
|
|
||||||
);
|
|
||||||
|
|
||||||
try testing.expectEqual(@as(u32, 21), glyph.height);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,518 +0,0 @@
|
|||||||
//! 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 builtin = @import("builtin");
|
|
||||||
const assert = std.debug.assert;
|
|
||||||
const Allocator = std.mem.Allocator;
|
|
||||||
const ArenaAllocator = std.heap.ArenaAllocator;
|
|
||||||
const fontpkg = @import("main.zig");
|
|
||||||
const Collection = fontpkg.Collection;
|
|
||||||
const Discover = fontpkg.Discover;
|
|
||||||
const Style = fontpkg.Style;
|
|
||||||
const Library = fontpkg.Library;
|
|
||||||
const Metrics = fontpkg.face.Metrics;
|
|
||||||
const CodepointMap = fontpkg.CodepointMap;
|
|
||||||
const DesiredSize = fontpkg.face.DesiredSize;
|
|
||||||
const Face = fontpkg.Face;
|
|
||||||
const Group = fontpkg.Group;
|
|
||||||
const GroupCache = fontpkg.GroupCache;
|
|
||||||
const discovery = @import("discovery.zig");
|
|
||||||
const configpkg = @import("../config.zig");
|
|
||||||
const Config = configpkg.Config;
|
|
||||||
|
|
||||||
const log = std.log.scoped(.font_group_cache_set);
|
|
||||||
|
|
||||||
/// The allocator to use for all heap allocations.
|
|
||||||
alloc: Allocator,
|
|
||||||
|
|
||||||
/// The map of font configurations to GroupCache instances.
|
|
||||||
map: Map = .{},
|
|
||||||
|
|
||||||
/// The font library that is used for all font groups.
|
|
||||||
font_lib: Library,
|
|
||||||
|
|
||||||
/// Font discovery mechanism.
|
|
||||||
font_discover: ?Discover = null,
|
|
||||||
|
|
||||||
/// Initialize a new GroupCacheSet.
|
|
||||||
pub fn init(alloc: Allocator) !GroupCacheSet {
|
|
||||||
var font_lib = try Library.init();
|
|
||||||
errdefer font_lib.deinit();
|
|
||||||
|
|
||||||
return .{
|
|
||||||
.alloc = alloc,
|
|
||||||
.map = .{},
|
|
||||||
.font_lib = font_lib,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn deinit(self: *GroupCacheSet) void {
|
|
||||||
var it = self.map.iterator();
|
|
||||||
while (it.next()) |entry| {
|
|
||||||
entry.key_ptr.deinit();
|
|
||||||
const ref = entry.value_ptr.*;
|
|
||||||
ref.cache.deinit(self.alloc);
|
|
||||||
self.alloc.destroy(ref.cache);
|
|
||||||
}
|
|
||||||
self.map.deinit(self.alloc);
|
|
||||||
|
|
||||||
if (comptime Discover != void) {
|
|
||||||
if (self.font_discover) |*v| v.deinit();
|
|
||||||
}
|
|
||||||
|
|
||||||
self.font_lib.deinit();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Initialize a GroupCache for the given font configuration. If the
|
|
||||||
/// GroupCache 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.
|
|
||||||
pub fn groupRef(
|
|
||||||
self: *GroupCacheSet,
|
|
||||||
config: *const Config,
|
|
||||||
font_size: DesiredSize,
|
|
||||||
) !struct { Key, *GroupCache } {
|
|
||||||
var key = try Key.init(self.alloc, config);
|
|
||||||
errdefer key.deinit();
|
|
||||||
|
|
||||||
const gop = try self.map.getOrPut(self.alloc, key);
|
|
||||||
if (gop.found_existing) {
|
|
||||||
log.debug("found cached GroupCache 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.cache };
|
|
||||||
}
|
|
||||||
errdefer self.map.removeByPtr(gop.key_ptr);
|
|
||||||
|
|
||||||
log.debug("initializing new GroupCache for font config", .{});
|
|
||||||
|
|
||||||
// A new font config, initialize the cache.
|
|
||||||
const cache = try self.alloc.create(GroupCache);
|
|
||||||
errdefer self.alloc.destroy(cache);
|
|
||||||
gop.value_ptr.* = .{
|
|
||||||
.cache = cache,
|
|
||||||
.ref = 1,
|
|
||||||
};
|
|
||||||
|
|
||||||
// Build our font face collection that we'll use to initialize
|
|
||||||
// the Group.
|
|
||||||
|
|
||||||
cache.* = try GroupCache.init(self.alloc, group: {
|
|
||||||
var group = try Group.init(
|
|
||||||
self.alloc,
|
|
||||||
self.font_lib,
|
|
||||||
font_size,
|
|
||||||
try self.collection(&key, font_size),
|
|
||||||
);
|
|
||||||
errdefer group.deinit();
|
|
||||||
group.metric_modifiers = key.metric_modifiers;
|
|
||||||
group.codepoint_map = key.codepoint_map;
|
|
||||||
group.discover = try self.discover();
|
|
||||||
|
|
||||||
// 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);
|
|
||||||
|
|
||||||
// Auto-italicize if we have to.
|
|
||||||
try group.italicize();
|
|
||||||
|
|
||||||
log.info("font loading complete, any non-logged faces are using the built-in font", .{});
|
|
||||||
break :group group;
|
|
||||||
});
|
|
||||||
errdefer cache.deinit(self.alloc);
|
|
||||||
|
|
||||||
return .{ gop.key_ptr.*, gop.value_ptr.cache };
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Builds the Collection for the given configuration key and
|
|
||||||
/// initial font size.
|
|
||||||
fn collection(
|
|
||||||
self: *GroupCacheSet,
|
|
||||||
key: *const Key,
|
|
||||||
size: DesiredSize,
|
|
||||||
) !Collection {
|
|
||||||
var c = try Collection.init(self.alloc);
|
|
||||||
errdefer c.deinit(self.alloc);
|
|
||||||
|
|
||||||
const opts: fontpkg.face.Options = .{
|
|
||||||
.size = size,
|
|
||||||
.metric_modifiers = &key.metric_modifiers,
|
|
||||||
};
|
|
||||||
|
|
||||||
// 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 },
|
|
||||||
);
|
|
||||||
} else 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,
|
|
||||||
opts,
|
|
||||||
) },
|
|
||||||
);
|
|
||||||
_ = try c.add(
|
|
||||||
self.alloc,
|
|
||||||
.bold,
|
|
||||||
.{ .fallback_loaded = try Face.init(
|
|
||||||
self.font_lib,
|
|
||||||
face_bold_ttf,
|
|
||||||
opts,
|
|
||||||
) },
|
|
||||||
);
|
|
||||||
|
|
||||||
// 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()) 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,
|
|
||||||
opts,
|
|
||||||
) },
|
|
||||||
);
|
|
||||||
_ = try c.add(
|
|
||||||
self.alloc,
|
|
||||||
.regular,
|
|
||||||
.{ .fallback_loaded = try Face.init(
|
|
||||||
self.font_lib,
|
|
||||||
face_emoji_text_ttf,
|
|
||||||
opts,
|
|
||||||
) },
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return c;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Decrement the ref count for the given key. If the ref count is zero,
|
|
||||||
/// the GroupCache will be deinitialized and removed from the map.j:w
|
|
||||||
pub fn groupDeref(self: *GroupCacheSet, key: Key) void {
|
|
||||||
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.cache.deinit(self.alloc);
|
|
||||||
self.alloc.destroy(entry.value_ptr.cache);
|
|
||||||
self.map.removeByPtr(entry.key_ptr);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Map of font configurations to GroupCache instances. The GroupCache
|
|
||||||
/// instances are pointers that are heap allocated so that they're
|
|
||||||
/// stable pointers across hash map resizes.
|
|
||||||
pub const Map = std.HashMapUnmanaged(
|
|
||||||
Key,
|
|
||||||
RefGroupCache,
|
|
||||||
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: *GroupCacheSet) !?*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 GroupCache.
|
|
||||||
const RefGroupCache = struct {
|
|
||||||
cache: *GroupCache,
|
|
||||||
ref: u32 = 0,
|
|
||||||
};
|
|
||||||
|
|
||||||
/// 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,
|
|
||||||
|
|
||||||
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,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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);
|
|
||||||
break :set set;
|
|
||||||
};
|
|
||||||
|
|
||||||
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,
|
|
||||||
},
|
|
||||||
.codepoint_map = codepoint_map,
|
|
||||||
.metric_modifiers = metric_modifiers,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
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);
|
|
||||||
autoHash(hasher, self.codepoint_map);
|
|
||||||
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 k = try Key.init(alloc, &cfg);
|
|
||||||
defer k.deinit();
|
|
||||||
|
|
||||||
try testing.expect(k.hashcode() > 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
test "basics" {
|
|
||||||
const testing = std.testing;
|
|
||||||
const alloc = testing.allocator;
|
|
||||||
|
|
||||||
var set = try GroupCacheSet.init(alloc);
|
|
||||||
defer set.deinit();
|
|
||||||
}
|
|
@ -10,9 +10,6 @@ pub const CodepointResolver = @import("CodepointResolver.zig");
|
|||||||
pub const Collection = @import("Collection.zig");
|
pub const Collection = @import("Collection.zig");
|
||||||
pub const DeferredFace = @import("DeferredFace.zig");
|
pub const DeferredFace = @import("DeferredFace.zig");
|
||||||
pub const Face = face.Face;
|
pub const Face = face.Face;
|
||||||
pub const Group = @import("Group.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 Metrics = face.Metrics;
|
pub const Metrics = face.Metrics;
|
||||||
pub const shape = @import("shape.zig");
|
pub const shape = @import("shape.zig");
|
||||||
@ -30,8 +27,6 @@ pub usingnamespace @import("library.zig");
|
|||||||
pub usingnamespace if (builtin.target.isWasm()) struct {
|
pub usingnamespace if (builtin.target.isWasm()) struct {
|
||||||
pub usingnamespace Atlas.Wasm;
|
pub usingnamespace Atlas.Wasm;
|
||||||
pub usingnamespace DeferredFace.Wasm;
|
pub usingnamespace DeferredFace.Wasm;
|
||||||
pub usingnamespace Group.Wasm;
|
|
||||||
pub usingnamespace GroupCache.Wasm;
|
|
||||||
pub usingnamespace face.web_canvas.Wasm;
|
pub usingnamespace face.web_canvas.Wasm;
|
||||||
pub usingnamespace shape.web_canvas.Wasm;
|
pub usingnamespace shape.web_canvas.Wasm;
|
||||||
} else struct {};
|
} else struct {};
|
||||||
|
@ -160,7 +160,7 @@ pub const DerivedConfig = struct {
|
|||||||
|
|
||||||
font_thicken: bool,
|
font_thicken: bool,
|
||||||
font_features: std.ArrayListUnmanaged([:0]const u8),
|
font_features: std.ArrayListUnmanaged([:0]const u8),
|
||||||
font_styles: font.Group.StyleStatus,
|
font_styles: font.CodepointResolver.StyleStatus,
|
||||||
cursor_color: ?terminal.color.RGB,
|
cursor_color: ?terminal.color.RGB,
|
||||||
cursor_opacity: f64,
|
cursor_opacity: f64,
|
||||||
cursor_text: ?terminal.color.RGB,
|
cursor_text: ?terminal.color.RGB,
|
||||||
@ -189,7 +189,7 @@ pub const DerivedConfig = struct {
|
|||||||
const font_features = try config.@"font-feature".list.clone(alloc);
|
const font_features = try config.@"font-feature".list.clone(alloc);
|
||||||
|
|
||||||
// Get our font styles
|
// Get our font styles
|
||||||
var font_styles = font.Group.StyleStatus.initFill(true);
|
var font_styles = font.CodepointResolver.StyleStatus.initFill(true);
|
||||||
font_styles.set(.bold, config.@"font-style-bold" != .false);
|
font_styles.set(.bold, config.@"font-style-bold" != .false);
|
||||||
font_styles.set(.italic, config.@"font-style-italic" != .false);
|
font_styles.set(.italic, config.@"font-style-italic" != .false);
|
||||||
font_styles.set(.bold_italic, config.@"font-style-bold-italic" != .false);
|
font_styles.set(.bold_italic, config.@"font-style-bold-italic" != .false);
|
||||||
|
@ -243,7 +243,7 @@ pub const DerivedConfig = struct {
|
|||||||
|
|
||||||
font_thicken: bool,
|
font_thicken: bool,
|
||||||
font_features: std.ArrayListUnmanaged([:0]const u8),
|
font_features: std.ArrayListUnmanaged([:0]const u8),
|
||||||
font_styles: font.Group.StyleStatus,
|
font_styles: font.CodepointResolver.StyleStatus,
|
||||||
cursor_color: ?terminal.color.RGB,
|
cursor_color: ?terminal.color.RGB,
|
||||||
cursor_text: ?terminal.color.RGB,
|
cursor_text: ?terminal.color.RGB,
|
||||||
cursor_opacity: f64,
|
cursor_opacity: f64,
|
||||||
@ -272,7 +272,7 @@ pub const DerivedConfig = struct {
|
|||||||
const font_features = try config.@"font-feature".list.clone(alloc);
|
const font_features = try config.@"font-feature".list.clone(alloc);
|
||||||
|
|
||||||
// Get our font styles
|
// Get our font styles
|
||||||
var font_styles = font.Group.StyleStatus.initFill(true);
|
var font_styles = font.CodepointResolver.StyleStatus.initFill(true);
|
||||||
font_styles.set(.bold, config.@"font-style-bold" != .false);
|
font_styles.set(.bold, config.@"font-style-bold" != .false);
|
||||||
font_styles.set(.italic, config.@"font-style-italic" != .false);
|
font_styles.set(.italic, config.@"font-style-italic" != .false);
|
||||||
font_styles.set(.bold_italic, config.@"font-style-bold-italic" != .false);
|
font_styles.set(.bold_italic, config.@"font-style-bold-italic" != .false);
|
||||||
|
Reference in New Issue
Block a user