mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-07-16 16:56:09 +03:00
font: [broken] working on extracting Collection from Group
This commit is contained in:
264
src/font/Collection.zig
Normal file
264
src/font/Collection.zig
Normal file
@ -0,0 +1,264 @@
|
||||
//! A font collection is a list of faces of different styles. The list is
|
||||
//! ordered by priority (per style). All fonts in a collection share the same
|
||||
//! size so they can be used interchangeably in cases a glyph is missing in one
|
||||
//! and present in another.
|
||||
const Collection = @This();
|
||||
|
||||
const std = @import("std");
|
||||
const Allocator = std.mem.Allocator;
|
||||
const font = @import("main.zig");
|
||||
const DeferredFace = font.DeferredFace;
|
||||
const Face = font.Face;
|
||||
const Library = font.Library;
|
||||
const Presentation = font.Presentation;
|
||||
const Style = font.Style;
|
||||
|
||||
/// The available faces we have. This shouldn't be modified manually.
|
||||
/// Instead, use the functions available on Collection.
|
||||
faces: StyleArray,
|
||||
|
||||
/// Initialize an empty collection.
|
||||
pub fn init(alloc: Allocator) !Collection {
|
||||
// Initialize our styles array, preallocating some space that is
|
||||
// likely to be used.
|
||||
var faces = StyleArray.initFill(.{});
|
||||
for (&faces.values) |*list| try list.ensureTotalCapacityPrecise(alloc, 2);
|
||||
return .{ .faces = faces };
|
||||
}
|
||||
|
||||
pub fn deinit(self: *Collection, alloc: Allocator) void {
|
||||
var it = self.faces.iterator();
|
||||
while (it.next()) |entry| {
|
||||
for (entry.value.items) |*item| item.deinit();
|
||||
entry.value.deinit(alloc);
|
||||
}
|
||||
}
|
||||
|
||||
pub const AddError = Allocator.Error || error{
|
||||
CollectionFull,
|
||||
};
|
||||
|
||||
/// Add a face to the collection for the given style. This face will be added
|
||||
/// next in priority if others exist already, i.e. it'll be the _last_ to be
|
||||
/// searched for a glyph in that list.
|
||||
///
|
||||
/// The collection takes ownership of the face. The face will be deallocated
|
||||
/// when the collection is deallocated.
|
||||
///
|
||||
/// If a loaded face is added to the collection, it should be the same
|
||||
/// size as all the other faces in the collection. This function will not
|
||||
/// verify or modify the size until the size of the entire collection is
|
||||
/// changed.
|
||||
pub fn add(
|
||||
self: *Collection,
|
||||
alloc: Allocator,
|
||||
style: Style,
|
||||
face: Entry,
|
||||
) AddError!Index {
|
||||
const list = self.faces.getPtr(style);
|
||||
|
||||
// We have some special indexes so we must never pass those.
|
||||
if (list.items.len >= Index.Special.start - 1)
|
||||
return error.CollectionFull;
|
||||
|
||||
const idx = list.items.len;
|
||||
try list.append(alloc, face);
|
||||
return .{ .style = style, .idx = @intCast(idx) };
|
||||
}
|
||||
|
||||
/// Packed array of all Style enum cases mapped to a growable list of faces.
|
||||
///
|
||||
/// We use this data structure because there aren't many styles and all
|
||||
/// styles are typically loaded for a terminal session. The overhead per
|
||||
/// style even if it is not used or barely used is minimal given the
|
||||
/// small style count.
|
||||
const StyleArray = std.EnumArray(Style, std.ArrayListUnmanaged(Entry));
|
||||
|
||||
/// A entry in a collection can be deferred or loaded. A deferred face
|
||||
/// is not yet fully loaded and only represents the font descriptor
|
||||
/// and usually uses less resources. A loaded face is fully parsed,
|
||||
/// ready to rasterize, and usually uses more resources than a
|
||||
/// deferred version.
|
||||
///
|
||||
/// A face can also be a "fallback" variant that is still either
|
||||
/// deferred or loaded. Today, there is only one difference between
|
||||
/// fallback and non-fallback (or "explicit") faces: the handling
|
||||
/// of emoji presentation.
|
||||
///
|
||||
/// For explicit faces, when an explicit emoji presentation is
|
||||
/// not requested, we will use any glyph for that codepoint found
|
||||
/// even if the font presentation does not match the UCD
|
||||
/// (Unicode Character Database) value. When an explicit presentation
|
||||
/// is requested (via either VS15/V16), that is always honored.
|
||||
/// The reason we do this is because we assume that if a user
|
||||
/// explicitly chosen a font face (hence it is "explicit" and
|
||||
/// not "fallback"), they want to use any glyphs possible within that
|
||||
/// font face. Fallback fonts on the other hand are picked as a
|
||||
/// last resort, so we should prefer exactness if possible.
|
||||
pub const Entry = union(enum) {
|
||||
deferred: DeferredFace, // Not loaded
|
||||
loaded: Face, // Loaded, explicit use
|
||||
|
||||
// The same as deferred/loaded but fallback font semantics (see large
|
||||
// comment above Entry).
|
||||
fallback_deferred: DeferredFace,
|
||||
fallback_loaded: Face,
|
||||
|
||||
pub fn deinit(self: *Entry) void {
|
||||
switch (self.*) {
|
||||
inline .deferred,
|
||||
.loaded,
|
||||
.fallback_deferred,
|
||||
.fallback_loaded,
|
||||
=> |*v| v.deinit(),
|
||||
}
|
||||
}
|
||||
|
||||
/// True if this face satisfies the given codepoint and presentation.
|
||||
fn hasCodepoint(self: Entry, cp: u32, p_mode: PresentationMode) bool {
|
||||
return switch (self) {
|
||||
// Non-fallback fonts require explicit presentation matching but
|
||||
// otherwise don't care about presentation
|
||||
.deferred => |v| switch (p_mode) {
|
||||
.explicit => |p| v.hasCodepoint(cp, p),
|
||||
.default, .any => v.hasCodepoint(cp, null),
|
||||
},
|
||||
|
||||
.loaded => |face| switch (p_mode) {
|
||||
.explicit => |p| face.presentation == p and face.glyphIndex(cp) != null,
|
||||
.default, .any => face.glyphIndex(cp) != null,
|
||||
},
|
||||
|
||||
// Fallback fonts require exact presentation matching.
|
||||
.fallback_deferred => |v| switch (p_mode) {
|
||||
.explicit, .default => |p| v.hasCodepoint(cp, p),
|
||||
.any => v.hasCodepoint(cp, null),
|
||||
},
|
||||
|
||||
.fallback_loaded => |face| switch (p_mode) {
|
||||
.explicit,
|
||||
.default,
|
||||
=> |p| face.presentation == p and face.glyphIndex(cp) != null,
|
||||
.any => face.glyphIndex(cp) != null,
|
||||
},
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
/// The requested presentation for a codepoint.
|
||||
pub const PresentationMode = union(enum) {
|
||||
/// The codepoint has an explicit presentation that is required,
|
||||
/// i.e. VS15/V16.
|
||||
explicit: Presentation,
|
||||
|
||||
/// The codepoint has no explicit presentation and we should use
|
||||
/// the presentation from the UCd.
|
||||
default: Presentation,
|
||||
|
||||
/// The codepoint can be any presentation.
|
||||
any: void,
|
||||
};
|
||||
|
||||
/// This represents a specific font in the collection.
|
||||
///
|
||||
/// The backing size of this packed struct represents the total number
|
||||
/// of possible usable fonts in a collection. And the number of bits
|
||||
/// used for the index and not the style represents the total number
|
||||
/// of possible usable fonts for a given style.
|
||||
///
|
||||
/// The goal is to keep the size of this struct as small as practical. We
|
||||
/// accept the limitations that this imposes so long as they're reasonable.
|
||||
/// At the time of writing this comment, this is a 16-bit struct with 13
|
||||
/// bits used for the index, supporting up to 8192 fonts per style. This
|
||||
/// seems more than reasonable. There are synthetic scenarios where this
|
||||
/// could be a limitation but I can't think of any that are practical.
|
||||
///
|
||||
/// If you somehow need more fonts per style, you can increase the size of
|
||||
/// the Backing type and everything should just work fine.
|
||||
pub const Index = packed struct(Index.Backing) {
|
||||
const Backing = u16;
|
||||
const backing_bits = @typeInfo(Backing).Int.bits;
|
||||
|
||||
/// The number of bits we use for the index.
|
||||
const idx_bits = backing_bits - @typeInfo(@typeInfo(Style).Enum.tag_type).Int.bits;
|
||||
pub const IndexInt = @Type(.{ .Int = .{ .signedness = .unsigned, .bits = idx_bits } });
|
||||
|
||||
/// The special-case fonts that we support.
|
||||
pub const Special = enum(IndexInt) {
|
||||
// We start all special fonts at this index so they can be detected.
|
||||
pub const start = std.math.maxInt(IndexInt);
|
||||
|
||||
/// Sprite drawing, this is rendered JIT using 2D graphics APIs.
|
||||
sprite = start,
|
||||
};
|
||||
|
||||
style: Style = .regular,
|
||||
idx: IndexInt = 0,
|
||||
|
||||
/// Initialize a special font index.
|
||||
pub fn initSpecial(v: Special) Index {
|
||||
return .{ .style = .regular, .idx = @intFromEnum(v) };
|
||||
}
|
||||
|
||||
/// Convert to int
|
||||
pub fn int(self: Index) Backing {
|
||||
return @bitCast(self);
|
||||
}
|
||||
|
||||
/// Returns true if this is a "special" index which doesn't map to
|
||||
/// a real font face. We can still render it but there is no face for
|
||||
/// this font.
|
||||
pub fn special(self: Index) ?Special {
|
||||
if (self.idx < Special.start) return null;
|
||||
return @enumFromInt(self.idx);
|
||||
}
|
||||
|
||||
test {
|
||||
// We never want to take up more than a byte since font indexes are
|
||||
// everywhere so if we increase the size of this we'll dramatically
|
||||
// increase our memory usage.
|
||||
try std.testing.expectEqual(@sizeOf(Backing), @sizeOf(Index));
|
||||
|
||||
// Just so we're aware when this changes. The current maximum number
|
||||
// of fonts for a style is 13 bits or 8192 fonts.
|
||||
try std.testing.expectEqual(13, idx_bits);
|
||||
}
|
||||
};
|
||||
|
||||
test init {
|
||||
const testing = std.testing;
|
||||
const alloc = testing.allocator;
|
||||
|
||||
var c = try init(alloc);
|
||||
defer c.deinit(alloc);
|
||||
}
|
||||
|
||||
test "add full" {
|
||||
const testing = std.testing;
|
||||
const alloc = testing.allocator;
|
||||
const testFont = @import("test.zig").fontRegular;
|
||||
|
||||
var lib = try Library.init();
|
||||
defer lib.deinit();
|
||||
|
||||
var c = try init(alloc);
|
||||
defer c.deinit(alloc);
|
||||
|
||||
for (0..Index.Special.start - 1) |_| {
|
||||
_ = try c.add(alloc, .regular, .{ .loaded = try Face.init(
|
||||
lib,
|
||||
testFont,
|
||||
.{ .size = .{ .points = 12 } },
|
||||
) });
|
||||
}
|
||||
|
||||
try testing.expectError(error.CollectionFull, c.add(
|
||||
alloc,
|
||||
.regular,
|
||||
.{ .loaded = try Face.init(
|
||||
lib,
|
||||
testFont,
|
||||
.{ .size = .{ .points = 12 } },
|
||||
) },
|
||||
));
|
||||
}
|
@ -16,6 +16,7 @@ const Allocator = std.mem.Allocator;
|
||||
const ziglyph = @import("ziglyph");
|
||||
|
||||
const font = @import("main.zig");
|
||||
const Collection = @import("main.zig").Collection;
|
||||
const DeferredFace = @import("main.zig").DeferredFace;
|
||||
const Face = @import("main.zig").Face;
|
||||
const Library = @import("main.zig").Library;
|
||||
@ -27,13 +28,6 @@ const quirks = @import("../quirks.zig");
|
||||
|
||||
const log = std.log.scoped(.font_group);
|
||||
|
||||
/// Packed array to map our styles to a set of faces.
|
||||
// Note: this is not the most efficient way to store these, but there is
|
||||
// usually only one font group for the entire process so this isn't the
|
||||
// most important memory efficiency we can look for. This is totally opaque
|
||||
// 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);
|
||||
|
||||
@ -92,7 +86,7 @@ metric_modifiers: ?font.face.Metrics.ModifierSet = null,
|
||||
|
||||
/// The available faces we have. This shouldn't be modified manually.
|
||||
/// Instead, use the functions available on Group.
|
||||
faces: StyleArray,
|
||||
faces: Collection,
|
||||
|
||||
/// 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
|
||||
@ -119,105 +113,25 @@ descriptor_cache: DescriptorCache = .{},
|
||||
/// terminal rendering will look wrong.
|
||||
sprite: ?font.sprite.Face = null,
|
||||
|
||||
/// A face in a group can be deferred or loaded. A deferred face
|
||||
/// is not yet fully loaded and only represents the font descriptor
|
||||
/// and usually uses less resources. A loaded face is fully parsed,
|
||||
/// ready to rasterize, and usually uses more resources than a
|
||||
/// deferred version.
|
||||
///
|
||||
/// A face can also be a "fallback" variant that is still either
|
||||
/// deferred or loaded. Today, there is only one different between
|
||||
/// fallback and non-fallback (or "explicit") faces: the handling
|
||||
/// of emoji presentation.
|
||||
///
|
||||
/// For explicit faces, when an explicit emoji presentation is
|
||||
/// not requested, we will use any glyph for that codepoint found
|
||||
/// even if the font presentation does not match the UCD
|
||||
/// (Unicode Character Database) value. When an explicit presentation
|
||||
/// is requested (via either VS15/V16), that is always honored.
|
||||
/// The reason we do this is because we assume that if a user
|
||||
/// explicitly chosen a font face (hence it is "explicit" and
|
||||
/// not "fallback"), they want to use any glyphs possible within that
|
||||
/// font face. Fallback fonts on the other hand are picked as a
|
||||
/// last resort, so we should prefer exactness if possible.
|
||||
pub const GroupFace = union(enum) {
|
||||
deferred: DeferredFace, // Not loaded
|
||||
loaded: Face, // Loaded, explicit use
|
||||
|
||||
// The same as deferred/loaded but fallback font semantics (see large
|
||||
// comment above GroupFace).
|
||||
fallback_deferred: DeferredFace,
|
||||
fallback_loaded: Face,
|
||||
|
||||
pub fn deinit(self: *GroupFace) void {
|
||||
switch (self.*) {
|
||||
inline .deferred,
|
||||
.loaded,
|
||||
.fallback_deferred,
|
||||
.fallback_loaded,
|
||||
=> |*v| v.deinit(),
|
||||
}
|
||||
}
|
||||
|
||||
/// True if this face satisfies the given codepoint and presentation.
|
||||
fn hasCodepoint(self: GroupFace, cp: u32, p_mode: PresentationMode) bool {
|
||||
return switch (self) {
|
||||
// Non-fallback fonts require explicit presentation matching but
|
||||
// otherwise don't care about presentation
|
||||
.deferred => |v| switch (p_mode) {
|
||||
.explicit => |p| v.hasCodepoint(cp, p),
|
||||
.default, .any => v.hasCodepoint(cp, null),
|
||||
},
|
||||
|
||||
.loaded => |face| switch (p_mode) {
|
||||
.explicit => |p| face.presentation == p and face.glyphIndex(cp) != null,
|
||||
.default, .any => face.glyphIndex(cp) != null,
|
||||
},
|
||||
|
||||
// Fallback fonts require exact presentation matching.
|
||||
.fallback_deferred => |v| switch (p_mode) {
|
||||
.explicit, .default => |p| v.hasCodepoint(cp, p),
|
||||
.any => v.hasCodepoint(cp, null),
|
||||
},
|
||||
|
||||
.fallback_loaded => |face| switch (p_mode) {
|
||||
.explicit,
|
||||
.default,
|
||||
=> |p| face.presentation == p and face.glyphIndex(cp) != null,
|
||||
.any => face.glyphIndex(cp) != null,
|
||||
},
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
/// Initializes an empty group. This is not useful until faces are added
|
||||
/// and finalizeInit is called.
|
||||
pub fn init(
|
||||
alloc: Allocator,
|
||||
lib: Library,
|
||||
size: font.face.DesiredSize,
|
||||
collection: Collection,
|
||||
) !Group {
|
||||
var result = Group{ .alloc = alloc, .lib = lib, .size = size, .faces = undefined };
|
||||
|
||||
// Initialize all our styles to initially sized lists.
|
||||
var i: usize = 0;
|
||||
while (i < StyleArray.len) : (i += 1) {
|
||||
result.faces.values[i] = .{};
|
||||
try result.faces.values[i].ensureTotalCapacityPrecise(alloc, 2);
|
||||
}
|
||||
|
||||
return result;
|
||||
return .{
|
||||
.alloc = alloc,
|
||||
.lib = lib,
|
||||
.size = size,
|
||||
.faces = collection,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn deinit(self: *Group) void {
|
||||
{
|
||||
var it = self.faces.iterator();
|
||||
while (it.next()) |entry| {
|
||||
for (entry.value.items) |*item| item.deinit();
|
||||
entry.value.deinit(self.alloc);
|
||||
}
|
||||
}
|
||||
|
||||
self.faces.deinit(self.alloc);
|
||||
if (self.metric_modifiers) |*v| v.deinit(self.alloc);
|
||||
|
||||
self.descriptor_cache.deinit(self.alloc);
|
||||
}
|
||||
|
||||
@ -230,23 +144,6 @@ pub fn faceOptions(self: *const Group) font.face.Options {
|
||||
};
|
||||
}
|
||||
|
||||
/// Add a face to the list for the given style. This face will be added as
|
||||
/// next in priority if others exist already, i.e. it'll be the _last_ to be
|
||||
/// searched for a glyph in that list.
|
||||
///
|
||||
/// The group takes ownership of the face. The face will be deallocated when
|
||||
/// the group is deallocated.
|
||||
pub fn addFace(self: *Group, style: Style, face: GroupFace) !FontIndex {
|
||||
const list = self.faces.getPtr(style);
|
||||
|
||||
// We have some special indexes so we must never pass those.
|
||||
if (list.items.len >= FontIndex.Special.start - 1) return error.GroupFull;
|
||||
|
||||
const idx = list.items.len;
|
||||
try list.append(self.alloc, face);
|
||||
return .{ .style = style, .idx = @intCast(idx) };
|
||||
}
|
||||
|
||||
/// 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 {
|
||||
@ -472,7 +369,9 @@ pub fn indexForCodepoint(
|
||||
// Discovery is supposed to only return faces that have our
|
||||
// codepoint but we can't search presentation in discovery so
|
||||
// we have to check it here.
|
||||
const face: GroupFace = .{ .fallback_deferred = deferred_face };
|
||||
const face: Collection.Entry = .{
|
||||
.fallback_deferred = deferred_face,
|
||||
};
|
||||
if (!face.hasCodepoint(cp, p_mode)) {
|
||||
deferred_face.deinit();
|
||||
continue;
|
||||
@ -895,9 +794,6 @@ test "face count limit" {
|
||||
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();
|
||||
|
||||
|
@ -19,6 +19,7 @@ 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;
|
||||
@ -111,105 +112,29 @@ pub fn groupRef(
|
||||
.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);
|
||||
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);
|
||||
|
||||
// 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;
|
||||
};
|
||||
group.discover = disco;
|
||||
|
||||
// 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 group.addFace(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 group.addFace(
|
||||
.regular,
|
||||
.{ .fallback_loaded = try Face.init(
|
||||
self.font_lib,
|
||||
face_ttf,
|
||||
group.faceOptions(),
|
||||
) },
|
||||
);
|
||||
_ = try group.addFace(
|
||||
.bold,
|
||||
.{ .fallback_loaded = try Face.init(
|
||||
self.font_lib,
|
||||
face_bold_ttf,
|
||||
group.faceOptions(),
|
||||
) },
|
||||
);
|
||||
|
||||
// Auto-italicize if we have to.
|
||||
try group.italicize();
|
||||
|
||||
// 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 = group.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 group.addFace(.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 group.addFace(
|
||||
.regular,
|
||||
.{ .fallback_loaded = try Face.init(
|
||||
self.font_lib,
|
||||
face_emoji_ttf,
|
||||
group.faceOptions(),
|
||||
) },
|
||||
);
|
||||
_ = try group.addFace(
|
||||
.regular,
|
||||
.{ .fallback_loaded = try Face.init(
|
||||
self.font_lib,
|
||||
face_emoji_text_ttf,
|
||||
group.faceOptions(),
|
||||
) },
|
||||
);
|
||||
}
|
||||
|
||||
log.info("font loading complete, any non-logged faces are using the built-in font", .{});
|
||||
break :group group;
|
||||
});
|
||||
@ -218,6 +143,124 @@ pub fn groupRef(
|
||||
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 {
|
||||
|
@ -6,6 +6,7 @@ pub const Atlas = @import("Atlas.zig");
|
||||
pub const discovery = @import("discovery.zig");
|
||||
pub const face = @import("face.zig");
|
||||
pub const CodepointMap = @import("CodepointMap.zig");
|
||||
pub const Collection = @import("Collection.zig");
|
||||
pub const DeferredFace = @import("DeferredFace.zig");
|
||||
pub const Face = face.Face;
|
||||
pub const Group = @import("Group.zig");
|
||||
|
Reference in New Issue
Block a user