diff --git a/pkg/macos/text/font.zig b/pkg/macos/text/font.zig index e0c1f4891..f4db72d6c 100644 --- a/pkg/macos/text/font.zig +++ b/pkg/macos/text/font.zig @@ -81,6 +81,14 @@ pub const Font = opaque { ); } + pub fn createPathForGlyph(self: *Font, glyph: graphics.Glyph) ?*graphics.Path { + return @constCast(@ptrCast(c.CTFontCreatePathForGlyph( + @ptrCast(self), + glyph, + null, + ))); + } + pub fn drawGlyphs( self: *Font, glyphs: []const graphics.Glyph, diff --git a/src/font/face/coretext.zig b/src/font/face/coretext.zig index c15c68230..72c0a6d9e 100644 --- a/src/font/face/coretext.zig +++ b/src/font/face/coretext.zig @@ -102,6 +102,20 @@ pub const Face = struct { }; result.quirks_disable_default_font_features = quirks.disableDefaultFontFeatures(&result); + // If our presentation is emoji, we also check for the presence of + // emoji codepoints. This forces fonts with colorized glyphs that aren't + // emoji font to be treated as text. Long term, this isn't what we want + // but this fixes some bugs in the short term. See: + // https://github.com/mitchellh/ghostty/issues/1768 + // + // Longer term, we'd like to detect mixed color/non-color fonts and + // handle them correctly by rendering the color glyphs as color and the + // non-color glyphs as text. + if (result.presentation == .emoji and result.glyphIndex('🥸') == null) { + log.warn("font has colorized glyphs but isn't emoji, treating as text", .{}); + result.presentation = .text; + } + // In debug mode, we output information about available variation axes, // if they exist. if (comptime builtin.mode == .Debug) { @@ -700,3 +714,16 @@ test "variable set variation" { _ = try face.renderGlyph(alloc, &atlas, face.glyphIndex(i).?, .{}); } } + +test "mixed color/non-color font treated as text" { + const testing = std.testing; + const testFont = @import("../test.zig").fontJuliaMono; + + var lib = try font.Library.init(); + defer lib.deinit(); + + var face = try Face.init(lib, testFont, .{ .size = .{ .points = 12 } }); + defer face.deinit(); + + try testing.expect(face.presentation == .text); +} diff --git a/src/font/face/freetype.zig b/src/font/face/freetype.zig index 2860503b4..858f14fca 100644 --- a/src/font/face/freetype.zig +++ b/src/font/face/freetype.zig @@ -72,6 +72,12 @@ pub const Face = struct { }; result.quirks_disable_default_font_features = quirks.disableDefaultFontFeatures(&result); + // See coretext.zig which has a similar check for this. + if (result.presentation == .emoji and result.glyphIndex('🥸') == null) { + log.warn("font has colorized glyphs but isn't emoji, treating as text", .{}); + result.presentation = .text; + } + // In debug mode, we output information about available variation axes, // if they exist. if (comptime builtin.mode == .Debug) mm: { diff --git a/src/font/res/JuliaMono-Regular.ttf b/src/font/res/JuliaMono-Regular.ttf new file mode 100644 index 000000000..605230aa8 Binary files /dev/null and b/src/font/res/JuliaMono-Regular.ttf differ diff --git a/src/font/test.zig b/src/font/test.zig index 253d067b4..e5254b9a8 100644 --- a/src/font/test.zig +++ b/src/font/test.zig @@ -18,6 +18,7 @@ pub const fontNerdFont = @embedFile("res/JetBrainsMonoNerdFont-Regular.ttf"); /// Specific font families below: pub const fontGeistMono = @embedFile("res/GeistMono-Regular.ttf"); pub const fontJetBrainsMono = @embedFile("res/JetBrainsMonoNoNF-Regular.ttf"); +pub const fontJuliaMono = @embedFile("res/JuliaMono-Regular.ttf"); /// Cozette is a unique font because it embeds some emoji characters /// but has a text presentation. diff --git a/src/quirks.zig b/src/quirks.zig index e521eeb56..419294f5d 100644 --- a/src/quirks.zig +++ b/src/quirks.zig @@ -22,8 +22,11 @@ pub fn disableDefaultFontFeatures(face: *const font.Face) bool { // CodeNewRoman, Menlo and Monaco both have a default ligature of "fi" that // looks really bad in terminal grids, so we want to disable ligatures // by default for these faces. + // + // JuliaMono has a default ligature of "st" that looks bad. return std.mem.eql(u8, name, "CodeNewRoman") or std.mem.eql(u8, name, "CodeNewRoman Nerd Font") or + std.mem.eql(u8, name, "JuliaMono") or std.mem.eql(u8, name, "Menlo") or std.mem.eql(u8, name, "Monaco"); }