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 ziglyph = @import("ziglyph");
|
||||||
|
|
||||||
const font = @import("main.zig");
|
const font = @import("main.zig");
|
||||||
|
const Collection = @import("main.zig").Collection;
|
||||||
const DeferredFace = @import("main.zig").DeferredFace;
|
const DeferredFace = @import("main.zig").DeferredFace;
|
||||||
const Face = @import("main.zig").Face;
|
const Face = @import("main.zig").Face;
|
||||||
const Library = @import("main.zig").Library;
|
const Library = @import("main.zig").Library;
|
||||||
@ -27,13 +28,6 @@ const quirks = @import("../quirks.zig");
|
|||||||
|
|
||||||
const log = std.log.scoped(.font_group);
|
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.
|
/// Packed array of booleans to indicate if a style is enabled or not.
|
||||||
pub const StyleStatus = std.EnumArray(Style, bool);
|
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.
|
/// The available faces we have. This shouldn't be modified manually.
|
||||||
/// Instead, use the functions available on Group.
|
/// Instead, use the functions available on Group.
|
||||||
faces: StyleArray,
|
faces: Collection,
|
||||||
|
|
||||||
/// The set of statuses and whether they're enabled or not. This defaults
|
/// 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
|
/// 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.
|
/// terminal rendering will look wrong.
|
||||||
sprite: ?font.sprite.Face = null,
|
sprite: ?font.sprite.Face = null,
|
||||||
|
|
||||||
/// A face in a group can be deferred or loaded. A deferred face
|
/// Initializes an empty group. This is not useful until faces are added
|
||||||
/// is not yet fully loaded and only represents the font descriptor
|
/// and finalizeInit is called.
|
||||||
/// 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,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
pub fn init(
|
pub fn init(
|
||||||
alloc: Allocator,
|
alloc: Allocator,
|
||||||
lib: Library,
|
lib: Library,
|
||||||
size: font.face.DesiredSize,
|
size: font.face.DesiredSize,
|
||||||
|
collection: Collection,
|
||||||
) !Group {
|
) !Group {
|
||||||
var result = Group{ .alloc = alloc, .lib = lib, .size = size, .faces = undefined };
|
return .{
|
||||||
|
.alloc = alloc,
|
||||||
// Initialize all our styles to initially sized lists.
|
.lib = lib,
|
||||||
var i: usize = 0;
|
.size = size,
|
||||||
while (i < StyleArray.len) : (i += 1) {
|
.faces = collection,
|
||||||
result.faces.values[i] = .{};
|
};
|
||||||
try result.faces.values[i].ensureTotalCapacityPrecise(alloc, 2);
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn deinit(self: *Group) void {
|
pub fn deinit(self: *Group) void {
|
||||||
{
|
self.faces.deinit(self.alloc);
|
||||||
var it = self.faces.iterator();
|
|
||||||
while (it.next()) |entry| {
|
|
||||||
for (entry.value.items) |*item| item.deinit();
|
|
||||||
entry.value.deinit(self.alloc);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (self.metric_modifiers) |*v| v.deinit(self.alloc);
|
if (self.metric_modifiers) |*v| v.deinit(self.alloc);
|
||||||
|
|
||||||
self.descriptor_cache.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
|
/// This will automatically create an italicized font from the regular
|
||||||
/// font face if we don't have any italicized fonts.
|
/// font face if we don't have any italicized fonts.
|
||||||
pub fn italicize(self: *Group) !void {
|
pub fn italicize(self: *Group) !void {
|
||||||
@ -472,7 +369,9 @@ pub fn indexForCodepoint(
|
|||||||
// Discovery is supposed to only return faces that have our
|
// Discovery is supposed to only return faces that have our
|
||||||
// codepoint but we can't search presentation in discovery so
|
// codepoint but we can't search presentation in discovery so
|
||||||
// we have to check it here.
|
// 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)) {
|
if (!face.hasCodepoint(cp, p_mode)) {
|
||||||
deferred_face.deinit();
|
deferred_face.deinit();
|
||||||
continue;
|
continue;
|
||||||
@ -895,9 +794,6 @@ test "face count limit" {
|
|||||||
const alloc = testing.allocator;
|
const alloc = testing.allocator;
|
||||||
const testFont = @import("test.zig").fontRegular;
|
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();
|
var lib = try Library.init();
|
||||||
defer lib.deinit();
|
defer lib.deinit();
|
||||||
|
|
||||||
|
@ -19,6 +19,7 @@ const assert = std.debug.assert;
|
|||||||
const Allocator = std.mem.Allocator;
|
const Allocator = std.mem.Allocator;
|
||||||
const ArenaAllocator = std.heap.ArenaAllocator;
|
const ArenaAllocator = std.heap.ArenaAllocator;
|
||||||
const fontpkg = @import("main.zig");
|
const fontpkg = @import("main.zig");
|
||||||
|
const Collection = fontpkg.Collection;
|
||||||
const Discover = fontpkg.Discover;
|
const Discover = fontpkg.Discover;
|
||||||
const Style = fontpkg.Style;
|
const Style = fontpkg.Style;
|
||||||
const Library = fontpkg.Library;
|
const Library = fontpkg.Library;
|
||||||
@ -111,24 +112,61 @@ pub fn groupRef(
|
|||||||
.ref = 1,
|
.ref = 1,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Build our font face collection that we'll use to initialize
|
||||||
|
// the Group.
|
||||||
|
|
||||||
cache.* = try GroupCache.init(self.alloc, 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();
|
errdefer group.deinit();
|
||||||
group.metric_modifiers = key.metric_modifiers;
|
group.metric_modifiers = key.metric_modifiers;
|
||||||
group.codepoint_map = key.codepoint_map;
|
group.codepoint_map = key.codepoint_map;
|
||||||
|
group.discover = try self.discover();
|
||||||
|
|
||||||
// Set our styles
|
// Set our styles
|
||||||
group.styles.set(.bold, config.@"font-style-bold" != .false);
|
group.styles.set(.bold, config.@"font-style-bold" != .false);
|
||||||
group.styles.set(.italic, config.@"font-style-italic" != .false);
|
group.styles.set(.italic, config.@"font-style-italic" != .false);
|
||||||
group.styles.set(.bold_italic, config.@"font-style-bold-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
|
// Search for fonts
|
||||||
if (Discover != void) discover: {
|
if (Discover != void) discover: {
|
||||||
const disco = try self.discover() orelse {
|
const disco = try self.discover() orelse {
|
||||||
log.warn("font discovery not available, cannot search for fonts", .{});
|
log.warn(
|
||||||
|
"font discovery not available, cannot search for fonts",
|
||||||
|
.{},
|
||||||
|
);
|
||||||
break :discover;
|
break :discover;
|
||||||
};
|
};
|
||||||
group.discover = disco;
|
|
||||||
|
|
||||||
// A buffer we use to store the font names for logging.
|
// A buffer we use to store the font names for logging.
|
||||||
var name_buf: [256]u8 = undefined;
|
var name_buf: [256]u8 = undefined;
|
||||||
@ -143,7 +181,12 @@ pub fn groupRef(
|
|||||||
field.name,
|
field.name,
|
||||||
try face.name(&name_buf),
|
try face.name(&name_buf),
|
||||||
});
|
});
|
||||||
_ = try group.addFace(style, .{ .deferred = face });
|
|
||||||
|
_ = try c.add(
|
||||||
|
self.alloc,
|
||||||
|
style,
|
||||||
|
.{ .deferred = face },
|
||||||
|
);
|
||||||
} else log.warn("font-family {s} not found: {s}", .{
|
} else log.warn("font-family {s} not found: {s}", .{
|
||||||
field.name,
|
field.name,
|
||||||
desc.family.?,
|
desc.family.?,
|
||||||
@ -153,69 +196,69 @@ pub fn groupRef(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Our built-in font will be used as a backup
|
// Our built-in font will be used as a backup
|
||||||
_ = try group.addFace(
|
_ = try c.add(
|
||||||
|
self.alloc,
|
||||||
.regular,
|
.regular,
|
||||||
.{ .fallback_loaded = try Face.init(
|
.{ .fallback_loaded = try Face.init(
|
||||||
self.font_lib,
|
self.font_lib,
|
||||||
face_ttf,
|
face_ttf,
|
||||||
group.faceOptions(),
|
opts,
|
||||||
) },
|
) },
|
||||||
);
|
);
|
||||||
_ = try group.addFace(
|
_ = try c.add(
|
||||||
|
self.alloc,
|
||||||
.bold,
|
.bold,
|
||||||
.{ .fallback_loaded = try Face.init(
|
.{ .fallback_loaded = try Face.init(
|
||||||
self.font_lib,
|
self.font_lib,
|
||||||
face_bold_ttf,
|
face_bold_ttf,
|
||||||
group.faceOptions(),
|
opts,
|
||||||
) },
|
) },
|
||||||
);
|
);
|
||||||
|
|
||||||
// Auto-italicize if we have to.
|
|
||||||
try group.italicize();
|
|
||||||
|
|
||||||
// On macOS, always search for and add the Apple Emoji font
|
// On macOS, always search for and add the Apple Emoji font
|
||||||
// as our preferred emoji font for fallback. We do this in case
|
// as our preferred emoji font for fallback. We do this in case
|
||||||
// people add other emoji fonts to their system, we always want to
|
// people add other emoji fonts to their system, we always want to
|
||||||
// prefer the official one. Users can override this by explicitly
|
// prefer the official one. Users can override this by explicitly
|
||||||
// specifying a font-family for emoji.
|
// specifying a font-family for emoji.
|
||||||
if (comptime builtin.target.isDarwin()) apple_emoji: {
|
if (comptime builtin.target.isDarwin()) apple_emoji: {
|
||||||
const disco = group.discover orelse break :apple_emoji;
|
const disco = try self.discover() orelse break :apple_emoji;
|
||||||
var disco_it = try disco.discover(self.alloc, .{
|
var disco_it = try disco.discover(self.alloc, .{
|
||||||
.family = "Apple Color Emoji",
|
.family = "Apple Color Emoji",
|
||||||
});
|
});
|
||||||
defer disco_it.deinit();
|
defer disco_it.deinit();
|
||||||
if (try disco_it.next()) |face| {
|
if (try disco_it.next()) |face| {
|
||||||
_ = try group.addFace(.regular, .{ .fallback_deferred = face });
|
_ = try c.add(
|
||||||
|
self.alloc,
|
||||||
|
.regular,
|
||||||
|
.{ .fallback_deferred = face },
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Emoji fallback. We don't include this on Mac since Mac is expected
|
// Emoji fallback. We don't include this on Mac since Mac is expected
|
||||||
// to always have the Apple Emoji available on the system.
|
// to always have the Apple Emoji available on the system.
|
||||||
if (comptime !builtin.target.isDarwin() or Discover == void) {
|
if (comptime !builtin.target.isDarwin() or Discover == void) {
|
||||||
_ = try group.addFace(
|
_ = try c.add(
|
||||||
|
self.alloc,
|
||||||
.regular,
|
.regular,
|
||||||
.{ .fallback_loaded = try Face.init(
|
.{ .fallback_loaded = try Face.init(
|
||||||
self.font_lib,
|
self.font_lib,
|
||||||
face_emoji_ttf,
|
face_emoji_ttf,
|
||||||
group.faceOptions(),
|
opts,
|
||||||
) },
|
) },
|
||||||
);
|
);
|
||||||
_ = try group.addFace(
|
_ = try c.add(
|
||||||
|
self.alloc,
|
||||||
.regular,
|
.regular,
|
||||||
.{ .fallback_loaded = try Face.init(
|
.{ .fallback_loaded = try Face.init(
|
||||||
self.font_lib,
|
self.font_lib,
|
||||||
face_emoji_text_ttf,
|
face_emoji_text_ttf,
|
||||||
group.faceOptions(),
|
opts,
|
||||||
) },
|
) },
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
log.info("font loading complete, any non-logged faces are using the built-in font", .{});
|
return c;
|
||||||
break :group group;
|
|
||||||
});
|
|
||||||
errdefer cache.deinit(self.alloc);
|
|
||||||
|
|
||||||
return .{ gop.key_ptr.*, gop.value_ptr.cache };
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Decrement the ref count for the given key. If the ref count is zero,
|
/// Decrement the ref count for the given key. If the ref count is zero,
|
||||||
|
@ -6,6 +6,7 @@ pub const Atlas = @import("Atlas.zig");
|
|||||||
pub const discovery = @import("discovery.zig");
|
pub const discovery = @import("discovery.zig");
|
||||||
pub const face = @import("face.zig");
|
pub const face = @import("face.zig");
|
||||||
pub const CodepointMap = @import("CodepointMap.zig");
|
pub const CodepointMap = @import("CodepointMap.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 Group = @import("Group.zig");
|
||||||
|
Reference in New Issue
Block a user