Skip to content

Commit

Permalink
font/coretext: score discovered fonts
Browse files Browse the repository at this point in the history
  • Loading branch information
mitchellh committed Oct 3, 2023
1 parent fda56fd commit 1127330
Show file tree
Hide file tree
Showing 8 changed files with 115 additions and 24 deletions.
4 changes: 2 additions & 2 deletions pkg/macos/foundation/array.zig
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ pub const MutableArray = opaque {
a: *const Elem,
b: *const Elem,
context: ?*Context,
) ComparisonResult,
) callconv(.C) ComparisonResult,
) void {
CFArraySortValues(
self,
Expand Down Expand Up @@ -122,7 +122,7 @@ test "array sorting" {
void,
null,
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 .equal;
return .less;
Expand Down
8 changes: 6 additions & 2 deletions pkg/macos/text/font_descriptor.zig
Original file line number Diff line number Diff line change
Expand Up @@ -46,18 +46,22 @@ pub const FontDescriptor = opaque {
) orelse Allocator.Error.OutOfMemory;
}

pub fn retain(self: *FontDescriptor) void {
_ = c.CFRetain(self);
}

pub fn release(self: *FontDescriptor) void {
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(
@ptrCast(self),
@ptrCast(attr.key()),
)));
}

pub fn copyAttributes(self: *FontDescriptor) *foundation.Dictionary {
pub fn copyAttributes(self: *const FontDescriptor) *foundation.Dictionary {
return @ptrFromInt(@intFromPtr(c.CTFontDescriptorCopyAttributes(
@ptrCast(self),
)));
Expand Down
8 changes: 4 additions & 4 deletions src/Surface.zig
Original file line number Diff line number Diff line change
Expand Up @@ -246,7 +246,7 @@ pub fn init(
var name_buf: [256]u8 = undefined;

if (config.@"font-family") |family| {
var disco_it = try disco.discover(.{
var disco_it = try disco.discover(alloc, .{
.family = family,
.style = config.@"font-style".nameValue(),
.size = font_size.points,
Expand All @@ -259,7 +259,7 @@ pub fn init(
} else log.warn("font-family not found: {s}", .{family});
}
if (config.@"font-family-bold") |family| {
var disco_it = try disco.discover(.{
var disco_it = try disco.discover(alloc, .{
.family = family,
.style = config.@"font-style-bold".nameValue(),
.size = font_size.points,
Expand All @@ -273,7 +273,7 @@ pub fn init(
} else log.warn("font-family-bold not found: {s}", .{family});
}
if (config.@"font-family-italic") |family| {
var disco_it = try disco.discover(.{
var disco_it = try disco.discover(alloc, .{
.family = family,
.style = config.@"font-style-italic".nameValue(),
.size = font_size.points,
Expand All @@ -287,7 +287,7 @@ pub fn init(
} else log.warn("font-family-italic not found: {s}", .{family});
}
if (config.@"font-family-bold-italic") |family| {
var disco_it = try disco.discover(.{
var disco_it = try disco.discover(alloc, .{
.family = family,
.style = config.@"font-style-bold-italic".nameValue(),
.size = font_size.points,
Expand Down
2 changes: 1 addition & 1 deletion src/cli/list_fonts.zig
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ fn runArgs(alloc_gpa: Allocator, argsIter: anytype) !u8 {
// Look up all available fonts
var disco = font.Discover.init();
defer disco.deinit();
var disco_it = try disco.discover(.{
var disco_it = try disco.discover(alloc, .{
.family = config.family,
.style = config.style,
.bold = config.bold,
Expand Down
3 changes: 2 additions & 1 deletion src/font/DeferredFace.zig
Original file line number Diff line number Diff line change
Expand Up @@ -408,6 +408,7 @@ test "coretext" {

const discovery = @import("main.zig").discovery;
const testing = std.testing;
const alloc = testing.allocator;

// Load freetype
var lib = try Library.init();
Expand All @@ -416,7 +417,7 @@ test "coretext" {
// Get a deferred face from fontconfig
var def = def: {
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();
break :def (try it.next()).?;
};
Expand Down
4 changes: 2 additions & 2 deletions src/font/Group.zig
Original file line number Diff line number Diff line change
Expand Up @@ -313,7 +313,7 @@ pub fn indexForCodepoint(
// If we are regular, try looking for a fallback using discovery.
if (style == .regular and font.Discover != void) {
if (self.discover) |disco| discover: {
var disco_it = disco.discover(.{
var disco_it = disco.discover(self.alloc, .{
.codepoint = cp,
.size = self.size.points,
.bold = style == .bold or style == .bold_italic,
Expand Down Expand Up @@ -382,7 +382,7 @@ fn indexForCodepointOverride(self: *Group, cp: u32) !?FontIndex {
const idx_: ?FontIndex = self.descriptor_cache.get(desc) orelse idx: {
// Slow path: we have to find this descriptor and load the font
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();

const face = (try disco_it.next()) orelse {
Expand Down
108 changes: 97 additions & 11 deletions src/font/discovery.zig
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
const std = @import("std");
const builtin = @import("builtin");
const Allocator = std.mem.Allocator;
const assert = std.debug.assert;
const fontconfig = @import("fontconfig");
const macos = @import("macos");
Expand Down Expand Up @@ -307,7 +308,7 @@ pub const CoreText = struct {

/// Discover fonts from a descriptor. This returns an iterator that can
/// 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;

// Build our pattern that we'll search for
Expand All @@ -323,33 +324,112 @@ pub const CoreText = struct {
const set = try macos.text.FontCollection.createWithFontDescriptors(desc_arr);
defer set.release();
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{
.list = list,
.alloc = alloc,
.list = zig_list,
.i = 0,
};
}

pub const DiscoverIterator = struct {
fn copyMatchingDescriptors(
alloc: Allocator,
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,

pub fn deinit(self: *DiscoverIterator) void {
self.list.release();
self.alloc.free(self.list);
self.* = undefined;
}

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
// limitation because we may have used that to filter but we
// don't want it anymore because it'll restrict the characters
// available.
//const desc = self.list.getValueAtIndex(macos.text.FontDescriptor, self.i);
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
// the descriptor removes the charset restriction. This is tested.
Expand Down Expand Up @@ -392,18 +472,22 @@ test "descriptor hash familiy names" {
test "fontconfig" {
if (options.backend != .fontconfig_freetype) return error.SkipZigTest;

const testing = std.testing;
const alloc = testing.allocator;

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();
}

test "fontconfig codepoint" {
if (options.backend != .fontconfig_freetype) return error.SkipZigTest;

const testing = std.testing;
const alloc = testing.allocator;

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();

// The first result should have the codepoint. Later ones may not
Expand All @@ -420,10 +504,11 @@ test "coretext" {
return error.SkipZigTest;

const testing = std.testing;
const alloc = testing.allocator;

var ct = CoreText.init();
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();
var count: usize = 0;
while (try it.next()) |_| {
Expand All @@ -437,10 +522,11 @@ test "coretext codepoint" {
return error.SkipZigTest;

const testing = std.testing;
const alloc = testing.allocator;

var ct = CoreText.init();
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();

// The first result should have the codepoint. Later ones may not
Expand Down
2 changes: 1 addition & 1 deletion src/font/shaper/harfbuzz.zig
Original file line number Diff line number Diff line change
Expand Up @@ -908,7 +908,7 @@ fn testShaper(alloc: Allocator) !TestShaper {
// On CoreText we want to load Apple Emoji, we should have it.
var disco = font.Discover.init();
defer disco.deinit();
var disco_it = try disco.discover(.{
var disco_it = try disco.discover(alloc, .{
.family = "Apple Color Emoji",
.size = 12,
.monospace = false,
Expand Down

0 comments on commit 1127330

Please sign in to comment.