mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-07-14 15:56:13 +03:00
Merge pull request #2143 from ghostty-org/font-tweaks
Font Improvements
This commit is contained in:
@ -52,10 +52,17 @@ const c = @cImport({
|
|||||||
///
|
///
|
||||||
/// The specific styles (bold, italic, bold italic) do not need to be
|
/// The specific styles (bold, italic, bold italic) do not need to be
|
||||||
/// explicitly set. If a style is not set, then the regular style (font-family)
|
/// explicitly set. If a style is not set, then the regular style (font-family)
|
||||||
/// will be searched for stylistic variants. If an italic style is not found,
|
/// will be searched for stylistic variants. If a stylistic variant is not
|
||||||
/// Ghostty will auto-italicize the regular style by applying a slant. If
|
/// found, Ghostty will use the regular style. This prevents falling back to a
|
||||||
/// a bold style is not found, Ghostty will look for another monospace
|
/// different font family just to get a style such as bold. This also applies
|
||||||
/// font.
|
/// if you explicitly specify a font family for a style. For example, if you
|
||||||
|
/// set `font-family-bold = FooBar` and "FooBar" cannot be found, Ghostty will
|
||||||
|
/// use whatever font is set for `font-family` for the bold style.
|
||||||
|
///
|
||||||
|
/// Finally, some styles may be synthesized if they are not supported.
|
||||||
|
/// For example, if a font does not have an italic style and no alternative
|
||||||
|
/// italic font is specified, Ghostty will synthesize an italic style by
|
||||||
|
/// applying a slant to the regular style.
|
||||||
///
|
///
|
||||||
/// You can disable styles completely by using the `font-style` set of
|
/// You can disable styles completely by using the `font-style` set of
|
||||||
/// configurations. See the documentation for `font-style` for more information.
|
/// configurations. See the documentation for `font-style` for more information.
|
||||||
|
@ -383,7 +383,7 @@ test getIndex {
|
|||||||
var lib = try Library.init();
|
var lib = try Library.init();
|
||||||
defer lib.deinit();
|
defer lib.deinit();
|
||||||
|
|
||||||
var c = try Collection.init(alloc);
|
var c = Collection.init();
|
||||||
c.load_options = .{ .library = lib };
|
c.load_options = .{ .library = lib };
|
||||||
|
|
||||||
{
|
{
|
||||||
@ -464,7 +464,7 @@ test "getIndex disabled font style" {
|
|||||||
var lib = try Library.init();
|
var lib = try Library.init();
|
||||||
defer lib.deinit();
|
defer lib.deinit();
|
||||||
|
|
||||||
var c = try Collection.init(alloc);
|
var c = Collection.init();
|
||||||
c.load_options = .{ .library = lib };
|
c.load_options = .{ .library = lib };
|
||||||
|
|
||||||
_ = try c.add(alloc, .regular, .{ .loaded = try Face.init(
|
_ = try c.add(alloc, .regular, .{ .loaded = try Face.init(
|
||||||
@ -516,7 +516,7 @@ test "getIndex box glyph" {
|
|||||||
var lib = try Library.init();
|
var lib = try Library.init();
|
||||||
defer lib.deinit();
|
defer lib.deinit();
|
||||||
|
|
||||||
const c = try Collection.init(alloc);
|
const c = Collection.init();
|
||||||
|
|
||||||
var r: CodepointResolver = .{
|
var r: CodepointResolver = .{
|
||||||
.collection = c,
|
.collection = c,
|
||||||
|
@ -16,6 +16,7 @@
|
|||||||
const Collection = @This();
|
const Collection = @This();
|
||||||
|
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
|
const assert = std.debug.assert;
|
||||||
const Allocator = std.mem.Allocator;
|
const Allocator = std.mem.Allocator;
|
||||||
const font = @import("main.zig");
|
const font = @import("main.zig");
|
||||||
const options = font.options;
|
const options = font.options;
|
||||||
@ -39,21 +40,18 @@ faces: StyleArray,
|
|||||||
load_options: ?LoadOptions = null,
|
load_options: ?LoadOptions = null,
|
||||||
|
|
||||||
/// Initialize an empty collection.
|
/// Initialize an empty collection.
|
||||||
pub fn init(
|
pub fn init() Collection {
|
||||||
alloc: Allocator,
|
|
||||||
) !Collection {
|
|
||||||
// Initialize our styles array, preallocating some space that is
|
// Initialize our styles array, preallocating some space that is
|
||||||
// likely to be used.
|
// likely to be used.
|
||||||
var faces = StyleArray.initFill(.{});
|
return .{ .faces = StyleArray.initFill(.{}) };
|
||||||
for (&faces.values) |*v| try v.ensureTotalCapacityPrecise(alloc, 2);
|
|
||||||
return .{ .faces = faces };
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn deinit(self: *Collection, alloc: Allocator) void {
|
pub fn deinit(self: *Collection, alloc: Allocator) void {
|
||||||
var it = self.faces.iterator();
|
var it = self.faces.iterator();
|
||||||
while (it.next()) |entry| {
|
while (it.next()) |array| {
|
||||||
for (entry.value.items) |*item| item.deinit();
|
var entry_it = array.value.iterator(0);
|
||||||
entry.value.deinit(alloc);
|
while (entry_it.next()) |entry| entry.deinit();
|
||||||
|
array.value.deinit(alloc);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (self.load_options) |*v| v.deinit(alloc);
|
if (self.load_options) |*v| v.deinit(alloc);
|
||||||
@ -84,14 +82,14 @@ pub fn add(
|
|||||||
const list = self.faces.getPtr(style);
|
const list = self.faces.getPtr(style);
|
||||||
|
|
||||||
// We have some special indexes so we must never pass those.
|
// We have some special indexes so we must never pass those.
|
||||||
if (list.items.len >= Index.Special.start - 1)
|
const idx = list.count();
|
||||||
|
if (idx >= Index.Special.start - 1)
|
||||||
return error.CollectionFull;
|
return error.CollectionFull;
|
||||||
|
|
||||||
// If this is deferred and we don't have load options, we can't.
|
// If this is deferred and we don't have load options, we can't.
|
||||||
if (face.isDeferred() and self.load_options == null)
|
if (face.isDeferred() and self.load_options == null)
|
||||||
return error.DeferredLoadingUnavailable;
|
return error.DeferredLoadingUnavailable;
|
||||||
|
|
||||||
const idx = list.items.len;
|
|
||||||
try list.append(alloc, face);
|
try list.append(alloc, face);
|
||||||
return .{ .style = style, .idx = @intCast(idx) };
|
return .{ .style = style, .idx = @intCast(idx) };
|
||||||
}
|
}
|
||||||
@ -104,27 +102,54 @@ pub fn add(
|
|||||||
pub fn getFace(self: *Collection, index: Index) !*Face {
|
pub fn getFace(self: *Collection, index: Index) !*Face {
|
||||||
if (index.special() != null) return error.SpecialHasNoFace;
|
if (index.special() != null) return error.SpecialHasNoFace;
|
||||||
const list = self.faces.getPtr(index.style);
|
const list = self.faces.getPtr(index.style);
|
||||||
const item = &list.items[index.idx];
|
const item: *Entry = item: {
|
||||||
return switch (item.*) {
|
var item = list.at(index.idx);
|
||||||
|
switch (item.*) {
|
||||||
|
.alias => |ptr| item = ptr,
|
||||||
|
|
||||||
|
.deferred,
|
||||||
|
.fallback_deferred,
|
||||||
|
.loaded,
|
||||||
|
.fallback_loaded,
|
||||||
|
=> {},
|
||||||
|
}
|
||||||
|
assert(item.* != .alias);
|
||||||
|
break :item item;
|
||||||
|
};
|
||||||
|
|
||||||
|
return self.getFaceFromEntry(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the face from an entry.
|
||||||
|
///
|
||||||
|
/// This entry must not be an alias.
|
||||||
|
fn getFaceFromEntry(self: *Collection, entry: *Entry) !*Face {
|
||||||
|
assert(entry.* != .alias);
|
||||||
|
|
||||||
|
return switch (entry.*) {
|
||||||
inline .deferred, .fallback_deferred => |*d, tag| deferred: {
|
inline .deferred, .fallback_deferred => |*d, tag| deferred: {
|
||||||
const opts = self.load_options orelse
|
const opts = self.load_options orelse
|
||||||
return error.DeferredLoadingUnavailable;
|
return error.DeferredLoadingUnavailable;
|
||||||
const face = try d.load(opts.library, opts.faceOptions());
|
const face = try d.load(opts.library, opts.faceOptions());
|
||||||
d.deinit();
|
d.deinit();
|
||||||
item.* = switch (tag) {
|
entry.* = switch (tag) {
|
||||||
.deferred => .{ .loaded = face },
|
.deferred => .{ .loaded = face },
|
||||||
.fallback_deferred => .{ .fallback_loaded = face },
|
.fallback_deferred => .{ .fallback_loaded = face },
|
||||||
else => unreachable,
|
else => unreachable,
|
||||||
};
|
};
|
||||||
|
|
||||||
break :deferred switch (tag) {
|
break :deferred switch (tag) {
|
||||||
.deferred => &item.loaded,
|
.deferred => &entry.loaded,
|
||||||
.fallback_deferred => &item.fallback_loaded,
|
.fallback_deferred => &entry.fallback_loaded,
|
||||||
else => unreachable,
|
else => unreachable,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
.loaded, .fallback_loaded => |*f| f,
|
.loaded, .fallback_loaded => |*f| f,
|
||||||
|
|
||||||
|
// When setting `entry` above, we ensure we don't end up with
|
||||||
|
// an alias.
|
||||||
|
.alias => unreachable,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -140,13 +165,17 @@ pub fn getIndex(
|
|||||||
style: Style,
|
style: Style,
|
||||||
p_mode: PresentationMode,
|
p_mode: PresentationMode,
|
||||||
) ?Index {
|
) ?Index {
|
||||||
for (self.faces.get(style).items, 0..) |elem, i| {
|
var i: usize = 0;
|
||||||
if (elem.hasCodepoint(cp, p_mode)) {
|
var it = self.faces.get(style).constIterator(0);
|
||||||
|
while (it.next()) |entry| {
|
||||||
|
if (entry.hasCodepoint(cp, p_mode)) {
|
||||||
return .{
|
return .{
|
||||||
.style = style,
|
.style = style,
|
||||||
.idx = @intCast(i),
|
.idx = @intCast(i),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
i += 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Not found
|
// Not found
|
||||||
@ -164,38 +193,52 @@ pub fn hasCodepoint(
|
|||||||
p_mode: PresentationMode,
|
p_mode: PresentationMode,
|
||||||
) bool {
|
) bool {
|
||||||
const list = self.faces.get(index.style);
|
const list = self.faces.get(index.style);
|
||||||
if (index.idx >= list.items.len) return false;
|
if (index.idx >= list.count()) return false;
|
||||||
return list.items[index.idx].hasCodepoint(cp, p_mode);
|
return list.at(index.idx).hasCodepoint(cp, p_mode);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Automatically create an italicized font from the regular
|
pub const CompleteError = Allocator.Error || error{
|
||||||
/// font face if we don't have one already. If we already have
|
DefaultUnavailable,
|
||||||
/// an italicized font face, this does nothing.
|
};
|
||||||
pub fn autoItalicize(self: *Collection, alloc: Allocator) !void {
|
|
||||||
// If we have an italic font, do nothing.
|
/// Ensure we have an option for all styles in the collection, such
|
||||||
const italic_list = self.faces.getPtr(.italic);
|
/// as italic and bold.
|
||||||
if (italic_list.items.len > 0) return;
|
///
|
||||||
|
/// This requires that a regular font face is already loaded.
|
||||||
|
/// This is asserted. If a font style is missing, we will synthesize
|
||||||
|
/// it if possible. Otherwise, we will use the regular font style.
|
||||||
|
pub fn completeStyles(self: *Collection, alloc: Allocator) CompleteError!void {
|
||||||
|
// If every style has at least one entry then we're done!
|
||||||
|
// This is the most common case.
|
||||||
|
empty: {
|
||||||
|
var it = self.faces.iterator();
|
||||||
|
while (it.next()) |entry| {
|
||||||
|
if (entry.value.count() == 0) break :empty;
|
||||||
|
}
|
||||||
|
|
||||||
// Not all font backends support auto-italicization.
|
|
||||||
if (comptime !@hasDecl(Face, "italicize")) {
|
|
||||||
log.warn(
|
|
||||||
"no italic font face available, italics will not render",
|
|
||||||
.{},
|
|
||||||
);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Our regular font. If we have no regular font we also do nothing.
|
// Find the first regular face that has non-colorized text glyphs.
|
||||||
const regular = regular: {
|
// This is the font we want to fallback to. This may not be index zero
|
||||||
const list = self.faces.get(.regular);
|
// if a user configures something like an Emoji font first.
|
||||||
if (list.items.len == 0) return;
|
const regular_entry: *Entry = entry: {
|
||||||
|
const list = self.faces.getPtr(.regular);
|
||||||
|
assert(list.count() > 0);
|
||||||
|
|
||||||
// Find our first regular face that has text glyphs.
|
// Find our first regular face that has text glyphs.
|
||||||
for (0..list.items.len) |i| {
|
var it = list.iterator(0);
|
||||||
const face = try self.getFace(.{
|
while (it.next()) |entry| {
|
||||||
.style = .regular,
|
// Load our face. If we fail to load it, we just skip it and
|
||||||
.idx = @intCast(i),
|
// continue on to try the next one.
|
||||||
});
|
const face = self.getFaceFromEntry(entry) catch |err| {
|
||||||
|
log.warn("error loading regular entry={d} err={}", .{
|
||||||
|
it.index - 1,
|
||||||
|
err,
|
||||||
|
});
|
||||||
|
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
|
||||||
// We have two conditionals here. The color check is obvious:
|
// We have two conditionals here. The color check is obvious:
|
||||||
// we want to auto-italicize a normal text font. The second
|
// we want to auto-italicize a normal text font. The second
|
||||||
@ -205,25 +248,80 @@ pub fn autoItalicize(self: *Collection, alloc: Allocator) !void {
|
|||||||
// it's a reasonable heuristic and the first case will match 99%
|
// it's a reasonable heuristic and the first case will match 99%
|
||||||
// of the time.
|
// of the time.
|
||||||
if (!face.hasColor() or face.glyphIndex('A') != null) {
|
if (!face.hasColor() or face.glyphIndex('A') != null) {
|
||||||
break :regular face;
|
break :entry entry;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// No regular text face found.
|
// No regular text face found. We can't provide any fallback.
|
||||||
return;
|
return error.DefaultUnavailable;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// If we don't have italic, attempt to create a synthetic italic face.
|
||||||
|
// If we can't create a synthetic italic face, we'll just use the regular
|
||||||
|
// face for italic.
|
||||||
|
const italic_list = self.faces.getPtr(.italic);
|
||||||
|
if (italic_list.count() == 0) italic: {
|
||||||
|
const synthetic = self.syntheticItalic(regular_entry) catch |err| {
|
||||||
|
log.warn("failed to create synthetic italic, italic style will not be available err={}", .{err});
|
||||||
|
try italic_list.append(alloc, .{ .alias = regular_entry });
|
||||||
|
break :italic;
|
||||||
|
};
|
||||||
|
|
||||||
|
log.info("synthetic italic face created", .{});
|
||||||
|
try italic_list.append(alloc, .{ .loaded = synthetic });
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we don't have bold, use the regular font.
|
||||||
|
const bold_list = self.faces.getPtr(.bold);
|
||||||
|
if (bold_list.count() == 0) {
|
||||||
|
log.warn("bold style not available, using regular font", .{});
|
||||||
|
try bold_list.append(alloc, .{ .alias = regular_entry });
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we don't have bold italic, use the regular italic font.
|
||||||
|
const bold_italic_list = self.faces.getPtr(.bold_italic);
|
||||||
|
if (bold_italic_list.count() == 0) {
|
||||||
|
log.warn("bold italic style not available, using italic font", .{});
|
||||||
|
|
||||||
|
// Nested alias isn't allowed so if the italic entry is an
|
||||||
|
// alias then we use the aliased entry.
|
||||||
|
const italic_entry = italic_list.at(0);
|
||||||
|
switch (italic_entry.*) {
|
||||||
|
.alias => |v| try bold_italic_list.append(
|
||||||
|
alloc,
|
||||||
|
.{ .alias = v },
|
||||||
|
),
|
||||||
|
|
||||||
|
.loaded,
|
||||||
|
.fallback_loaded,
|
||||||
|
.deferred,
|
||||||
|
.fallback_deferred,
|
||||||
|
=> try bold_italic_list.append(
|
||||||
|
alloc,
|
||||||
|
.{ .alias = italic_entry },
|
||||||
|
),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create an synthetic italic font face from the given entry and return it.
|
||||||
|
fn syntheticItalic(self: *Collection, entry: *Entry) !Face {
|
||||||
|
// Not all font backends support auto-italicization.
|
||||||
|
if (comptime !@hasDecl(Face, "italicize")) return error.SyntheticItalicUnavailable;
|
||||||
|
|
||||||
// We require loading options to auto-italicize.
|
// We require loading options to auto-italicize.
|
||||||
const opts = self.load_options orelse return error.DeferredLoadingUnavailable;
|
const opts = self.load_options orelse return error.DeferredLoadingUnavailable;
|
||||||
|
|
||||||
// Try to italicize it.
|
// Try to italicize it.
|
||||||
|
const regular = try self.getFaceFromEntry(entry);
|
||||||
const face = try regular.italicize(opts.faceOptions());
|
const face = try regular.italicize(opts.faceOptions());
|
||||||
try italic_list.append(alloc, .{ .loaded = face });
|
|
||||||
|
|
||||||
var buf: [256]u8 = undefined;
|
var buf: [256]u8 = undefined;
|
||||||
if (face.name(&buf)) |name| {
|
if (face.name(&buf)) |name| {
|
||||||
log.info("font auto-italicized: {s}", .{name});
|
log.info("font auto-italicized: {s}", .{name});
|
||||||
} else |_| {}
|
} else |_| {}
|
||||||
|
|
||||||
|
return face;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Update the size of all faces in the collection. This will
|
/// Update the size of all faces in the collection. This will
|
||||||
@ -241,12 +339,19 @@ pub fn setSize(self: *Collection, size: DesiredSize) !void {
|
|||||||
|
|
||||||
// Resize all our faces that are loaded
|
// Resize all our faces that are loaded
|
||||||
var it = self.faces.iterator();
|
var it = self.faces.iterator();
|
||||||
while (it.next()) |entry| {
|
while (it.next()) |array| {
|
||||||
for (entry.value.items) |*elem| switch (elem.*) {
|
var entry_it = array.value.iterator(0);
|
||||||
.deferred, .fallback_deferred => continue,
|
while (entry_it.next()) |entry| switch (entry.*) {
|
||||||
.loaded, .fallback_loaded => |*f| try f.setSize(
|
.loaded, .fallback_loaded => |*f| try f.setSize(
|
||||||
opts.faceOptions(),
|
opts.faceOptions(),
|
||||||
),
|
),
|
||||||
|
|
||||||
|
// Deferred aren't loaded so we don't need to set their size.
|
||||||
|
// The size for when they're loaded is set since `opts` changed.
|
||||||
|
.deferred, .fallback_deferred => continue,
|
||||||
|
|
||||||
|
// Alias faces don't own their size.
|
||||||
|
.alias => continue,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -257,7 +362,13 @@ pub fn setSize(self: *Collection, size: DesiredSize) !void {
|
|||||||
/// styles are typically loaded for a terminal session. The overhead per
|
/// 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
|
/// style even if it is not used or barely used is minimal given the
|
||||||
/// small style count.
|
/// small style count.
|
||||||
const StyleArray = std.EnumArray(Style, std.ArrayListUnmanaged(Entry));
|
///
|
||||||
|
/// We use a segmented list because the entry values must be pointer-stable
|
||||||
|
/// to support the "alias" field in Entry.
|
||||||
|
///
|
||||||
|
/// WARNING: We cannot use any prealloc yet for the segmented list because
|
||||||
|
/// the collection is copied around by value and pointers aren't stable.
|
||||||
|
const StyleArray = std.EnumArray(Style, std.SegmentedList(Entry, 0));
|
||||||
|
|
||||||
/// Load options are used to configure all the details a Collection
|
/// Load options are used to configure all the details a Collection
|
||||||
/// needs to load deferred faces.
|
/// needs to load deferred faces.
|
||||||
@ -318,6 +429,10 @@ pub const Entry = union(enum) {
|
|||||||
fallback_deferred: DeferredFace,
|
fallback_deferred: DeferredFace,
|
||||||
fallback_loaded: Face,
|
fallback_loaded: Face,
|
||||||
|
|
||||||
|
// An alias to another entry. This is used to share the same face,
|
||||||
|
// avoid memory duplication. An alias must point to a non-alias entry.
|
||||||
|
alias: *Entry,
|
||||||
|
|
||||||
pub fn deinit(self: *Entry) void {
|
pub fn deinit(self: *Entry) void {
|
||||||
switch (self.*) {
|
switch (self.*) {
|
||||||
inline .deferred,
|
inline .deferred,
|
||||||
@ -325,6 +440,10 @@ pub const Entry = union(enum) {
|
|||||||
.fallback_deferred,
|
.fallback_deferred,
|
||||||
.fallback_loaded,
|
.fallback_loaded,
|
||||||
=> |*v| v.deinit(),
|
=> |*v| v.deinit(),
|
||||||
|
|
||||||
|
// Aliased fonts are not owned by this entry so we let them
|
||||||
|
// be deallocated by the owner.
|
||||||
|
.alias => {},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -333,6 +452,7 @@ pub const Entry = union(enum) {
|
|||||||
return switch (self) {
|
return switch (self) {
|
||||||
.deferred, .fallback_deferred => true,
|
.deferred, .fallback_deferred => true,
|
||||||
.loaded, .fallback_loaded => false,
|
.loaded, .fallback_loaded => false,
|
||||||
|
.alias => |v| v.isDeferred(),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -343,6 +463,8 @@ pub const Entry = union(enum) {
|
|||||||
p_mode: PresentationMode,
|
p_mode: PresentationMode,
|
||||||
) bool {
|
) bool {
|
||||||
return switch (self) {
|
return switch (self) {
|
||||||
|
.alias => |v| v.hasCodepoint(cp, p_mode),
|
||||||
|
|
||||||
// Non-fallback fonts require explicit presentation matching but
|
// Non-fallback fonts require explicit presentation matching but
|
||||||
// otherwise don't care about presentation
|
// otherwise don't care about presentation
|
||||||
.deferred => |v| switch (p_mode) {
|
.deferred => |v| switch (p_mode) {
|
||||||
@ -467,7 +589,7 @@ test init {
|
|||||||
const testing = std.testing;
|
const testing = std.testing;
|
||||||
const alloc = testing.allocator;
|
const alloc = testing.allocator;
|
||||||
|
|
||||||
var c = try init(alloc);
|
var c = init();
|
||||||
defer c.deinit(alloc);
|
defer c.deinit(alloc);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -479,7 +601,7 @@ test "add full" {
|
|||||||
var lib = try Library.init();
|
var lib = try Library.init();
|
||||||
defer lib.deinit();
|
defer lib.deinit();
|
||||||
|
|
||||||
var c = try init(alloc);
|
var c = init();
|
||||||
defer c.deinit(alloc);
|
defer c.deinit(alloc);
|
||||||
|
|
||||||
for (0..Index.Special.start - 1) |_| {
|
for (0..Index.Special.start - 1) |_| {
|
||||||
@ -505,7 +627,7 @@ test "add deferred without loading options" {
|
|||||||
const testing = std.testing;
|
const testing = std.testing;
|
||||||
const alloc = testing.allocator;
|
const alloc = testing.allocator;
|
||||||
|
|
||||||
var c = try init(alloc);
|
var c = init();
|
||||||
defer c.deinit(alloc);
|
defer c.deinit(alloc);
|
||||||
|
|
||||||
try testing.expectError(error.DeferredLoadingUnavailable, c.add(
|
try testing.expectError(error.DeferredLoadingUnavailable, c.add(
|
||||||
@ -525,7 +647,7 @@ test getFace {
|
|||||||
var lib = try Library.init();
|
var lib = try Library.init();
|
||||||
defer lib.deinit();
|
defer lib.deinit();
|
||||||
|
|
||||||
var c = try init(alloc);
|
var c = init();
|
||||||
defer c.deinit(alloc);
|
defer c.deinit(alloc);
|
||||||
|
|
||||||
const idx = try c.add(alloc, .regular, .{ .loaded = try Face.init(
|
const idx = try c.add(alloc, .regular, .{ .loaded = try Face.init(
|
||||||
@ -549,7 +671,7 @@ test getIndex {
|
|||||||
var lib = try Library.init();
|
var lib = try Library.init();
|
||||||
defer lib.deinit();
|
defer lib.deinit();
|
||||||
|
|
||||||
var c = try init(alloc);
|
var c = init();
|
||||||
defer c.deinit(alloc);
|
defer c.deinit(alloc);
|
||||||
|
|
||||||
_ = try c.add(alloc, .regular, .{ .loaded = try Face.init(
|
_ = try c.add(alloc, .regular, .{ .loaded = try Face.init(
|
||||||
@ -572,9 +694,7 @@ test getIndex {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
test autoItalicize {
|
test completeStyles {
|
||||||
if (comptime !@hasDecl(Face, "italicize")) return error.SkipZigTest;
|
|
||||||
|
|
||||||
const testing = std.testing;
|
const testing = std.testing;
|
||||||
const alloc = testing.allocator;
|
const alloc = testing.allocator;
|
||||||
const testFont = @import("test.zig").fontRegular;
|
const testFont = @import("test.zig").fontRegular;
|
||||||
@ -582,7 +702,7 @@ test autoItalicize {
|
|||||||
var lib = try Library.init();
|
var lib = try Library.init();
|
||||||
defer lib.deinit();
|
defer lib.deinit();
|
||||||
|
|
||||||
var c = try init(alloc);
|
var c = init();
|
||||||
defer c.deinit(alloc);
|
defer c.deinit(alloc);
|
||||||
c.load_options = .{ .library = lib };
|
c.load_options = .{ .library = lib };
|
||||||
|
|
||||||
@ -592,9 +712,13 @@ test autoItalicize {
|
|||||||
.{ .size = .{ .points = 12, .xdpi = 96, .ydpi = 96 } },
|
.{ .size = .{ .points = 12, .xdpi = 96, .ydpi = 96 } },
|
||||||
) });
|
) });
|
||||||
|
|
||||||
|
try testing.expect(c.getIndex('A', .bold, .{ .any = {} }) == null);
|
||||||
try testing.expect(c.getIndex('A', .italic, .{ .any = {} }) == null);
|
try testing.expect(c.getIndex('A', .italic, .{ .any = {} }) == null);
|
||||||
try c.autoItalicize(alloc);
|
try testing.expect(c.getIndex('A', .bold_italic, .{ .any = {} }) == null);
|
||||||
|
try c.completeStyles(alloc);
|
||||||
|
try testing.expect(c.getIndex('A', .bold, .{ .any = {} }) != null);
|
||||||
try testing.expect(c.getIndex('A', .italic, .{ .any = {} }) != null);
|
try testing.expect(c.getIndex('A', .italic, .{ .any = {} }) != null);
|
||||||
|
try testing.expect(c.getIndex('A', .bold_italic, .{ .any = {} }) != null);
|
||||||
}
|
}
|
||||||
|
|
||||||
test setSize {
|
test setSize {
|
||||||
@ -605,7 +729,7 @@ test setSize {
|
|||||||
var lib = try Library.init();
|
var lib = try Library.init();
|
||||||
defer lib.deinit();
|
defer lib.deinit();
|
||||||
|
|
||||||
var c = try init(alloc);
|
var c = init();
|
||||||
defer c.deinit(alloc);
|
defer c.deinit(alloc);
|
||||||
c.load_options = .{ .library = lib };
|
c.load_options = .{ .library = lib };
|
||||||
|
|
||||||
@ -628,7 +752,7 @@ test hasCodepoint {
|
|||||||
var lib = try Library.init();
|
var lib = try Library.init();
|
||||||
defer lib.deinit();
|
defer lib.deinit();
|
||||||
|
|
||||||
var c = try init(alloc);
|
var c = init();
|
||||||
defer c.deinit(alloc);
|
defer c.deinit(alloc);
|
||||||
c.load_options = .{ .library = lib };
|
c.load_options = .{ .library = lib };
|
||||||
|
|
||||||
@ -652,7 +776,7 @@ test "hasCodepoint emoji default graphical" {
|
|||||||
var lib = try Library.init();
|
var lib = try Library.init();
|
||||||
defer lib.deinit();
|
defer lib.deinit();
|
||||||
|
|
||||||
var c = try init(alloc);
|
var c = init();
|
||||||
defer c.deinit(alloc);
|
defer c.deinit(alloc);
|
||||||
c.load_options = .{ .library = lib };
|
c.load_options = .{ .library = lib };
|
||||||
|
|
||||||
|
@ -325,7 +325,7 @@ const TestMode = enum { normal };
|
|||||||
fn testGrid(mode: TestMode, alloc: Allocator, lib: Library) !SharedGrid {
|
fn testGrid(mode: TestMode, alloc: Allocator, lib: Library) !SharedGrid {
|
||||||
const testFont = @import("test.zig").fontRegular;
|
const testFont = @import("test.zig").fontRegular;
|
||||||
|
|
||||||
var c = try Collection.init(alloc);
|
var c = Collection.init();
|
||||||
c.load_options = .{ .library = lib };
|
c.load_options = .{ .library = lib };
|
||||||
|
|
||||||
switch (mode) {
|
switch (mode) {
|
||||||
|
@ -167,7 +167,7 @@ fn collection(
|
|||||||
.metric_modifiers = key.metric_modifiers,
|
.metric_modifiers = key.metric_modifiers,
|
||||||
};
|
};
|
||||||
|
|
||||||
var c = try Collection.init(self.alloc);
|
var c = Collection.init();
|
||||||
errdefer c.deinit(self.alloc);
|
errdefer c.deinit(self.alloc);
|
||||||
c.load_options = load_options;
|
c.load_options = load_options;
|
||||||
|
|
||||||
@ -187,20 +187,56 @@ fn collection(
|
|||||||
inline for (@typeInfo(Style).Enum.fields) |field| {
|
inline for (@typeInfo(Style).Enum.fields) |field| {
|
||||||
const style = @field(Style, field.name);
|
const style = @field(Style, field.name);
|
||||||
for (key.descriptorsForStyle(style)) |desc| {
|
for (key.descriptorsForStyle(style)) |desc| {
|
||||||
var disco_it = try disco.discover(self.alloc, desc);
|
{
|
||||||
defer disco_it.deinit();
|
var disco_it = try disco.discover(self.alloc, desc);
|
||||||
if (try disco_it.next()) |face| {
|
defer disco_it.deinit();
|
||||||
log.info("font {s}: {s}", .{
|
if (try disco_it.next()) |face| {
|
||||||
field.name,
|
log.info("font {s}: {s}", .{
|
||||||
try face.name(&name_buf),
|
field.name,
|
||||||
});
|
try face.name(&name_buf),
|
||||||
|
});
|
||||||
|
|
||||||
_ = try c.add(
|
_ = try c.add(
|
||||||
self.alloc,
|
self.alloc,
|
||||||
style,
|
style,
|
||||||
.{ .deferred = face },
|
.{ .deferred = face },
|
||||||
);
|
);
|
||||||
} else log.warn("font-family {s} not found: {s}", .{
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If there are variation configurations and we didn't find
|
||||||
|
// the font, then we retry the discovery with all stylistic
|
||||||
|
// bits set to false. This is because some fonts may not
|
||||||
|
// set the stylistic bit in their table but still support
|
||||||
|
// axes to mimic the style. At the time of writing, Berkeley
|
||||||
|
// Mono Variable is like this. See #2140.
|
||||||
|
if (style != .regular and desc.variations.len > 0) {
|
||||||
|
var disco_it = try disco.discover(self.alloc, desc: {
|
||||||
|
var copy = desc;
|
||||||
|
copy.bold = false;
|
||||||
|
copy.italic = false;
|
||||||
|
break :desc copy;
|
||||||
|
});
|
||||||
|
defer disco_it.deinit();
|
||||||
|
if (try disco_it.next()) |face| {
|
||||||
|
log.info("font {s}: {s}", .{
|
||||||
|
field.name,
|
||||||
|
try face.name(&name_buf),
|
||||||
|
});
|
||||||
|
|
||||||
|
_ = try c.add(
|
||||||
|
self.alloc,
|
||||||
|
style,
|
||||||
|
.{ .deferred = face },
|
||||||
|
);
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
log.warn("font-family {s} not found: {s}", .{
|
||||||
field.name,
|
field.name,
|
||||||
desc.family.?,
|
desc.family.?,
|
||||||
});
|
});
|
||||||
@ -218,15 +254,6 @@ fn collection(
|
|||||||
load_options.faceOptions(),
|
load_options.faceOptions(),
|
||||||
) },
|
) },
|
||||||
);
|
);
|
||||||
_ = try c.add(
|
|
||||||
self.alloc,
|
|
||||||
.bold,
|
|
||||||
.{ .fallback_loaded = try Face.init(
|
|
||||||
self.font_lib,
|
|
||||||
face_bold_ttf,
|
|
||||||
load_options.faceOptions(),
|
|
||||||
) },
|
|
||||||
);
|
|
||||||
|
|
||||||
// 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
|
||||||
@ -271,8 +298,9 @@ fn collection(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Auto-italicize
|
// Complete our styles to ensure we have something to satisfy every
|
||||||
try c.autoItalicize(self.alloc);
|
// possible style request.
|
||||||
|
try c.completeStyles(self.alloc);
|
||||||
|
|
||||||
return c;
|
return c;
|
||||||
}
|
}
|
||||||
@ -489,7 +517,7 @@ pub const Key = struct {
|
|||||||
.style = style,
|
.style = style,
|
||||||
.size = font_size.points,
|
.size = font_size.points,
|
||||||
.bold = style == null,
|
.bold = style == null,
|
||||||
.variations = config.@"font-variation".list.items,
|
.variations = config.@"font-variation-bold".list.items,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
for (config.@"font-family-italic".list.items) |family| {
|
for (config.@"font-family-italic".list.items) |family| {
|
||||||
@ -499,7 +527,7 @@ pub const Key = struct {
|
|||||||
.style = style,
|
.style = style,
|
||||||
.size = font_size.points,
|
.size = font_size.points,
|
||||||
.italic = style == null,
|
.italic = style == null,
|
||||||
.variations = config.@"font-variation".list.items,
|
.variations = config.@"font-variation-italic".list.items,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
for (config.@"font-family-bold-italic".list.items) |family| {
|
for (config.@"font-family-bold-italic".list.items) |family| {
|
||||||
@ -510,7 +538,7 @@ pub const Key = struct {
|
|||||||
.size = font_size.points,
|
.size = font_size.points,
|
||||||
.bold = style == null,
|
.bold = style == null,
|
||||||
.italic = style == null,
|
.italic = style == null,
|
||||||
.variations = config.@"font-variation".list.items,
|
.variations = config.@"font-variation-bold-italic".list.items,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -453,22 +453,22 @@ pub const CoreText = struct {
|
|||||||
// here.
|
// here.
|
||||||
|
|
||||||
if (desc.bold and desc.italic) {
|
if (desc.bold and desc.italic) {
|
||||||
const items = collection.faces.get(.bold_italic).items;
|
const entries = collection.faces.get(.bold_italic);
|
||||||
if (items.len > 0) {
|
if (entries.count() > 0) {
|
||||||
break :original try collection.getFace(.{ .style = .bold_italic });
|
break :original try collection.getFace(.{ .style = .bold_italic });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (desc.bold) {
|
if (desc.bold) {
|
||||||
const items = collection.faces.get(.bold).items;
|
const entries = collection.faces.get(.bold);
|
||||||
if (items.len > 0) {
|
if (entries.count() > 0) {
|
||||||
break :original try collection.getFace(.{ .style = .bold });
|
break :original try collection.getFace(.{ .style = .bold });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (desc.italic) {
|
if (desc.italic) {
|
||||||
const items = collection.faces.get(.italic).items;
|
const entries = collection.faces.get(.italic);
|
||||||
if (items.len > 0) {
|
if (entries.count() > 0) {
|
||||||
break :original try collection.getFace(.{ .style = .italic });
|
break :original try collection.getFace(.{ .style = .italic });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1746,7 +1746,7 @@ fn testShaperWithFont(alloc: Allocator, font_req: TestFont) !TestShaper {
|
|||||||
var lib = try Library.init();
|
var lib = try Library.init();
|
||||||
errdefer lib.deinit();
|
errdefer lib.deinit();
|
||||||
|
|
||||||
var c = try Collection.init(alloc);
|
var c = Collection.init();
|
||||||
c.load_options = .{ .library = lib };
|
c.load_options = .{ .library = lib };
|
||||||
|
|
||||||
// Setup group
|
// Setup group
|
||||||
|
@ -1207,7 +1207,7 @@ fn testShaperWithFont(alloc: Allocator, font_req: TestFont) !TestShaper {
|
|||||||
var lib = try Library.init();
|
var lib = try Library.init();
|
||||||
errdefer lib.deinit();
|
errdefer lib.deinit();
|
||||||
|
|
||||||
var c = try Collection.init(alloc);
|
var c = Collection.init();
|
||||||
c.load_options = .{ .library = lib };
|
c.load_options = .{ .library = lib };
|
||||||
|
|
||||||
// Setup group
|
// Setup group
|
||||||
|
Reference in New Issue
Block a user