diff --git a/pkg/macos/text/font.zig b/pkg/macos/text/font.zig index 6fd2db21a..626b9089b 100644 --- a/pkg/macos/text/font.zig +++ b/pkg/macos/text/font.zig @@ -18,6 +18,17 @@ pub const Font = opaque { ) orelse Allocator.Error.OutOfMemory; } + pub fn createWithName(name: *foundation.String, size: f32) Allocator.Error!*Font { + return @as( + ?*Font, + @ptrFromInt(@intFromPtr(c.CTFontCreateWithName( + @ptrCast(name), + size, + null, + ))), + ) orelse Allocator.Error.OutOfMemory; + } + pub fn createForString( self: *Font, str: *foundation.String, diff --git a/src/font/SharedGridSet.zig b/src/font/SharedGridSet.zig index b4f602e89..dc7d8dd7c 100644 --- a/src/font/SharedGridSet.zig +++ b/src/font/SharedGridSet.zig @@ -340,6 +340,24 @@ fn collection( // specifying a font-family for emoji. if (comptime builtin.target.os.tag.isDarwin() and Discover != void) apple_emoji: { const disco = try self.discover() orelse break :apple_emoji; + + // Fast path: we know the exact name of the font we want so we + // can look it up directly, which is sometimes significantly faster than + // full discovery (e.g. CoreText). + if (@hasDecl(Discover, "discoverExactFamily")) { + if (try disco.discoverExactFamily( + "Apple Color Emoji", + )) |face| { + _ = try c.addDeferred(self.alloc, face, .{ + .style = .regular, + .fallback = true, + // No size adjustment for emojis. + .size_adjustment = .none, + }); + break :apple_emoji; + } + } + var disco_it = try disco.discover(self.alloc, .{ .family = "Apple Color Emoji", }); diff --git a/src/font/discovery.zig b/src/font/discovery.zig index d1e60fd34..1e44026ce 100644 --- a/src/font/discovery.zig +++ b/src/font/discovery.zig @@ -383,6 +383,54 @@ pub const CoreText = struct { }; } + /// Discover a font by its exact name (family, full, or PostScript + /// name). This is significantly faster than `discover` because it + /// avoids the system-wide font matching that CTFontCollection does + /// (which takes multiple milliseconds). This should be preferred + /// when the desired font is known exactly, e.g. system fonts such + /// as Apple Color Emoji. + /// + /// Returns null if no font with this exact family name exists; + /// CoreText fallback fonts are never returned. + pub fn discoverExactFamily( + self: *const CoreText, + family: []const u8, + ) !?DeferredFace { + _ = self; + + const family_str = try macos.foundation.String.createWithBytes( + family, + .utf8, + false, + ); + defer family_str.release(); + + // Create our font. We need a size to initialize it so we use size + // 12 but we will alter the size later (same as DiscoverIterator). + const ct_font = try macos.text.Font.createWithName(family_str, 12); + + // CTFontCreateWithName never returns null: if the requested font + // isn't installed it returns a substitute font. Verify we got + // the family we asked for, otherwise report not found. + const found: bool = found: { + const actual = ct_font.copyFamilyName(); + defer actual.release(); + var buf: [256]u8 = undefined; + const actual_slice = actual.cstring(&buf, .utf8) orelse + break :found false; + break :found std.mem.eql(u8, actual_slice, family); + }; + if (!found) { + ct_font.release(); + return null; + } + + return .{ .ct = .{ + .font = ct_font, + .variations = &.{}, + } }; + } + pub fn discoverFallback( self: *const CoreText, alloc: Allocator,