font: fallback search should search full discovery chain

Fixes #668

We were previously only checking the first font result in the search.
This also fixes our CoreText scoring algorithm to prioritize faces that
have the codepoint we're searching for.
This commit is contained in:
Mitchell Hashimoto
2023-10-13 12:25:27 -07:00
parent 00b7625694
commit 1411015038
3 changed files with 40 additions and 10 deletions

View File

@ -7,7 +7,7 @@ const text = @import("../text.zig");
const c = @import("c.zig");
pub const Font = opaque {
pub fn createWithFontDescriptor(desc: *text.FontDescriptor, size: f32) Allocator.Error!*Font {
pub fn createWithFontDescriptor(desc: *const text.FontDescriptor, size: f32) Allocator.Error!*Font {
return @as(
?*Font,
@ptrFromInt(@intFromPtr(c.CTFontCreateWithFontDescriptor(

View File

@ -327,6 +327,7 @@ pub fn indexForCodepoint(
// If we are regular, try looking for a fallback using discovery.
if (style == .regular and font.Discover != void) {
log.debug("searching for a fallback font for cp={x}", .{cp});
if (self.discover) |disco| discover: {
var disco_it = disco.discover(self.alloc, .{
.codepoint = cp,
@ -337,11 +338,18 @@ pub fn indexForCodepoint(
}) catch break :discover;
defer disco_it.deinit();
if (disco_it.next() catch break :discover) |face| {
while (true) {
const face_ = disco_it.next() catch |err| {
log.warn("fallback search failed with error err={}", .{err});
break;
};
const face = face_ orelse break;
// Discovery is supposed to only return faces that have our
// codepoint but we can't search presentation in discovery so
// we have to check it here.
if (face.hasCodepoint(cp, p)) {
if (!face.hasCodepoint(cp, p)) continue;
var buf: [256]u8 = undefined;
log.info("found codepoint 0x{x} in fallback face={s}", .{
cp,
@ -349,7 +357,8 @@ pub fn indexForCodepoint(
});
return self.addFace(style, .{ .deferred = face }) catch break :discover;
}
}
log.debug("no fallback face found for cp={x}", .{cp});
}
}

View File

@ -399,6 +399,7 @@ pub const CoreText = struct {
style: Style = .unmatched,
monospace: bool = false,
codepoint: bool = false,
const Style = enum(u8) { unmatched = 0, match = 0xFF, _ };
@ -410,6 +411,26 @@ pub const CoreText = struct {
fn score(desc: *const Descriptor, ct_desc: *const macos.text.FontDescriptor) Score {
var score_acc: Score = .{};
// If we're searching for a codepoint, prioritize fonts that
// have that codepoint.
if (desc.codepoint > 0) codepoint: {
const font = macos.text.Font.createWithFontDescriptor(ct_desc, 12) catch
break :codepoint;
defer font.release();
// Turn UTF-32 into UTF-16 for CT API
var unichars: [2]u16 = undefined;
const pair = macos.foundation.stringGetSurrogatePairForLongCharacter(
desc.codepoint,
&unichars,
);
const len: usize = if (pair) 2 else 1;
// Get our glyphs
var glyphs = [2]macos.graphics.Glyph{ 0, 0 };
score_acc.codepoint = font.getGlyphsForCharacters(unichars[0..len], glyphs[0..len]);
}
// Get our symbolic traits for the descriptor so we can compare
// boolean attributes like bold, monospace, etc.
const symbolic_traits: macos.text.FontSymbolicTraits = traits: {