Merge pull request #1734 from mitchellh/ct-fallback

coretext: let macOS choose the font for CJK Unified Ideographs
This commit is contained in:
Mitchell Hashimoto
2024-05-07 15:21:38 -07:00
committed by GitHub
3 changed files with 114 additions and 2 deletions

View File

@ -18,6 +18,18 @@ pub const Font = opaque {
) orelse Allocator.Error.OutOfMemory;
}
pub fn createForString(
self: *Font,
str: *foundation.String,
range: foundation.Range,
) ?*Font {
return @ptrCast(@constCast(c.CTFontCreateForString(
@ptrCast(self),
@ptrCast(str),
@bitCast(range),
)));
}
pub fn copyWithAttributes(
self: *Font,
size: f32,
@ -51,6 +63,10 @@ pub const Font = opaque {
return @ptrCast(@constCast(c.CTFontCopyFeatures(@ptrCast(self))));
}
pub fn copyDefaultCascadeListForLanguages(self: *Font) *foundation.Array {
return @ptrCast(@constCast(c.CTFontCopyDefaultCascadeListForLanguages(@ptrCast(self), null)));
}
pub fn getGlyphCount(self: *Font) usize {
return @intCast(c.CTFontGetGlyphCount(@ptrCast(self)));
}

View File

@ -172,7 +172,7 @@ pub fn getIndex(
if (self.discover) |disco| discover: {
const load_opts = self.collection.load_options orelse
break :discover;
var disco_it = disco.discover(alloc, .{
var disco_it = disco.discoverFallback(alloc, &self.collection, .{
.codepoint = cp,
.size = load_opts.size.points,
.bold = style == .bold or style == .bold_italic,

View File

@ -5,6 +5,7 @@ const assert = std.debug.assert;
const fontconfig = @import("fontconfig");
const macos = @import("macos");
const options = @import("main.zig").options;
const Collection = @import("main.zig").Collection;
const DeferredFace = @import("main.zig").DeferredFace;
const Variation = @import("main.zig").face.Variation;
@ -258,7 +259,11 @@ pub const Fontconfig = 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 Fontconfig, alloc: Allocator, desc: Descriptor) !DiscoverIterator {
pub fn discover(
self: *const Fontconfig,
alloc: Allocator,
desc: Descriptor,
) !DiscoverIterator {
_ = alloc;
// Build our pattern that we'll search for
@ -282,6 +287,16 @@ pub const Fontconfig = struct {
};
}
pub fn discoverFallback(
self: *const Fontconfig,
alloc: Allocator,
collection: *Collection,
desc: Descriptor,
) !DiscoverIterator {
_ = collection;
return try self.discover(alloc, desc);
}
pub const DiscoverIterator = struct {
config: *fontconfig.Config,
pattern: *fontconfig.Pattern,
@ -364,6 +379,87 @@ pub const CoreText = struct {
};
}
pub fn discoverFallback(
self: *const CoreText,
alloc: Allocator,
collection: *Collection,
desc: Descriptor,
) !DiscoverIterator {
// If we have a codepoint within the CJK unified ideographs block
// then we fallback to macOS to find a font that supports it because
// there isn't a better way manually with CoreText that I can find that
// properly takes into account system locale.
//
// References:
// - http://unicode.org/charts/PDF/U4E00.pdf
// - https://chromium.googlesource.com/chromium/src/+/main/third_party/blink/renderer/platform/fonts/LocaleInFonts.md#unified-han-ideographs
if (desc.codepoint >= 0x4E00 and
desc.codepoint <= 0x9FFF)
han: {
const han = try self.discoverCodepoint(
collection,
desc,
) orelse break :han;
// This is silly but our discover iterator needs a slice so
// we allocate here. This isn't a performance bottleneck but
// this is something we can optimize very easily...
const list = try alloc.alloc(*macos.text.FontDescriptor, 1);
errdefer alloc.free(list);
list[0] = han;
return DiscoverIterator{
.alloc = alloc,
.list = list,
.i = 0,
};
}
return try self.discover(alloc, desc);
}
/// Discover a font for a specific codepoint using the CoreText
/// CTFontCreateForString API.
fn discoverCodepoint(
self: *const CoreText,
collection: *Collection,
desc: Descriptor,
) !?*macos.text.FontDescriptor {
_ = self;
assert(desc.codepoint > 0);
// Get our original font. This is dependent on the requestd style
// from the descriptor.
const original = original: {
break :original try collection.getFace(.{ .style = .regular });
};
// We need it in utf8 format
var buf: [4]u8 = undefined;
const len = try std.unicode.utf8Encode(
@intCast(desc.codepoint),
&buf,
);
// We need a CFString
const str = try macos.foundation.String.createWithBytes(
buf[0..len],
.utf8,
false,
);
defer str.release();
// Get our font
const font = original.font.createForString(
str,
macos.foundation.Range.init(0, 1),
) orelse return null;
defer font.release();
// Get the descriptor
return font.copyDescriptor();
}
fn copyMatchingDescriptors(
alloc: Allocator,
list: *macos.foundation.Array,