mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-08-02 14:57:31 +03:00
font/coretext: score discovered fonts
This commit is contained in:
@ -63,7 +63,7 @@ pub const MutableArray = opaque {
|
|||||||
a: *const Elem,
|
a: *const Elem,
|
||||||
b: *const Elem,
|
b: *const Elem,
|
||||||
context: ?*Context,
|
context: ?*Context,
|
||||||
) ComparisonResult,
|
) callconv(.C) ComparisonResult,
|
||||||
) void {
|
) void {
|
||||||
CFArraySortValues(
|
CFArraySortValues(
|
||||||
self,
|
self,
|
||||||
@ -122,7 +122,7 @@ test "array sorting" {
|
|||||||
void,
|
void,
|
||||||
null,
|
null,
|
||||||
struct {
|
struct {
|
||||||
fn compare(a: *const u8, b: *const u8, _: ?*void) ComparisonResult {
|
fn compare(a: *const u8, b: *const u8, _: ?*void) callconv(.C) ComparisonResult {
|
||||||
if (a.* > b.*) return .greater;
|
if (a.* > b.*) return .greater;
|
||||||
if (a.* == b.*) return .equal;
|
if (a.* == b.*) return .equal;
|
||||||
return .less;
|
return .less;
|
||||||
|
@ -46,18 +46,22 @@ pub const FontDescriptor = opaque {
|
|||||||
) orelse Allocator.Error.OutOfMemory;
|
) orelse Allocator.Error.OutOfMemory;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn retain(self: *FontDescriptor) void {
|
||||||
|
_ = c.CFRetain(self);
|
||||||
|
}
|
||||||
|
|
||||||
pub fn release(self: *FontDescriptor) void {
|
pub fn release(self: *FontDescriptor) void {
|
||||||
c.CFRelease(self);
|
c.CFRelease(self);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn copyAttribute(self: *FontDescriptor, comptime attr: FontAttribute) attr.Value() {
|
pub fn copyAttribute(self: *const FontDescriptor, comptime attr: FontAttribute) attr.Value() {
|
||||||
return @ptrFromInt(@intFromPtr(c.CTFontDescriptorCopyAttribute(
|
return @ptrFromInt(@intFromPtr(c.CTFontDescriptorCopyAttribute(
|
||||||
@ptrCast(self),
|
@ptrCast(self),
|
||||||
@ptrCast(attr.key()),
|
@ptrCast(attr.key()),
|
||||||
)));
|
)));
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn copyAttributes(self: *FontDescriptor) *foundation.Dictionary {
|
pub fn copyAttributes(self: *const FontDescriptor) *foundation.Dictionary {
|
||||||
return @ptrFromInt(@intFromPtr(c.CTFontDescriptorCopyAttributes(
|
return @ptrFromInt(@intFromPtr(c.CTFontDescriptorCopyAttributes(
|
||||||
@ptrCast(self),
|
@ptrCast(self),
|
||||||
)));
|
)));
|
||||||
|
@ -246,7 +246,7 @@ pub fn init(
|
|||||||
var name_buf: [256]u8 = undefined;
|
var name_buf: [256]u8 = undefined;
|
||||||
|
|
||||||
if (config.@"font-family") |family| {
|
if (config.@"font-family") |family| {
|
||||||
var disco_it = try disco.discover(.{
|
var disco_it = try disco.discover(alloc, .{
|
||||||
.family = family,
|
.family = family,
|
||||||
.style = config.@"font-style".nameValue(),
|
.style = config.@"font-style".nameValue(),
|
||||||
.size = font_size.points,
|
.size = font_size.points,
|
||||||
@ -259,7 +259,7 @@ pub fn init(
|
|||||||
} else log.warn("font-family not found: {s}", .{family});
|
} else log.warn("font-family not found: {s}", .{family});
|
||||||
}
|
}
|
||||||
if (config.@"font-family-bold") |family| {
|
if (config.@"font-family-bold") |family| {
|
||||||
var disco_it = try disco.discover(.{
|
var disco_it = try disco.discover(alloc, .{
|
||||||
.family = family,
|
.family = family,
|
||||||
.style = config.@"font-style-bold".nameValue(),
|
.style = config.@"font-style-bold".nameValue(),
|
||||||
.size = font_size.points,
|
.size = font_size.points,
|
||||||
@ -273,7 +273,7 @@ pub fn init(
|
|||||||
} else log.warn("font-family-bold not found: {s}", .{family});
|
} else log.warn("font-family-bold not found: {s}", .{family});
|
||||||
}
|
}
|
||||||
if (config.@"font-family-italic") |family| {
|
if (config.@"font-family-italic") |family| {
|
||||||
var disco_it = try disco.discover(.{
|
var disco_it = try disco.discover(alloc, .{
|
||||||
.family = family,
|
.family = family,
|
||||||
.style = config.@"font-style-italic".nameValue(),
|
.style = config.@"font-style-italic".nameValue(),
|
||||||
.size = font_size.points,
|
.size = font_size.points,
|
||||||
@ -287,7 +287,7 @@ pub fn init(
|
|||||||
} else log.warn("font-family-italic not found: {s}", .{family});
|
} else log.warn("font-family-italic not found: {s}", .{family});
|
||||||
}
|
}
|
||||||
if (config.@"font-family-bold-italic") |family| {
|
if (config.@"font-family-bold-italic") |family| {
|
||||||
var disco_it = try disco.discover(.{
|
var disco_it = try disco.discover(alloc, .{
|
||||||
.family = family,
|
.family = family,
|
||||||
.style = config.@"font-style-bold-italic".nameValue(),
|
.style = config.@"font-style-bold-italic".nameValue(),
|
||||||
.size = font_size.points,
|
.size = font_size.points,
|
||||||
|
@ -85,7 +85,7 @@ fn runArgs(alloc_gpa: Allocator, argsIter: anytype) !u8 {
|
|||||||
// Look up all available fonts
|
// Look up all available fonts
|
||||||
var disco = font.Discover.init();
|
var disco = font.Discover.init();
|
||||||
defer disco.deinit();
|
defer disco.deinit();
|
||||||
var disco_it = try disco.discover(.{
|
var disco_it = try disco.discover(alloc, .{
|
||||||
.family = config.family,
|
.family = config.family,
|
||||||
.style = config.style,
|
.style = config.style,
|
||||||
.bold = config.bold,
|
.bold = config.bold,
|
||||||
|
@ -408,6 +408,7 @@ test "coretext" {
|
|||||||
|
|
||||||
const discovery = @import("main.zig").discovery;
|
const discovery = @import("main.zig").discovery;
|
||||||
const testing = std.testing;
|
const testing = std.testing;
|
||||||
|
const alloc = testing.allocator;
|
||||||
|
|
||||||
// Load freetype
|
// Load freetype
|
||||||
var lib = try Library.init();
|
var lib = try Library.init();
|
||||||
@ -416,7 +417,7 @@ test "coretext" {
|
|||||||
// Get a deferred face from fontconfig
|
// Get a deferred face from fontconfig
|
||||||
var def = def: {
|
var def = def: {
|
||||||
var fc = discovery.CoreText.init();
|
var fc = discovery.CoreText.init();
|
||||||
var it = try fc.discover(.{ .family = "Monaco", .size = 12 });
|
var it = try fc.discover(alloc, .{ .family = "Monaco", .size = 12 });
|
||||||
defer it.deinit();
|
defer it.deinit();
|
||||||
break :def (try it.next()).?;
|
break :def (try it.next()).?;
|
||||||
};
|
};
|
||||||
|
@ -313,7 +313,7 @@ pub fn indexForCodepoint(
|
|||||||
// If we are regular, try looking for a fallback using discovery.
|
// If we are regular, try looking for a fallback using discovery.
|
||||||
if (style == .regular and font.Discover != void) {
|
if (style == .regular and font.Discover != void) {
|
||||||
if (self.discover) |disco| discover: {
|
if (self.discover) |disco| discover: {
|
||||||
var disco_it = disco.discover(.{
|
var disco_it = disco.discover(self.alloc, .{
|
||||||
.codepoint = cp,
|
.codepoint = cp,
|
||||||
.size = self.size.points,
|
.size = self.size.points,
|
||||||
.bold = style == .bold or style == .bold_italic,
|
.bold = style == .bold or style == .bold_italic,
|
||||||
@ -382,7 +382,7 @@ fn indexForCodepointOverride(self: *Group, cp: u32) !?FontIndex {
|
|||||||
const idx_: ?FontIndex = self.descriptor_cache.get(desc) orelse idx: {
|
const idx_: ?FontIndex = self.descriptor_cache.get(desc) orelse idx: {
|
||||||
// Slow path: we have to find this descriptor and load the font
|
// Slow path: we have to find this descriptor and load the font
|
||||||
const discover = self.discover orelse return null;
|
const discover = self.discover orelse return null;
|
||||||
var disco_it = try discover.discover(desc);
|
var disco_it = try discover.discover(self.alloc, desc);
|
||||||
defer disco_it.deinit();
|
defer disco_it.deinit();
|
||||||
|
|
||||||
const face = (try disco_it.next()) orelse {
|
const face = (try disco_it.next()) orelse {
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const builtin = @import("builtin");
|
const builtin = @import("builtin");
|
||||||
|
const Allocator = std.mem.Allocator;
|
||||||
const assert = std.debug.assert;
|
const assert = std.debug.assert;
|
||||||
const fontconfig = @import("fontconfig");
|
const fontconfig = @import("fontconfig");
|
||||||
const macos = @import("macos");
|
const macos = @import("macos");
|
||||||
@ -307,7 +308,7 @@ pub const CoreText = struct {
|
|||||||
|
|
||||||
/// Discover fonts from a descriptor. This returns an iterator that can
|
/// Discover fonts from a descriptor. This returns an iterator that can
|
||||||
/// be used to build up the deferred fonts.
|
/// be used to build up the deferred fonts.
|
||||||
pub fn discover(self: *const CoreText, desc: Descriptor) !DiscoverIterator {
|
pub fn discover(self: *const CoreText, alloc: Allocator, desc: Descriptor) !DiscoverIterator {
|
||||||
_ = self;
|
_ = self;
|
||||||
|
|
||||||
// Build our pattern that we'll search for
|
// Build our pattern that we'll search for
|
||||||
@ -323,25 +324,104 @@ pub const CoreText = struct {
|
|||||||
const set = try macos.text.FontCollection.createWithFontDescriptors(desc_arr);
|
const set = try macos.text.FontCollection.createWithFontDescriptors(desc_arr);
|
||||||
defer set.release();
|
defer set.release();
|
||||||
const list = set.createMatchingFontDescriptors();
|
const list = set.createMatchingFontDescriptors();
|
||||||
errdefer list.release();
|
defer list.release();
|
||||||
|
|
||||||
|
// Sort our descriptors
|
||||||
|
const zig_list = try copyMatchingDescriptors(alloc, list);
|
||||||
|
errdefer alloc.free(zig_list);
|
||||||
|
sortMatchingDescriptors(&desc, zig_list);
|
||||||
|
|
||||||
return DiscoverIterator{
|
return DiscoverIterator{
|
||||||
.list = list,
|
.alloc = alloc,
|
||||||
|
.list = zig_list,
|
||||||
.i = 0,
|
.i = 0,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
pub const DiscoverIterator = struct {
|
fn copyMatchingDescriptors(
|
||||||
|
alloc: Allocator,
|
||||||
list: *macos.foundation.Array,
|
list: *macos.foundation.Array,
|
||||||
|
) ![]*macos.text.FontDescriptor {
|
||||||
|
var result = try alloc.alloc(*macos.text.FontDescriptor, list.getCount());
|
||||||
|
errdefer alloc.free(result);
|
||||||
|
for (0..result.len) |i| {
|
||||||
|
result[i] = list.getValueAtIndex(macos.text.FontDescriptor, i);
|
||||||
|
|
||||||
|
// We need to retain becauseonce the list is freed it will
|
||||||
|
// release all its members.
|
||||||
|
result[i].retain();
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn sortMatchingDescriptors(
|
||||||
|
desc: *const Descriptor,
|
||||||
|
list: []*macos.text.FontDescriptor,
|
||||||
|
) void {
|
||||||
|
var desc_mut = desc.*;
|
||||||
|
if (desc_mut.style == null) {
|
||||||
|
// If there is no explicit style set, we set a preferred
|
||||||
|
// based on the style bool attributes.
|
||||||
|
//
|
||||||
|
// TODO: doesn't handle i18n font names well, we should have
|
||||||
|
// another mechanism that uses the weight attribute if it exists.
|
||||||
|
// Wait for this to be a real problem.
|
||||||
|
desc_mut.style = if (desc_mut.bold and desc_mut.italic)
|
||||||
|
"Bold Italic"
|
||||||
|
else if (desc_mut.bold)
|
||||||
|
"Bold"
|
||||||
|
else if (desc_mut.italic)
|
||||||
|
"Italic"
|
||||||
|
else
|
||||||
|
null;
|
||||||
|
}
|
||||||
|
|
||||||
|
std.mem.sortUnstable(*macos.text.FontDescriptor, list, &desc_mut, struct {
|
||||||
|
fn lessThan(
|
||||||
|
desc_inner: *const Descriptor,
|
||||||
|
lhs: *macos.text.FontDescriptor,
|
||||||
|
rhs: *macos.text.FontDescriptor,
|
||||||
|
) bool {
|
||||||
|
const lhs_score = score(desc_inner, lhs);
|
||||||
|
const rhs_score = score(desc_inner, rhs);
|
||||||
|
// Higher score is "less" (earlier)
|
||||||
|
return lhs_score > rhs_score;
|
||||||
|
}
|
||||||
|
}.lessThan);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn score(desc: *const Descriptor, ct_desc: *const macos.text.FontDescriptor) i32 {
|
||||||
|
var score_acc: i32 = 0;
|
||||||
|
|
||||||
|
score_acc += if (desc.style) |desired_style| style: {
|
||||||
|
const style = ct_desc.copyAttribute(.style_name);
|
||||||
|
defer style.release();
|
||||||
|
var buf: [128]u8 = undefined;
|
||||||
|
const style_str = style.cstring(&buf, .utf8) orelse break :style 0;
|
||||||
|
|
||||||
|
// Matching style string gets highest score
|
||||||
|
if (std.mem.eql(u8, desired_style, style_str)) break :style 100;
|
||||||
|
|
||||||
|
// Otherwise the score is based on the length of the style string.
|
||||||
|
// Shorter styles are scored higher.
|
||||||
|
break :style -1 * @as(i32, @intCast(style_str.len));
|
||||||
|
} else 0;
|
||||||
|
|
||||||
|
return score_acc;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const DiscoverIterator = struct {
|
||||||
|
alloc: Allocator,
|
||||||
|
list: []const *macos.text.FontDescriptor,
|
||||||
i: usize,
|
i: usize,
|
||||||
|
|
||||||
pub fn deinit(self: *DiscoverIterator) void {
|
pub fn deinit(self: *DiscoverIterator) void {
|
||||||
self.list.release();
|
self.alloc.free(self.list);
|
||||||
self.* = undefined;
|
self.* = undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn next(self: *DiscoverIterator) !?DeferredFace {
|
pub fn next(self: *DiscoverIterator) !?DeferredFace {
|
||||||
if (self.i >= self.list.getCount()) return null;
|
if (self.i >= self.list.len) return null;
|
||||||
|
|
||||||
// Get our descriptor. We need to remove the character set
|
// Get our descriptor. We need to remove the character set
|
||||||
// limitation because we may have used that to filter but we
|
// limitation because we may have used that to filter but we
|
||||||
@ -349,7 +429,7 @@ pub const CoreText = struct {
|
|||||||
// available.
|
// available.
|
||||||
//const desc = self.list.getValueAtIndex(macos.text.FontDescriptor, self.i);
|
//const desc = self.list.getValueAtIndex(macos.text.FontDescriptor, self.i);
|
||||||
const desc = desc: {
|
const desc = desc: {
|
||||||
const original = self.list.getValueAtIndex(macos.text.FontDescriptor, self.i);
|
const original = self.list[self.i];
|
||||||
|
|
||||||
// For some reason simply copying the attributes and recreating
|
// For some reason simply copying the attributes and recreating
|
||||||
// the descriptor removes the charset restriction. This is tested.
|
// the descriptor removes the charset restriction. This is tested.
|
||||||
@ -392,8 +472,11 @@ test "descriptor hash familiy names" {
|
|||||||
test "fontconfig" {
|
test "fontconfig" {
|
||||||
if (options.backend != .fontconfig_freetype) return error.SkipZigTest;
|
if (options.backend != .fontconfig_freetype) return error.SkipZigTest;
|
||||||
|
|
||||||
|
const testing = std.testing;
|
||||||
|
const alloc = testing.allocator;
|
||||||
|
|
||||||
var fc = Fontconfig.init();
|
var fc = Fontconfig.init();
|
||||||
var it = try fc.discover(.{ .family = "monospace", .size = 12 });
|
var it = try fc.discover(alloc, .{ .family = "monospace", .size = 12 });
|
||||||
defer it.deinit();
|
defer it.deinit();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -401,9 +484,10 @@ test "fontconfig codepoint" {
|
|||||||
if (options.backend != .fontconfig_freetype) return error.SkipZigTest;
|
if (options.backend != .fontconfig_freetype) return error.SkipZigTest;
|
||||||
|
|
||||||
const testing = std.testing;
|
const testing = std.testing;
|
||||||
|
const alloc = testing.allocator;
|
||||||
|
|
||||||
var fc = Fontconfig.init();
|
var fc = Fontconfig.init();
|
||||||
var it = try fc.discover(.{ .codepoint = 'A', .size = 12 });
|
var it = try fc.discover(alloc, .{ .codepoint = 'A', .size = 12 });
|
||||||
defer it.deinit();
|
defer it.deinit();
|
||||||
|
|
||||||
// The first result should have the codepoint. Later ones may not
|
// The first result should have the codepoint. Later ones may not
|
||||||
@ -420,10 +504,11 @@ test "coretext" {
|
|||||||
return error.SkipZigTest;
|
return error.SkipZigTest;
|
||||||
|
|
||||||
const testing = std.testing;
|
const testing = std.testing;
|
||||||
|
const alloc = testing.allocator;
|
||||||
|
|
||||||
var ct = CoreText.init();
|
var ct = CoreText.init();
|
||||||
defer ct.deinit();
|
defer ct.deinit();
|
||||||
var it = try ct.discover(.{ .family = "Monaco", .size = 12 });
|
var it = try ct.discover(alloc, .{ .family = "Monaco", .size = 12 });
|
||||||
defer it.deinit();
|
defer it.deinit();
|
||||||
var count: usize = 0;
|
var count: usize = 0;
|
||||||
while (try it.next()) |_| {
|
while (try it.next()) |_| {
|
||||||
@ -437,10 +522,11 @@ test "coretext codepoint" {
|
|||||||
return error.SkipZigTest;
|
return error.SkipZigTest;
|
||||||
|
|
||||||
const testing = std.testing;
|
const testing = std.testing;
|
||||||
|
const alloc = testing.allocator;
|
||||||
|
|
||||||
var ct = CoreText.init();
|
var ct = CoreText.init();
|
||||||
defer ct.deinit();
|
defer ct.deinit();
|
||||||
var it = try ct.discover(.{ .codepoint = 'A', .size = 12 });
|
var it = try ct.discover(alloc, .{ .codepoint = 'A', .size = 12 });
|
||||||
defer it.deinit();
|
defer it.deinit();
|
||||||
|
|
||||||
// The first result should have the codepoint. Later ones may not
|
// The first result should have the codepoint. Later ones may not
|
||||||
|
@ -908,7 +908,7 @@ fn testShaper(alloc: Allocator) !TestShaper {
|
|||||||
// On CoreText we want to load Apple Emoji, we should have it.
|
// On CoreText we want to load Apple Emoji, we should have it.
|
||||||
var disco = font.Discover.init();
|
var disco = font.Discover.init();
|
||||||
defer disco.deinit();
|
defer disco.deinit();
|
||||||
var disco_it = try disco.discover(.{
|
var disco_it = try disco.discover(alloc, .{
|
||||||
.family = "Apple Color Emoji",
|
.family = "Apple Color Emoji",
|
||||||
.size = 12,
|
.size = 12,
|
||||||
.monospace = false,
|
.monospace = false,
|
||||||
|
Reference in New Issue
Block a user