From 1a7cde9e3e61826cc863848d697079b8927aada0 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Mon, 27 May 2024 20:23:10 -0700 Subject: [PATCH 01/13] font/coretext: can read font tables --- pkg/macos/foundation/base.zig | 17 +++++++++++++++++ pkg/macos/foundation/data.zig | 6 +++++- pkg/macos/text/font.zig | 18 ++++++++++++++++++ src/font/face/coretext.zig | 32 ++++++++++++++++++++++++++++++++ 4 files changed, 72 insertions(+), 1 deletion(-) diff --git a/pkg/macos/foundation/base.zig b/pkg/macos/foundation/base.zig index bfd436115..24673ea2f 100644 --- a/pkg/macos/foundation/base.zig +++ b/pkg/macos/foundation/base.zig @@ -16,3 +16,20 @@ pub const Range = extern struct { return @bitCast(c.CFRangeMake(@intCast(loc), @intCast(len))); } }; + +pub const FourCharCode = packed struct(u32) { + d: u8, + c: u8, + b: u8, + a: u8, + + pub fn init(v: *const [4]u8) FourCharCode { + return .{ .a = v[0], .b = v[1], .c = v[2], .d = v[3] }; + } + + /// Converts the ID to a string. The return value is only valid + /// for the lifetime of the self pointer. + pub fn str(self: FourCharCode) [4]u8 { + return .{ self.a, self.b, self.c, self.d }; + } +}; diff --git a/pkg/macos/foundation/data.zig b/pkg/macos/foundation/data.zig index 7c9b7d6cb..cbac0db54 100644 --- a/pkg/macos/foundation/data.zig +++ b/pkg/macos/foundation/data.zig @@ -20,9 +20,13 @@ pub const Data = opaque { foundation.CFRelease(self); } - pub fn getPointer(self: *Data) *const anyopaque { + pub fn getPointer(self: *Data) [*]const u8 { return @ptrCast(c.CFDataGetBytePtr(@ptrCast(self))); } + + pub fn getLength(self: *Data) usize { + return @intCast(c.CFDataGetLength(@ptrCast(self))); + } }; test { diff --git a/pkg/macos/text/font.zig b/pkg/macos/text/font.zig index f4db72d6c..10f8c23ca 100644 --- a/pkg/macos/text/font.zig +++ b/pkg/macos/text/font.zig @@ -67,6 +67,14 @@ pub const Font = opaque { return @ptrCast(@constCast(c.CTFontCopyDefaultCascadeListForLanguages(@ptrCast(self), null))); } + pub fn copyTable(self: *Font, tag: FontTableTag) ?*foundation.Data { + return @constCast(@ptrCast(c.CTFontCopyTable( + @ptrCast(self), + @intFromEnum(tag), + c.kCTFontTableOptionNoOptions, + ))); + } + pub fn getGlyphCount(self: *Font) usize { return @intCast(c.CTFontGetGlyphCount(@ptrCast(self))); } @@ -195,6 +203,16 @@ pub const FontOrientation = enum(c_uint) { vertical = c.kCTFontOrientationVertical, }; +pub const FontTableTag = enum(u32) { + svg = c.kCTFontTableSVG, + _, + + pub fn init(v: *const [4]u8) FontTableTag { + const raw: u32 = @bitCast(foundation.FourCharCode.init(v)); + return @enumFromInt(raw); + } +}; + test { const testing = std.testing; diff --git a/src/font/face/coretext.zig b/src/font/face/coretext.zig index 72c0a6d9e..fdc063b1c 100644 --- a/src/font/face/coretext.zig +++ b/src/font/face/coretext.zig @@ -570,6 +570,21 @@ pub const Face = struct { return result; } + + /// Copy the font table data for the given tag. + pub fn copyTable(self: Face, alloc: Allocator, tag: *const [4]u8) !?[]u8 { + const data = self.font.copyTable(macos.text.FontTableTag.init(tag)) orelse + return null; + defer data.release(); + + const buf = try alloc.alloc(u8, data.getLength()); + errdefer alloc.free(buf); + + const ptr = data.getPointer(); + @memcpy(buf, ptr[0..buf.len]); + + return buf; + } }; test { @@ -727,3 +742,20 @@ test "mixed color/non-color font treated as text" { try testing.expect(face.presentation == .text); } + +test "svg font table" { + const testing = std.testing; + const alloc = testing.allocator; + 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(); + + const table = (try face.copyTable(alloc, "SVG ")).?; + defer alloc.free(table); + + try testing.expect(table.len > 0); +} From 9f885ff64fc40253b666ef9fa0bb62720190c7d8 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Mon, 27 May 2024 21:34:45 -0700 Subject: [PATCH 02/13] font/opentype: add SVG table parser, membership check --- src/font/main.zig | 1 + src/font/opentype.zig | 7 +++ src/font/opentype/svg.zig | 108 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 116 insertions(+) create mode 100644 src/font/opentype.zig create mode 100644 src/font/opentype/svg.zig diff --git a/src/font/main.zig b/src/font/main.zig index a287d9a06..b9adf5cbf 100644 --- a/src/font/main.zig +++ b/src/font/main.zig @@ -12,6 +12,7 @@ pub const DeferredFace = @import("DeferredFace.zig"); pub const Face = face.Face; pub const Glyph = @import("Glyph.zig"); pub const Metrics = face.Metrics; +pub const opentype = @import("opentype.zig"); pub const shape = @import("shape.zig"); pub const Shaper = shape.Shaper; pub const ShaperCache = shape.Cache; diff --git a/src/font/opentype.zig b/src/font/opentype.zig new file mode 100644 index 000000000..798df5b2c --- /dev/null +++ b/src/font/opentype.zig @@ -0,0 +1,7 @@ +const svg = @import("opentype/svg.zig"); + +pub const SVG = svg.SVG; + +test { + @import("std").testing.refAllDecls(@This()); +} diff --git a/src/font/opentype/svg.zig b/src/font/opentype/svg.zig new file mode 100644 index 000000000..4afffe7f1 --- /dev/null +++ b/src/font/opentype/svg.zig @@ -0,0 +1,108 @@ +const std = @import("std"); +const assert = std.debug.assert; +const font = @import("../main.zig"); + +/// SVG glyphs description table: +/// +/// References: +/// - https://www.w3.org/2013/10/SVG_in_OpenType/#thesvg +/// - https://learn.microsoft.com/en-us/typography/opentype/spec/svg +pub const SVG = struct { + /// The start and end glyph IDs (inclusive) that are present in the + /// table. This is used to very quickly include/exclude a glyph from + /// the table. + start_glyph_id: u16, + end_glyph_id: u16, + + /// All records in the table. + records: []const [12]u8, + + pub fn init(data: []const u8) !SVG { + var fbs = std.io.fixedBufferStream(data); + const reader = fbs.reader(); + + // Version + if (try reader.readInt(u16, .big) != 0) { + return error.SVGVersionNotSupported; + } + + // Offset + const offset = try reader.readInt(u32, .big); + + // Seek to the offset to get our document list + try fbs.seekTo(offset); + + // Get our document records along with the start/end glyph range. + const len = try reader.readInt(u16, .big); + const records: [*]const [12]u8 = @ptrCast(data[try fbs.getPos()..]); + const start_range = try glyphRange(&records[0]); + const end_range = if (len == 1) start_range else try glyphRange(&records[(len - 1)]); + + return .{ + .start_glyph_id = start_range[0], + .end_glyph_id = end_range[1], + .records = records[0..len], + }; + } + + pub fn hasGlyph(self: SVG, glyph_id: u16) bool { + // Fast path: outside the table range + if (glyph_id < self.start_glyph_id or glyph_id > self.end_glyph_id) { + return false; + } + + // Fast path, matches the start/end glyph IDs + if (glyph_id == self.start_glyph_id or glyph_id == self.end_glyph_id) { + return true; + } + + // Slow path: binary search our records + return std.sort.binarySearch( + [12]u8, + glyph_id, + self.records, + {}, + compareGlyphId, + ) != null; + } + + fn compareGlyphId(_: void, glyph_id: u16, record: [12]u8) std.math.Order { + const start, const end = glyphRange(&record) catch return .lt; + if (glyph_id < start) { + return .lt; + } else if (glyph_id > end) { + return .gt; + } else { + return .eq; + } + } + + fn glyphRange(record: []const u8) !struct { u16, u16 } { + var fbs = std.io.fixedBufferStream(record); + const reader = fbs.reader(); + return .{ + try reader.readInt(u16, .big), + try reader.readInt(u16, .big), + }; + } +}; + +test "SVG" { + const testing = std.testing; + const alloc = testing.allocator; + const testFont = @import("../test.zig").fontJuliaMono; + + var lib = try font.Library.init(); + defer lib.deinit(); + + var face = try font.Face.init(lib, testFont, .{ .size = .{ .points = 12 } }); + defer face.deinit(); + + const table = (try face.copyTable(alloc, "SVG ")).?; + defer alloc.free(table); + + const svg = try SVG.init(table); + try testing.expectEqual(11482, svg.start_glyph_id); + try testing.expectEqual(11482, svg.end_glyph_id); + try testing.expect(svg.hasGlyph(11482)); +} From 8920f45fd88ccd6424cb6f46838ce11863316fa5 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Tue, 28 May 2024 09:48:01 -0700 Subject: [PATCH 03/13] font/freetype: API to load font table --- pkg/freetype/face.zig | 27 +++++++++++++++++++++++++++ pkg/freetype/main.zig | 1 + pkg/freetype/tag.zig | 17 +++++++++++++++++ src/font/face/freetype.zig | 21 +++++++++++++++++++++ 4 files changed, 66 insertions(+) create mode 100644 pkg/freetype/tag.zig diff --git a/pkg/freetype/face.zig b/pkg/freetype/face.zig index 2bd4a33c7..7b72d1b94 100644 --- a/pkg/freetype/face.zig +++ b/pkg/freetype/face.zig @@ -1,7 +1,9 @@ const std = @import("std"); +const Allocator = std.mem.Allocator; const c = @import("c.zig"); const errors = @import("errors.zig"); const Library = @import("Library.zig"); +const Tag = @import("tag.zig").Tag; const Error = errors.Error; const intToError = errors.intToError; @@ -102,6 +104,31 @@ pub const Face = struct { return if (intToError(res)) |_| name else |err| err; } + /// Load any SFNT font table into client memory. + pub fn loadSfntTable( + self: Face, + alloc: Allocator, + tag: Tag, + ) (Allocator.Error || Error)!?[]u8 { + const tag_u64: u64 = @intCast(@as(u32, @bitCast(tag))); + + // Get the length of the table in bytes + var len: c_ulong = 0; + var res = c.FT_Load_Sfnt_Table(self.handle, tag_u64, 0, null, &len); + _ = intToError(res) catch |err| return err; + + // If our length is zero we don't have a table. + if (len == 0) return null; + + // Allocate a buffer to hold the table and load it + const buf = try alloc.alloc(u8, len); + errdefer alloc.free(buf); + res = c.FT_Load_Sfnt_Table(self.handle, tag_u64, 0, buf.ptr, &len); + _ = intToError(res) catch |err| return err; + + return buf; + } + /// Retrieve the font variation descriptor for a font. pub fn getMMVar(self: Face) Error!*c.FT_MM_Var { var result: *c.FT_MM_Var = undefined; diff --git a/pkg/freetype/main.zig b/pkg/freetype/main.zig index 4adfeeaf4..bfa5e6bc1 100644 --- a/pkg/freetype/main.zig +++ b/pkg/freetype/main.zig @@ -4,6 +4,7 @@ pub const Library = @import("Library.zig"); pub usingnamespace @import("computations.zig"); pub usingnamespace @import("errors.zig"); pub usingnamespace @import("face.zig"); +pub usingnamespace @import("tag.zig"); test { @import("std").testing.refAllDecls(@This()); diff --git a/pkg/freetype/tag.zig b/pkg/freetype/tag.zig new file mode 100644 index 000000000..32dab2a01 --- /dev/null +++ b/pkg/freetype/tag.zig @@ -0,0 +1,17 @@ +/// FT_Tag +pub const Tag = packed struct(u32) { + d: u8, + c: u8, + b: u8, + a: u8, + + pub fn init(v: *const [4]u8) Tag { + return .{ .a = v[0], .b = v[1], .c = v[2], .d = v[3] }; + } + + /// Converts the ID to a string. The return value is only valid + /// for the lifetime of the self pointer. + pub fn str(self: Tag) [4]u8 { + return .{ self.a, self.b, self.c, self.d }; + } +}; diff --git a/src/font/face/freetype.zig b/src/font/face/freetype.zig index 858f14fca..33eca002c 100644 --- a/src/font/face/freetype.zig +++ b/src/font/face/freetype.zig @@ -631,6 +631,11 @@ pub const Face = struct { const div = @as(f32, @floatFromInt(mul)) / 64; return @ceil(div); } + + /// Copy the font table data for the given tag. + pub fn copyTable(self: Face, alloc: Allocator, tag: *const [4]u8) !?[]u8 { + return try self.face.loadSfntTable(alloc, freetype.Tag.init(tag)); + } }; test { @@ -763,3 +768,19 @@ test "mono to rgba" { // glyph 3 is mono in Noto _ = try ft_font.renderGlyph(alloc, &atlas, 3, .{}); } + +test "svg font table" { + const alloc = testing.allocator; + 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(); + + const table = (try face.copyTable(alloc, "SVG ")).?; + defer alloc.free(table); + + try testing.expectEqual(430, table.len); +} From d22c645a0210a7392b3ba7086a412f1d609d6d83 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Tue, 28 May 2024 13:04:55 -0700 Subject: [PATCH 04/13] font/coretext: determine glyph colorization --- src/font/face.zig | 9 +++ src/font/face/coretext.zig | 125 ++++++++++++++++++++++++++++++++++--- src/font/opentype/svg.zig | 7 ++- 3 files changed, 133 insertions(+), 8 deletions(-) diff --git a/src/font/face.zig b/src/font/face.zig index 8bcfb8209..5971b5b33 100644 --- a/src/font/face.zig +++ b/src/font/face.zig @@ -48,6 +48,15 @@ pub const DesiredSize = struct { } }; +/// Glyph index into a face. +pub const GlyphIndex = struct { + /// The index in the face. + index: u32, + + /// True if the glyph is a colored glyph. + color: bool, +}; + /// A font variation setting. The best documentation for this I know of /// is actually the CSS font-variation-settings property on MDN: /// https://developer.mozilla.org/en-US/docs/Web/CSS/font-variation-settings diff --git a/src/font/face/coretext.zig b/src/font/face/coretext.zig index fdc063b1c..9d2477b6c 100644 --- a/src/font/face/coretext.zig +++ b/src/font/face/coretext.zig @@ -5,7 +5,9 @@ const Allocator = std.mem.Allocator; const macos = @import("macos"); const harfbuzz = @import("harfbuzz"); const font = @import("../main.zig"); +const opentype = @import("../opentype.zig"); const quirks = @import("../../quirks.zig"); +const GlyphIndex = font.face.GlyphIndex; const log = std.log.scoped(.font_face); @@ -26,6 +28,12 @@ pub const Face = struct { /// Set quirks.disableDefaultFontFeatures quirks_disable_default_font_features: bool = false, + /// If the face can possibly be colored, then this is the state + /// used to check for color information. This is null if the font + /// can't possibly be colored (i.e. doesn't have SVG, sbix, etc + /// tables). + color: ?ColorState = null, + /// True if our build is using Harfbuzz. If we're not, we can avoid /// some Harfbuzz-specific code paths. const harfbuzz_shaper = font.options.backend.hasHarfbuzz(); @@ -94,11 +102,18 @@ pub const Face = struct { } else {}; errdefer if (comptime harfbuzz_shaper) hb_font.destroy(); + const color: ?ColorState = if (traits.color_glyphs) + try ColorState.init(ct_font) + else + null; + errdefer if (color) |v| v.deinit(); + var result: Face = .{ .font = ct_font, .hb_font = hb_font, .presentation = if (traits.color_glyphs) .emoji else .text, .metrics = metrics, + .color = color, }; result.quirks_disable_default_font_features = quirks.disableDefaultFontFeatures(&result); @@ -167,6 +182,7 @@ pub const Face = struct { pub fn deinit(self: *Face) void { self.font.release(); if (comptime harfbuzz_shaper) self.hb_font.destroy(); + if (self.color) |v| v.deinit(); self.* = undefined; } @@ -228,7 +244,7 @@ pub const Face = struct { /// Returns the glyph index for the given Unicode code point. If this /// face doesn't support this glyph, null is returned. - pub fn glyphIndex(self: Face, cp: u32) ?u32 { + pub fn glyphIndex(self: Face, cp: u32) ?GlyphIndex { // Turn UTF-32 into UTF-16 for CT API var unichars: [2]u16 = undefined; const pair = macos.foundation.stringGetSurrogatePairForLongCharacter(cp, &unichars); @@ -243,7 +259,13 @@ pub const Face = struct { // to decode down into exactly one glyph ID. if (pair) assert(glyphs[1] == 0); - return @intCast(glyphs[0]); + // If we have colorization information, then check if this + // glyph is colorized. + + return .{ + .index = @intCast(glyphs[0]), + .color = if (self.color) |v| v.isColored(glyphs[0]) else false, + }; } pub fn renderGlyph( @@ -587,6 +609,69 @@ pub const Face = struct { } }; +const ColorState = struct { + /// True if there is an sbix font table. For now, the mere presence + /// of an sbix font table causes us to assume the glyph is colored. + /// We can improve this later. + sbix: bool, + + /// The SVG font table data (if any), which we can use to determine + /// if a glyph is present in the SVG table. + svg: ?opentype.SVG, + svg_data: ?*macos.foundation.Data, + + pub fn init(f: *macos.text.Font) !ColorState { + // sbix is true if the table exists in the font data at all. + // In the future we probably want to actually parse it and + // check for glyphs. + const sbix: bool = sbix: { + const tag = macos.text.FontTableTag.init("sbix"); + const data = f.copyTable(tag) orelse break :sbix false; + data.release(); + break :sbix data.getLength() > 0; + }; + + // Read the SVG table out of the font data. + const svg: ?struct { + svg: opentype.SVG, + data: *macos.foundation.Data, + } = svg: { + const tag = macos.text.FontTableTag.init("SVG "); + const data = f.copyTable(tag) orelse break :svg null; + errdefer data.release(); + const ptr = data.getPointer(); + const len = data.getLength(); + break :svg .{ + .svg = try opentype.SVG.init(ptr[0..len]), + .data = data, + }; + }; + + return .{ + .sbix = sbix, + .svg = if (svg) |v| v.svg else null, + .svg_data = if (svg) |v| v.data else null, + }; + } + + pub fn deinit(self: *const ColorState) void { + if (self.svg_data) |v| v.release(); + } + + /// Returns true if the given glyph ID is colored. + pub fn isColored(self: *const ColorState, glyph_id: u16) bool { + // sbix is always true for now + if (self.sbix) return true; + + // if we have svg data, check it + if (self.svg) |svg| { + if (svg.hasGlyph(glyph_id)) return true; + } + + return false; + } +}; + test { const testing = std.testing; const alloc = testing.allocator; @@ -610,7 +695,7 @@ test { var i: u8 = 32; while (i < 127) : (i += 1) { try testing.expect(face.glyphIndex(i) != null); - _ = try face.renderGlyph(alloc, &atlas, face.glyphIndex(i).?, .{}); + _ = try face.renderGlyph(alloc, &atlas, face.glyphIndex(i).?.index, .{}); } } @@ -651,7 +736,10 @@ test "emoji" { try testing.expectEqual(font.Presentation.emoji, face.presentation); // Glyph index check - try testing.expect(face.glyphIndex('🥸') != null); + { + const glyph = face.glyphIndex('🥸').?; + try testing.expect(glyph.color); + } } test "in-memory" { @@ -674,7 +762,7 @@ test "in-memory" { var i: u8 = 32; while (i < 127) : (i += 1) { try testing.expect(face.glyphIndex(i) != null); - _ = try face.renderGlyph(alloc, &atlas, face.glyphIndex(i).?, .{}); + _ = try face.renderGlyph(alloc, &atlas, face.glyphIndex(i).?.index, .{}); } } @@ -698,7 +786,7 @@ test "variable" { var i: u8 = 32; while (i < 127) : (i += 1) { try testing.expect(face.glyphIndex(i) != null); - _ = try face.renderGlyph(alloc, &atlas, face.glyphIndex(i).?, .{}); + _ = try face.renderGlyph(alloc, &atlas, face.glyphIndex(i).?.index, .{}); } } @@ -726,7 +814,7 @@ test "variable set variation" { var i: u8 = 32; while (i < 127) : (i += 1) { try testing.expect(face.glyphIndex(i) != null); - _ = try face.renderGlyph(alloc, &atlas, face.glyphIndex(i).?, .{}); + _ = try face.renderGlyph(alloc, &atlas, face.glyphIndex(i).?.index, .{}); } } @@ -759,3 +847,26 @@ test "svg font table" { try testing.expect(table.len > 0); } + +test "glyphIndex colored vs 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(); + + { + const glyph = face.glyphIndex('A').?; + try testing.expectEqual(4, glyph.index); + try testing.expectEqual(false, glyph.color); + } + + { + const glyph = face.glyphIndex(0xE800).?; + try testing.expectEqual(11482, glyph.index); + try testing.expectEqual(true, glyph.color); + } +} diff --git a/src/font/opentype/svg.zig b/src/font/opentype/svg.zig index 4afffe7f1..985e58bec 100644 --- a/src/font/opentype/svg.zig +++ b/src/font/opentype/svg.zig @@ -2,7 +2,12 @@ const std = @import("std"); const assert = std.debug.assert; const font = @import("../main.zig"); -/// SVG glyphs description table: +/// SVG glyphs description table. +/// +/// This struct is focused purely on the operations we need for Ghostty, +/// namely to be able to look up whether an glyph ID is present in the SVG +/// table or not. This struct isn't meant to be a general purpose SVG table +/// reader. /// /// References: /// - https://www.w3.org/2013/10/SVG_in_OpenType/#thesvg From dc6b1b0b7a6c3d3f92123b80bbe8bcda39a9757a Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Tue, 28 May 2024 13:20:37 -0700 Subject: [PATCH 05/13] font/coretext: hasColor/isColored --- src/font/CodepointResolver.zig | 5 ++++- src/font/face/coretext.zig | 40 +++++++++++++++++++++------------- src/font/main.zig | 2 +- 3 files changed, 30 insertions(+), 17 deletions(-) diff --git a/src/font/CodepointResolver.zig b/src/font/CodepointResolver.zig index 543378c4f..b15bbcb27 100644 --- a/src/font/CodepointResolver.zig +++ b/src/font/CodepointResolver.zig @@ -291,7 +291,10 @@ fn getIndexCodepointOverride( /// Returns the presentation for a specific font index. This is useful for /// determining what atlas is needed. -pub fn getPresentation(self: *CodepointResolver, index: Collection.Index) !Presentation { +pub fn getPresentation( + self: *CodepointResolver, + index: Collection.Index, +) !Presentation { if (index.special()) |sp| return switch (sp) { .sprite => .text, }; diff --git a/src/font/face/coretext.zig b/src/font/face/coretext.zig index 9d2477b6c..306b6e5be 100644 --- a/src/font/face/coretext.zig +++ b/src/font/face/coretext.zig @@ -242,9 +242,22 @@ pub const Face = struct { self.* = face; } + /// Returns true if the face has any glyphs that are colorized. + /// To determine if an individual glyph is colorized you must use + /// isColored. + pub fn hasColor(self: *const Face) bool { + return self.color != null; + } + + /// Returns true if the given glyph ID is colorized. + pub fn isColored(self: *const Face, glyph_id: u16) bool { + const c = self.color orelse return false; + return c.isColored(glyph_id); + } + /// Returns the glyph index for the given Unicode code point. If this /// face doesn't support this glyph, null is returned. - pub fn glyphIndex(self: Face, cp: u32) ?GlyphIndex { + pub fn glyphIndex(self: Face, cp: u32) ?u16 { // Turn UTF-32 into UTF-16 for CT API var unichars: [2]u16 = undefined; const pair = macos.foundation.stringGetSurrogatePairForLongCharacter(cp, &unichars); @@ -262,10 +275,7 @@ pub const Face = struct { // If we have colorization information, then check if this // glyph is colorized. - return .{ - .index = @intCast(glyphs[0]), - .color = if (self.color) |v| v.isColored(glyphs[0]) else false, - }; + return @intCast(glyphs[0]); } pub fn renderGlyph( @@ -695,7 +705,7 @@ test { var i: u8 = 32; while (i < 127) : (i += 1) { try testing.expect(face.glyphIndex(i) != null); - _ = try face.renderGlyph(alloc, &atlas, face.glyphIndex(i).?.index, .{}); + _ = try face.renderGlyph(alloc, &atlas, face.glyphIndex(i).?, .{}); } } @@ -737,8 +747,8 @@ test "emoji" { // Glyph index check { - const glyph = face.glyphIndex('🥸').?; - try testing.expect(glyph.color); + const id = face.glyphIndex('🥸').?; + try testing.expect(face.isColored(id)); } } @@ -762,7 +772,7 @@ test "in-memory" { var i: u8 = 32; while (i < 127) : (i += 1) { try testing.expect(face.glyphIndex(i) != null); - _ = try face.renderGlyph(alloc, &atlas, face.glyphIndex(i).?.index, .{}); + _ = try face.renderGlyph(alloc, &atlas, face.glyphIndex(i).?, .{}); } } @@ -786,7 +796,7 @@ test "variable" { var i: u8 = 32; while (i < 127) : (i += 1) { try testing.expect(face.glyphIndex(i) != null); - _ = try face.renderGlyph(alloc, &atlas, face.glyphIndex(i).?.index, .{}); + _ = try face.renderGlyph(alloc, &atlas, face.glyphIndex(i).?, .{}); } } @@ -814,7 +824,7 @@ test "variable set variation" { var i: u8 = 32; while (i < 127) : (i += 1) { try testing.expect(face.glyphIndex(i) != null); - _ = try face.renderGlyph(alloc, &atlas, face.glyphIndex(i).?.index, .{}); + _ = try face.renderGlyph(alloc, &atlas, face.glyphIndex(i).?, .{}); } } @@ -860,13 +870,13 @@ test "glyphIndex colored vs text" { { const glyph = face.glyphIndex('A').?; - try testing.expectEqual(4, glyph.index); - try testing.expectEqual(false, glyph.color); + try testing.expectEqual(4, glyph); + try testing.expect(!face.isColored(glyph)); } { const glyph = face.glyphIndex(0xE800).?; - try testing.expectEqual(11482, glyph.index); - try testing.expectEqual(true, glyph.color); + try testing.expectEqual(11482, glyph); + try testing.expect(face.isColored(glyph)); } } diff --git a/src/font/main.zig b/src/font/main.zig index b9adf5cbf..cbcb696d2 100644 --- a/src/font/main.zig +++ b/src/font/main.zig @@ -160,7 +160,7 @@ pub const Style = enum(u3) { bold_italic = 3, }; -/// The presentation for a an emoji. +/// The presentation for an emoji. pub const Presentation = enum(u1) { text = 0, // U+FE0E emoji = 1, // U+FEOF From 326659c522536e9215afbc64feb9e6015d5e7fa8 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Tue, 28 May 2024 20:09:05 -0700 Subject: [PATCH 06/13] font: handle presentation at glyph layer --- src/font/CodepointResolver.zig | 3 +- src/font/Collection.zig | 35 ++++++++++++++---- src/font/DeferredFace.zig | 1 + src/font/SharedGrid.zig | 2 +- src/font/face/coretext.zig | 67 +++++++--------------------------- 5 files changed, 45 insertions(+), 63 deletions(-) diff --git a/src/font/CodepointResolver.zig b/src/font/CodepointResolver.zig index b15bbcb27..e695e5a74 100644 --- a/src/font/CodepointResolver.zig +++ b/src/font/CodepointResolver.zig @@ -294,13 +294,14 @@ fn getIndexCodepointOverride( pub fn getPresentation( self: *CodepointResolver, index: Collection.Index, + glyph_index: u32, ) !Presentation { if (index.special()) |sp| return switch (sp) { .sprite => .text, }; const face = try self.collection.getFace(index); - return face.presentation; + return if (face.isColorGlyph(glyph_index)) .emoji else .text; } /// Render a glyph by glyph index into the given font atlas and return diff --git a/src/font/Collection.zig b/src/font/Collection.zig index 38f4b92f8..86231b839 100644 --- a/src/font/Collection.zig +++ b/src/font/Collection.zig @@ -190,16 +190,23 @@ pub fn autoItalicize(self: *Collection, alloc: Allocator) !void { const list = self.faces.get(.regular); if (list.items.len == 0) return; - // Find our first font that is text. This will force - // loading any deferred faces but we only load them until - // we find a text face. A text face is almost always the - // first face in the list. + // Find our first regular face that has text glyphs. for (0..list.items.len) |i| { const face = try self.getFace(.{ .style = .regular, .idx = @intCast(i), }); - if (face.presentation == .text) break :regular face; + + // We have two conditionals here. The color check is obvious: + // we want to auto-italicize a normal text font. The second + // check is less obvious... for mixed color/non-color fonts, we + // accept the regular font if it has basic ASCII. This may not + // be strictly correct (especially with international fonts) but + // it's a reasonable heuristic and the first case will match 99% + // of the time. + if (!face.hasColor() or face.glyphIndex('A') != null) { + break :regular face; + } } // No regular text face found. @@ -344,7 +351,13 @@ pub const Entry = union(enum) { }, .loaded => |face| switch (p_mode) { - .explicit => |p| face.presentation == p and face.glyphIndex(cp) != null, + .explicit => |p| explicit: { + const index = face.glyphIndex(cp) orelse break :explicit false; + break :explicit switch (p) { + .text => !face.isColorGlyph(index), + .emoji => face.isColorGlyph(index), + }; + }, .default, .any => face.glyphIndex(cp) != null, }, @@ -357,7 +370,13 @@ pub const Entry = union(enum) { .fallback_loaded => |face| switch (p_mode) { .explicit, .default, - => |p| face.presentation == p and face.glyphIndex(cp) != null, + => |p| explicit: { + const index = face.glyphIndex(cp) orelse break :explicit false; + break :explicit switch (p) { + .text => !face.isColorGlyph(index), + .emoji => face.isColorGlyph(index), + }; + }, .any => face.glyphIndex(cp) != null, }, }; @@ -371,7 +390,7 @@ pub const PresentationMode = union(enum) { explicit: Presentation, /// The codepoint has no explicit presentation and we should use - /// the presentation from the UCd. + /// the presentation from the UCD. default: Presentation, /// The codepoint can be any presentation. diff --git a/src/font/DeferredFace.zig b/src/font/DeferredFace.zig index 8051895a4..94fbab445 100644 --- a/src/font/DeferredFace.zig +++ b/src/font/DeferredFace.zig @@ -281,6 +281,7 @@ pub fn hasCodepoint(self: DeferredFace, cp: u32, p: ?Presentation) bool { => { // If we are using coretext, we check the loaded CT font. if (self.ct) |ct| { + // TODO(mixed-fonts): handle presentation on a glyph level if (p) |desired_p| { const traits = ct.font.getSymbolicTraits(); const actual_p: Presentation = if (traits.color_glyphs) .emoji else .text; diff --git a/src/font/SharedGrid.zig b/src/font/SharedGrid.zig index 9c0f5ca9c..e99d39078 100644 --- a/src/font/SharedGrid.zig +++ b/src/font/SharedGrid.zig @@ -253,7 +253,7 @@ pub fn renderGlyph( if (gop.found_existing) return gop.value_ptr.*; // Get the presentation to determine what atlas to use - const p = try self.resolver.getPresentation(index); + const p = try self.resolver.getPresentation(index, glyph_index); const atlas: *font.Atlas = switch (p) { .text => &self.atlas_greyscale, .emoji => &self.atlas_color, diff --git a/src/font/face/coretext.zig b/src/font/face/coretext.zig index 306b6e5be..4618ad0b7 100644 --- a/src/font/face/coretext.zig +++ b/src/font/face/coretext.zig @@ -19,9 +19,6 @@ pub const Face = struct { /// if we're using Harfbuzz. hb_font: if (harfbuzz_shaper) harfbuzz.Font else void, - /// The presentation for this font. - presentation: font.Presentation, - /// Metrics for this font face. These are useful for renderers. metrics: font.face.Metrics, @@ -111,26 +108,11 @@ pub const Face = struct { var result: Face = .{ .font = ct_font, .hb_font = hb_font, - .presentation = if (traits.color_glyphs) .emoji else .text, .metrics = metrics, .color = color, }; 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) { @@ -244,15 +226,15 @@ pub const Face = struct { /// Returns true if the face has any glyphs that are colorized. /// To determine if an individual glyph is colorized you must use - /// isColored. + /// isColorGlyph. pub fn hasColor(self: *const Face) bool { return self.color != null; } /// Returns true if the given glyph ID is colorized. - pub fn isColored(self: *const Face, glyph_id: u16) bool { + pub fn isColorGlyph(self: *const Face, glyph_id: u32) bool { const c = self.color orelse return false; - return c.isColored(glyph_id); + return c.isColorGlyph(glyph_id); } /// Returns the glyph index for the given Unicode code point. If this @@ -328,7 +310,7 @@ pub const Face = struct { depth: u32, space: *macos.graphics.ColorSpace, context_opts: c_uint, - } = if (self.presentation == .text) .{ + } = if (!self.isColorGlyph(glyph_index)) .{ .color = false, .depth = 1, .space = try macos.graphics.ColorSpace.createDeviceGray(), @@ -669,13 +651,18 @@ const ColorState = struct { } /// Returns true if the given glyph ID is colored. - pub fn isColored(self: *const ColorState, glyph_id: u16) bool { + pub fn isColorGlyph(self: *const ColorState, glyph_id: u32) bool { + // Our font system uses 32-bit glyph IDs for special values but + // actual fonts only contain 16-bit glyph IDs so if we can't cast + // into it it must be false. + const glyph_u16 = std.math.cast(u16, glyph_id) orelse return false; + // sbix is always true for now if (self.sbix) return true; // if we have svg data, check it if (self.svg) |svg| { - if (svg.hasGlyph(glyph_id)) return true; + if (svg.hasGlyph(glyph_u16)) return true; } return false; @@ -699,8 +686,6 @@ test { var face = try Face.initFontCopy(ct_font, .{ .size = .{ .points = 12 } }); defer face.deinit(); - try testing.expectEqual(font.Presentation.text, face.presentation); - // Generate all visible ASCII var i: u8 = 32; while (i < 127) : (i += 1) { @@ -722,8 +707,6 @@ test "name" { var face = try Face.initFontCopy(ct_font, .{ .size = .{ .points = 12 } }); defer face.deinit(); - try testing.expectEqual(font.Presentation.text, face.presentation); - var buf: [1024]u8 = undefined; const font_name = try face.name(&buf); try testing.expect(std.mem.eql(u8, font_name, "Menlo")); @@ -742,13 +725,10 @@ test "emoji" { var face = try Face.initFontCopy(ct_font, .{ .size = .{ .points = 18 } }); defer face.deinit(); - // Presentation - try testing.expectEqual(font.Presentation.emoji, face.presentation); - // Glyph index check { const id = face.glyphIndex('🥸').?; - try testing.expect(face.isColored(id)); + try testing.expect(face.isColorGlyph(id)); } } @@ -766,8 +746,6 @@ test "in-memory" { var face = try Face.init(lib, testFont, .{ .size = .{ .points = 12 } }); defer face.deinit(); - try testing.expectEqual(font.Presentation.text, face.presentation); - // Generate all visible ASCII var i: u8 = 32; while (i < 127) : (i += 1) { @@ -790,8 +768,6 @@ test "variable" { var face = try Face.init(lib, testFont, .{ .size = .{ .points = 12 } }); defer face.deinit(); - try testing.expectEqual(font.Presentation.text, face.presentation); - // Generate all visible ASCII var i: u8 = 32; while (i < 127) : (i += 1) { @@ -814,8 +790,6 @@ test "variable set variation" { var face = try Face.init(lib, testFont, .{ .size = .{ .points = 12 } }); defer face.deinit(); - try testing.expectEqual(font.Presentation.text, face.presentation); - try face.setVariations(&.{ .{ .id = font.face.Variation.Id.init("wght"), .value = 400 }, }, .{ .size = .{ .points = 12 } }); @@ -828,19 +802,6 @@ test "variable set variation" { } } -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); -} - test "svg font table" { const testing = std.testing; const alloc = testing.allocator; @@ -871,12 +832,12 @@ test "glyphIndex colored vs text" { { const glyph = face.glyphIndex('A').?; try testing.expectEqual(4, glyph); - try testing.expect(!face.isColored(glyph)); + try testing.expect(!face.isColorGlyph(glyph)); } { const glyph = face.glyphIndex(0xE800).?; try testing.expectEqual(11482, glyph); - try testing.expect(face.isColored(glyph)); + try testing.expect(face.isColorGlyph(glyph)); } } From 4daa49fe27c5aea0ebfec6fde757fa0e529760ec Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Tue, 28 May 2024 20:25:49 -0700 Subject: [PATCH 07/13] font/freetype: update to new presentation APIs --- pkg/freetype/face.zig | 6 ++++++ src/font/face/freetype.zig | 37 ++++++++++++++++++++----------------- 2 files changed, 26 insertions(+), 17 deletions(-) diff --git a/pkg/freetype/face.zig b/pkg/freetype/face.zig index 7b72d1b94..f4eaf24f0 100644 --- a/pkg/freetype/face.zig +++ b/pkg/freetype/face.zig @@ -26,6 +26,12 @@ pub const Face = struct { return c.FT_HAS_COLOR(self.handle); } + /// A macro that returns true whenever a face object contains an ‘sbix’ + /// OpenType table and outline glyphs. + pub fn hasSBIX(self: Face) bool { + return c.FT_HAS_SBIX(self.handle); + } + /// A macro that returns true whenever a face object contains some /// multiple masters. pub fn hasMultipleMasters(self: Face) bool { diff --git a/src/font/face/freetype.zig b/src/font/face/freetype.zig index 33eca002c..6037f6c0c 100644 --- a/src/font/face/freetype.zig +++ b/src/font/face/freetype.zig @@ -15,7 +15,6 @@ const Allocator = std.mem.Allocator; const font = @import("../main.zig"); const Glyph = font.Glyph; const Library = font.Library; -const Presentation = font.Presentation; const convert = @import("freetype_convert.zig"); const fastmem = @import("../../fastmem.zig"); const quirks = @import("../../quirks.zig"); @@ -32,10 +31,6 @@ pub const Face = struct { /// Harfbuzz font corresponding to this face. hb_font: harfbuzz.Font, - /// The presentation for this font. This is a heuristic since fonts don't have - /// a way to declare this. We just assume a font with color is an emoji font. - presentation: Presentation, - /// Metrics for this font face. These are useful for renderers. metrics: font.face.Metrics, @@ -67,17 +62,10 @@ pub const Face = struct { .lib = lib.lib, .face = face, .hb_font = hb_font, - .presentation = if (face.hasColor()) .emoji else .text, .metrics = calcMetrics(face, opts.metric_modifiers), }; 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: { @@ -219,10 +207,29 @@ pub const Face = struct { /// Returns true if this font is colored. This can be used by callers to /// determine what kind of atlas to pass in. - fn hasColor(self: Face) bool { + pub fn hasColor(self: Face) bool { return self.face.hasColor(); } + /// Returns true if the given glyph ID is colorized. + pub fn isColorGlyph(self: *const Face, glyph_id: u32) bool { + // sbix table is always true for now + if (self.face.hasSBIX()) return true; + + // Otherwise, load the glyph and see what format it is in. + self.face.loadGlyph(glyph_id, .{ + .render = true, + .color = self.face.hasColor(), + .no_bitmap = !self.face.hasColor(), + }) catch return false; + + // If the glyph is SVG we assume colorized + const glyph = self.face.handle.*.glyph; + if (glyph.*.format == freetype.c.FT_GLYPH_FORMAT_SVG) return true; + + return false; + } + /// Render a glyph using the glyph index. The rendered glyph is stored in the /// given texture atlas. pub fn renderGlyph( @@ -655,8 +662,6 @@ test { ); defer ft_font.deinit(); - try testing.expectEqual(Presentation.text, ft_font.presentation); - // Generate all visible ASCII var i: u8 = 32; while (i < 127) : (i += 1) { @@ -691,8 +696,6 @@ test "color emoji" { ); defer ft_font.deinit(); - try testing.expectEqual(Presentation.emoji, ft_font.presentation); - _ = try ft_font.renderGlyph(alloc, &atlas, ft_font.glyphIndex('🥸').?, .{}); // resize From adaeffb2b53477ed8edfbf8cf9ff52cd0116e8a7 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Tue, 28 May 2024 20:40:46 -0700 Subject: [PATCH 08/13] font/freetype: CBDT/CBLC tables imply color --- pkg/freetype/face.zig | 9 +++++++++ src/font/face/freetype.zig | 12 ++++++++++++ 2 files changed, 21 insertions(+) diff --git a/pkg/freetype/face.zig b/pkg/freetype/face.zig index f4eaf24f0..e7dc7b0fa 100644 --- a/pkg/freetype/face.zig +++ b/pkg/freetype/face.zig @@ -135,6 +135,15 @@ pub const Face = struct { return buf; } + /// Check whether a given SFNT table is available in a face. + pub fn hasSfntTable(self: Face, tag: Tag) bool { + const tag_u64: u64 = @intCast(@as(u32, @bitCast(tag))); + var len: c_ulong = 0; + const res = c.FT_Load_Sfnt_Table(self.handle, tag_u64, 0, null, &len); + _ = intToError(res) catch return false; + return len != 0; + } + /// Retrieve the font variation descriptor for a font. pub fn getMMVar(self: Face) Error!*c.FT_MM_Var { var result: *c.FT_MM_Var = undefined; diff --git a/src/font/face/freetype.zig b/src/font/face/freetype.zig index 6037f6c0c..346d8aa8d 100644 --- a/src/font/face/freetype.zig +++ b/src/font/face/freetype.zig @@ -216,6 +216,11 @@ pub const Face = struct { // sbix table is always true for now if (self.face.hasSBIX()) return true; + // CBDT/CBLC tables always imply colorized glyphs. + // These are used by Noto. + if (self.face.hasSfntTable(freetype.Tag.init("CBDT"))) return true; + if (self.face.hasSfntTable(freetype.Tag.init("CBLC"))) return true; + // Otherwise, load the glyph and see what format it is in. self.face.loadGlyph(glyph_id, .{ .render = true, @@ -698,6 +703,13 @@ test "color emoji" { _ = try ft_font.renderGlyph(alloc, &atlas, ft_font.glyphIndex('🥸').?, .{}); + // Make sure this glyph has color + { + try testing.expect(ft_font.hasColor()); + const glyph_id = ft_font.glyphIndex('🥸').?; + try testing.expect(ft_font.isColorGlyph(glyph_id)); + } + // resize { const glyph = try ft_font.renderGlyph(alloc, &atlas, ft_font.glyphIndex('🥸').?, .{ From a205e1e2c1ad8b4e8c6e1ce92f374f2af5b12123 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Tue, 28 May 2024 20:49:21 -0700 Subject: [PATCH 09/13] pkg/freetype: use c_ulong which isn't 64-bit on windows --- pkg/freetype/face.zig | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/pkg/freetype/face.zig b/pkg/freetype/face.zig index e7dc7b0fa..94c3e2931 100644 --- a/pkg/freetype/face.zig +++ b/pkg/freetype/face.zig @@ -116,11 +116,11 @@ pub const Face = struct { alloc: Allocator, tag: Tag, ) (Allocator.Error || Error)!?[]u8 { - const tag_u64: u64 = @intCast(@as(u32, @bitCast(tag))); + const tag_c: c_ulong = @intCast(@as(u32, @bitCast(tag))); // Get the length of the table in bytes var len: c_ulong = 0; - var res = c.FT_Load_Sfnt_Table(self.handle, tag_u64, 0, null, &len); + var res = c.FT_Load_Sfnt_Table(self.handle, tag_c, 0, null, &len); _ = intToError(res) catch |err| return err; // If our length is zero we don't have a table. @@ -129,7 +129,7 @@ pub const Face = struct { // Allocate a buffer to hold the table and load it const buf = try alloc.alloc(u8, len); errdefer alloc.free(buf); - res = c.FT_Load_Sfnt_Table(self.handle, tag_u64, 0, buf.ptr, &len); + res = c.FT_Load_Sfnt_Table(self.handle, tag_c, 0, buf.ptr, &len); _ = intToError(res) catch |err| return err; return buf; @@ -137,9 +137,9 @@ pub const Face = struct { /// Check whether a given SFNT table is available in a face. pub fn hasSfntTable(self: Face, tag: Tag) bool { - const tag_u64: u64 = @intCast(@as(u32, @bitCast(tag))); + const tag_c: c_ulong = @intCast(@as(u32, @bitCast(tag))); var len: c_ulong = 0; - const res = c.FT_Load_Sfnt_Table(self.handle, tag_u64, 0, null, &len); + const res = c.FT_Load_Sfnt_Table(self.handle, tag_c, 0, null, &len); _ = intToError(res) catch return false; return len != 0; } From 6a8dc12ae146cf2d6890d7378bf60c6191a21e10 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Tue, 28 May 2024 20:54:05 -0700 Subject: [PATCH 10/13] font: remove deferred face todo, note why --- src/font/DeferredFace.zig | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/font/DeferredFace.zig b/src/font/DeferredFace.zig index 94fbab445..fc7c81378 100644 --- a/src/font/DeferredFace.zig +++ b/src/font/DeferredFace.zig @@ -281,7 +281,12 @@ pub fn hasCodepoint(self: DeferredFace, cp: u32, p: ?Presentation) bool { => { // If we are using coretext, we check the loaded CT font. if (self.ct) |ct| { - // TODO(mixed-fonts): handle presentation on a glyph level + // This presentation check isn't as detailed as isColorGlyph + // because forced presentation modes are only used for emoji and + // emoji should always have color glyphs set. This can be + // more correct by using the isColorGlyph logic but I'd want + // to find a font that actualy requires this so we can write + // a test for it before changing it. if (p) |desired_p| { const traits = ct.font.getSymbolicTraits(); const actual_p: Presentation = if (traits.color_glyphs) .emoji else .text; From 9a628d8a8e385b3210464992d19ce371b60c897e Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Tue, 28 May 2024 20:56:47 -0700 Subject: [PATCH 11/13] font: remove unused structs --- src/font/face.zig | 9 --------- src/font/face/coretext.zig | 1 - 2 files changed, 10 deletions(-) diff --git a/src/font/face.zig b/src/font/face.zig index 5971b5b33..8bcfb8209 100644 --- a/src/font/face.zig +++ b/src/font/face.zig @@ -48,15 +48,6 @@ pub const DesiredSize = struct { } }; -/// Glyph index into a face. -pub const GlyphIndex = struct { - /// The index in the face. - index: u32, - - /// True if the glyph is a colored glyph. - color: bool, -}; - /// A font variation setting. The best documentation for this I know of /// is actually the CSS font-variation-settings property on MDN: /// https://developer.mozilla.org/en-US/docs/Web/CSS/font-variation-settings diff --git a/src/font/face/coretext.zig b/src/font/face/coretext.zig index 4618ad0b7..0abf614cb 100644 --- a/src/font/face/coretext.zig +++ b/src/font/face/coretext.zig @@ -7,7 +7,6 @@ const harfbuzz = @import("harfbuzz"); const font = @import("../main.zig"); const opentype = @import("../opentype.zig"); const quirks = @import("../../quirks.zig"); -const GlyphIndex = font.face.GlyphIndex; const log = std.log.scoped(.font_face); From f6e708c0fb04d89b9705bc6e20131e5637a5723f Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Tue, 28 May 2024 20:58:06 -0700 Subject: [PATCH 12/13] font/coretext: cleanup unused comments --- src/font/face/coretext.zig | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/font/face/coretext.zig b/src/font/face/coretext.zig index 0abf614cb..ceb2e40f7 100644 --- a/src/font/face/coretext.zig +++ b/src/font/face/coretext.zig @@ -253,9 +253,6 @@ pub const Face = struct { // to decode down into exactly one glyph ID. if (pair) assert(glyphs[1] == 0); - // If we have colorization information, then check if this - // glyph is colorized. - return @intCast(glyphs[0]); } @@ -600,6 +597,8 @@ pub const Face = struct { } }; +/// The state associated with a font face that may have colorized glyphs. +/// This is used to determine if a specific glyph ID is colorized. const ColorState = struct { /// True if there is an sbix font table. For now, the mere presence /// of an sbix font table causes us to assume the glyph is colored. From d978d05d7ec628d2190b6b39f1a092c75384db1a Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Tue, 28 May 2024 21:05:32 -0700 Subject: [PATCH 13/13] font/coretext: glyphIndex must return u32 for noop shaper --- src/font/face/coretext.zig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/font/face/coretext.zig b/src/font/face/coretext.zig index ceb2e40f7..6d07f1fa4 100644 --- a/src/font/face/coretext.zig +++ b/src/font/face/coretext.zig @@ -238,7 +238,7 @@ pub const Face = struct { /// Returns the glyph index for the given Unicode code point. If this /// face doesn't support this glyph, null is returned. - pub fn glyphIndex(self: Face, cp: u32) ?u16 { + pub fn glyphIndex(self: Face, cp: u32) ?u32 { // Turn UTF-32 into UTF-16 for CT API var unichars: [2]u16 = undefined; const pair = macos.foundation.stringGetSurrogatePairForLongCharacter(cp, &unichars);