mirror of
https://github.com/ghostty-org/ghostty.git
synced 2026-06-09 05:14:27 +00:00
use new font.GroupCache, remove unused font structs
This commit is contained in:
@@ -102,14 +102,8 @@ pub fn hasColor(self: Face) bool {
|
||||
return self.face.hasColor();
|
||||
}
|
||||
|
||||
/// Load a glyph for this face. The codepoint can be either a u8 or
|
||||
/// []const u8 depending on if you know it is ASCII or must be UTF-8 decoded.
|
||||
pub fn loadGlyph(self: Face, alloc: Allocator, atlas: *Atlas, cp: u32) !Glyph {
|
||||
// We need a UTF32 codepoint for freetype
|
||||
const glyph_index = self.glyphIndex(cp) orelse return error.GlyphNotFound;
|
||||
return self.renderGlyph(alloc, atlas, glyph_index);
|
||||
}
|
||||
|
||||
/// Render a glyph using the glyph index. The rendered glyph is stored in the
|
||||
/// given texture atlas.
|
||||
pub fn renderGlyph(self: Face, alloc: Allocator, atlas: *Atlas, glyph_index: u32) !Glyph {
|
||||
// If our glyph has color, we want to render the color
|
||||
try self.face.loadGlyph(glyph_index, .{
|
||||
@@ -211,7 +205,7 @@ test {
|
||||
// Generate all visible ASCII
|
||||
var i: u8 = 32;
|
||||
while (i < 127) : (i += 1) {
|
||||
_ = try font.loadGlyph(alloc, &atlas, i);
|
||||
_ = try font.renderGlyph(alloc, &atlas, font.glyphIndex(i).?);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -228,5 +222,5 @@ test "color emoji" {
|
||||
var font = try init(lib, testFont, .{ .points = 12 });
|
||||
defer font.deinit();
|
||||
|
||||
_ = try font.loadGlyph(alloc, &atlas, '🥸');
|
||||
_ = try font.renderGlyph(alloc, &atlas, font.glyphIndex('🥸').?);
|
||||
}
|
||||
|
||||
@@ -1,159 +0,0 @@
|
||||
//! FallbackSet represents a set of families in priority order to load a glyph.
|
||||
//! This can be used to merge multiple font families together to find a glyph
|
||||
//! for a codepoint.
|
||||
const FallbackSet = @This();
|
||||
|
||||
const std = @import("std");
|
||||
const assert = std.debug.assert;
|
||||
const Allocator = std.mem.Allocator;
|
||||
|
||||
const Atlas = @import("../Atlas.zig");
|
||||
const Family = @import("main.zig").Family;
|
||||
const Library = @import("main.zig").Library;
|
||||
const Glyph = @import("main.zig").Glyph;
|
||||
const Style = @import("main.zig").Style;
|
||||
const codepoint = @import("main.zig").codepoint;
|
||||
|
||||
const log = std.log.scoped(.font_fallback);
|
||||
|
||||
/// The families to look for in order. This should be managed directly
|
||||
/// by the caller of the set. Deinit will deallocate this.
|
||||
families: std.ArrayListUnmanaged(Family) = .{},
|
||||
|
||||
/// A quick lookup that points directly to the family that loaded a glyph.
|
||||
glyphs: std.AutoHashMapUnmanaged(GlyphKey, usize) = .{},
|
||||
|
||||
const GlyphKey = struct {
|
||||
style: Style,
|
||||
codepoint: u32,
|
||||
};
|
||||
|
||||
pub fn deinit(self: *FallbackSet, alloc: Allocator) void {
|
||||
self.families.deinit(alloc);
|
||||
self.glyphs.deinit(alloc);
|
||||
self.* = undefined;
|
||||
}
|
||||
|
||||
pub const GetOrAdd = struct {
|
||||
/// Index of the family where the glyph was loaded from
|
||||
family: usize,
|
||||
|
||||
/// True if the glyph was found or whether it was newly loaded
|
||||
found_existing: bool,
|
||||
|
||||
/// The glyph
|
||||
glyph: *Glyph,
|
||||
};
|
||||
|
||||
pub fn getOrAddGlyph(
|
||||
self: *FallbackSet,
|
||||
alloc: Allocator,
|
||||
v: anytype,
|
||||
style: Style,
|
||||
) !GetOrAdd {
|
||||
assert(self.families.items.len > 0);
|
||||
|
||||
// We need a UTF32 codepoint
|
||||
const utf32 = codepoint(v);
|
||||
|
||||
// If we have this already, load it directly
|
||||
const glyphKey: GlyphKey = .{ .style = style, .codepoint = utf32 };
|
||||
const gop = try self.glyphs.getOrPut(alloc, glyphKey);
|
||||
if (gop.found_existing) {
|
||||
const i = gop.value_ptr.*;
|
||||
assert(i < self.families.items.len);
|
||||
return GetOrAdd{
|
||||
.family = i,
|
||||
.found_existing = true,
|
||||
.glyph = self.families.items[i].getGlyph(v, style) orelse unreachable,
|
||||
};
|
||||
}
|
||||
errdefer _ = self.glyphs.remove(glyphKey);
|
||||
|
||||
// Go through each familiy and look for a matching glyph
|
||||
var fam_i: ?usize = 0;
|
||||
const glyph = glyph: {
|
||||
for (self.families.items) |*family, i| {
|
||||
fam_i = i;
|
||||
|
||||
// If this family already has it loaded, return it.
|
||||
if (family.getGlyph(v, style)) |glyph| break :glyph glyph;
|
||||
|
||||
// Try to load it.
|
||||
if (family.addGlyph(alloc, v, style)) |glyph|
|
||||
break :glyph glyph
|
||||
else |err| switch (err) {
|
||||
// TODO: this probably doesn't belong here and should
|
||||
// be higher level... but how?
|
||||
error.AtlasFull => {
|
||||
try family.atlas.grow(alloc, family.atlas.size * 2);
|
||||
break :glyph try family.addGlyph(alloc, v, style);
|
||||
},
|
||||
|
||||
error.GlyphNotFound => {},
|
||||
else => return err,
|
||||
}
|
||||
}
|
||||
|
||||
// If we are regular, we use a fallback character
|
||||
log.warn("glyph not found, using fallback. codepoint={x}", .{utf32});
|
||||
fam_i = null;
|
||||
break :glyph try self.families.items[0].addGlyph(alloc, ' ', style);
|
||||
};
|
||||
|
||||
// If we found a real value, then cache it.
|
||||
// TODO: support caching fallbacks too
|
||||
if (fam_i) |i|
|
||||
gop.value_ptr.* = i
|
||||
else
|
||||
_ = self.glyphs.remove(glyphKey);
|
||||
|
||||
return GetOrAdd{
|
||||
.family = fam_i orelse 0,
|
||||
.glyph = glyph,
|
||||
|
||||
// Technically possible that we found this in a cache...
|
||||
.found_existing = false,
|
||||
};
|
||||
}
|
||||
|
||||
test {
|
||||
const fontRegular = @import("test.zig").fontRegular;
|
||||
const fontEmoji = @import("test.zig").fontEmoji;
|
||||
|
||||
const testing = std.testing;
|
||||
const alloc = testing.allocator;
|
||||
|
||||
var lib = try Library.init();
|
||||
defer lib.deinit();
|
||||
|
||||
var set: FallbackSet = .{};
|
||||
try set.families.append(alloc, fam: {
|
||||
var fam = Family.init(lib, try Atlas.init(alloc, 512, .greyscale));
|
||||
try fam.loadFaceFromMemory(.regular, fontRegular, .{ .points = 48 });
|
||||
break :fam fam;
|
||||
});
|
||||
try set.families.append(alloc, fam: {
|
||||
var fam = Family.init(lib, try Atlas.init(alloc, 512, .rgba));
|
||||
try fam.loadFaceFromMemory(.regular, fontEmoji, .{ .points = 48 });
|
||||
break :fam fam;
|
||||
});
|
||||
|
||||
defer {
|
||||
for (set.families.items) |*family| {
|
||||
family.atlas.deinit(alloc);
|
||||
family.deinit(alloc);
|
||||
}
|
||||
set.deinit(alloc);
|
||||
}
|
||||
|
||||
// Generate all visible ASCII
|
||||
var i: u8 = 32;
|
||||
while (i < 127) : (i += 1) {
|
||||
_ = try set.getOrAddGlyph(alloc, i, .regular);
|
||||
}
|
||||
|
||||
// Emoji should work
|
||||
_ = try set.getOrAddGlyph(alloc, '🥸', .regular);
|
||||
_ = try set.getOrAddGlyph(alloc, '🥸', .bold);
|
||||
}
|
||||
@@ -1,153 +0,0 @@
|
||||
//! Family represents a multiple styles of a single font: regular, bold,
|
||||
//! italic, etc. It is able to cache the glyphs into a single atlas.
|
||||
const Family = @This();
|
||||
|
||||
const std = @import("std");
|
||||
const Allocator = std.mem.Allocator;
|
||||
const Atlas = @import("../Atlas.zig");
|
||||
const Face = @import("main.zig").Face;
|
||||
const Glyph = @import("main.zig").Glyph;
|
||||
const Style = @import("main.zig").Style;
|
||||
const testFont = @import("test.zig").fontRegular;
|
||||
const codepoint = @import("main.zig").codepoint;
|
||||
const Library = @import("main.zig").Library;
|
||||
|
||||
const log = std.log.scoped(.font_family);
|
||||
|
||||
/// The texture atlas where all the font glyphs are rendered.
|
||||
/// This is NOT owned by the Family, deinitialization must
|
||||
/// be manually done.
|
||||
atlas: Atlas,
|
||||
|
||||
/// The library shared state.
|
||||
lib: Library,
|
||||
|
||||
/// The glyphs that are loaded into the atlas, keyed by codepoint.
|
||||
glyphs: std.AutoHashMapUnmanaged(GlyphKey, Glyph) = .{},
|
||||
|
||||
/// The font faces representing all the styles in this family.
|
||||
/// These should be set directly or via various loader functions.
|
||||
regular: ?Face = null,
|
||||
bold: ?Face = null,
|
||||
|
||||
/// This struct is used for the hash key for glyphs.
|
||||
const GlyphKey = struct {
|
||||
style: Style,
|
||||
codepoint: u32,
|
||||
};
|
||||
|
||||
pub fn init(lib: Library, atlas: Atlas) Family {
|
||||
return .{
|
||||
.lib = lib,
|
||||
.atlas = atlas,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn deinit(self: *Family, alloc: Allocator) void {
|
||||
self.glyphs.deinit(alloc);
|
||||
|
||||
if (self.regular) |*face| face.deinit();
|
||||
if (self.bold) |*face| face.deinit();
|
||||
|
||||
self.* = undefined;
|
||||
}
|
||||
|
||||
/// Loads a font to use from memory.
|
||||
///
|
||||
/// This can only be called if a font is not already loaded for the given style.
|
||||
pub fn loadFaceFromMemory(
|
||||
self: *Family,
|
||||
comptime style: Style,
|
||||
source: [:0]const u8,
|
||||
size: Face.DesiredSize,
|
||||
) !void {
|
||||
var face = try Face.init(self.lib, source, size);
|
||||
errdefer face.deinit();
|
||||
|
||||
@field(self, switch (style) {
|
||||
.regular => "regular",
|
||||
.bold => "bold",
|
||||
.italic => unreachable,
|
||||
.bold_italic => unreachable,
|
||||
}) = face;
|
||||
}
|
||||
|
||||
/// Get the glyph for the given codepoint and style. If the glyph hasn't
|
||||
/// been loaded yet this will return null.
|
||||
pub fn getGlyph(self: Family, cp: anytype, style: Style) ?*Glyph {
|
||||
const utf32 = codepoint(cp);
|
||||
const entry = self.glyphs.getEntry(.{
|
||||
.style = style,
|
||||
.codepoint = utf32,
|
||||
}) orelse return null;
|
||||
return entry.value_ptr;
|
||||
}
|
||||
|
||||
/// Add a glyph. If the glyph has already been loaded this will return
|
||||
/// the existing loaded glyph. If a glyph style can't be found, this will
|
||||
/// fall back to the "regular" style. If a glyph can't be found in the
|
||||
/// "regular" style, this will fall back to the unknown glyph character.
|
||||
///
|
||||
/// The codepoint can be either a u8 or []const u8 depending on if you know
|
||||
/// it is ASCII or must be UTF-8 decoded.
|
||||
pub fn addGlyph(self: *Family, alloc: Allocator, v: anytype, style: Style) !*Glyph {
|
||||
const face = face: {
|
||||
// Real is the face we SHOULD use for this style.
|
||||
var real = switch (style) {
|
||||
.regular => self.regular,
|
||||
.bold => self.bold,
|
||||
.italic => unreachable,
|
||||
.bold_italic => unreachable,
|
||||
};
|
||||
|
||||
// Fall back to regular if it is null
|
||||
if (real == null) real = self.regular;
|
||||
|
||||
// Return our face if we have it.
|
||||
if (real) |ptr| break :face ptr;
|
||||
|
||||
// If we reached this point, we have no font in the style we
|
||||
// want OR the fallback.
|
||||
return error.NoFontFallback;
|
||||
};
|
||||
|
||||
// We need a UTF32 codepoint
|
||||
const utf32 = codepoint(v);
|
||||
|
||||
// If we have this glyph loaded already then we're done.
|
||||
const glyphKey = .{
|
||||
.style = style,
|
||||
.codepoint = utf32,
|
||||
};
|
||||
const gop = try self.glyphs.getOrPut(alloc, glyphKey);
|
||||
if (gop.found_existing) return gop.value_ptr;
|
||||
errdefer _ = self.glyphs.remove(glyphKey);
|
||||
|
||||
// Get the glyph and add it to the atlas.
|
||||
gop.value_ptr.* = try face.loadGlyph(alloc, &self.atlas, utf32);
|
||||
return gop.value_ptr;
|
||||
}
|
||||
|
||||
test {
|
||||
const testing = std.testing;
|
||||
const alloc = testing.allocator;
|
||||
|
||||
var lib = try Library.init();
|
||||
defer lib.deinit();
|
||||
|
||||
var fam = init(lib, try Atlas.init(alloc, 512, .greyscale));
|
||||
defer fam.deinit(alloc);
|
||||
defer fam.atlas.deinit(alloc);
|
||||
try fam.loadFaceFromMemory(.regular, testFont, .{ .points = 12 });
|
||||
|
||||
// Generate all visible ASCII
|
||||
var i: u8 = 32;
|
||||
while (i < 127) : (i += 1) {
|
||||
_ = try fam.addGlyph(alloc, i, .regular);
|
||||
}
|
||||
|
||||
i = 32;
|
||||
while (i < 127) : (i += 1) {
|
||||
try testing.expect(fam.getGlyph(i, .regular) != null);
|
||||
}
|
||||
}
|
||||
@@ -40,6 +40,7 @@ const GlyphKey = struct {
|
||||
glyph: u32,
|
||||
};
|
||||
|
||||
/// The GroupCache takes ownership of Group and will free it.
|
||||
pub fn init(alloc: Allocator, group: Group) !GroupCache {
|
||||
var atlas_greyscale = try Atlas.init(alloc, 512, .greyscale);
|
||||
errdefer atlas_greyscale.deinit(alloc);
|
||||
@@ -65,6 +66,7 @@ pub fn deinit(self: *GroupCache, alloc: Allocator) void {
|
||||
self.glyphs.deinit(alloc);
|
||||
self.atlas_greyscale.deinit(alloc);
|
||||
self.atlas_color.deinit(alloc);
|
||||
self.group.deinit(alloc);
|
||||
}
|
||||
|
||||
/// Reset the cache. This should be called:
|
||||
@@ -146,13 +148,13 @@ test {
|
||||
var lib = try Library.init();
|
||||
defer lib.deinit();
|
||||
|
||||
var group = try Group.init(alloc);
|
||||
defer group.deinit(alloc);
|
||||
try group.addFace(alloc, .regular, try Face.init(lib, testFont, .{ .points = 12 }));
|
||||
|
||||
var cache = try init(alloc, group);
|
||||
var cache = try init(alloc, try Group.init(alloc));
|
||||
defer cache.deinit(alloc);
|
||||
|
||||
// Setup group
|
||||
try cache.group.addFace(alloc, .regular, try Face.init(lib, testFont, .{ .points = 12 }));
|
||||
const group = cache.group;
|
||||
|
||||
// Visible ASCII. Do it twice to verify cache.
|
||||
var i: u32 = 32;
|
||||
while (i < 127) : (i += 1) {
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
const std = @import("std");
|
||||
|
||||
pub const Face = @import("Face.zig");
|
||||
pub const Family = @import("Family.zig");
|
||||
pub const Group = @import("Group.zig");
|
||||
pub const GroupCache = @import("GroupCache.zig");
|
||||
pub const Glyph = @import("Glyph.zig");
|
||||
pub const FallbackSet = @import("FallbackSet.zig");
|
||||
pub const Library = @import("Library.zig");
|
||||
|
||||
/// The styles that a family can take.
|
||||
|
||||
Reference in New Issue
Block a user