diff --git a/src/Surface.zig b/src/Surface.zig index 004a2c987..8bacd4886 100644 --- a/src/Surface.zig +++ b/src/Surface.zig @@ -222,6 +222,9 @@ pub fn init( }; group.discover = disco; + // A buffer we use to store the font names for logging. + var name_buf: [256]u8 = undefined; + if (config.@"font-family") |family| { var disco_it = try disco.discover(.{ .family = family, @@ -229,8 +232,8 @@ pub fn init( }); defer disco_it.deinit(); if (try disco_it.next()) |face| { - log.info("font regular: {s}", .{try face.name()}); - try group.addFace(alloc, .regular, face); + log.info("font regular: {s}", .{try face.name(&name_buf)}); + try group.addFace(.regular, .{ .deferred = face }); } else log.warn("font-family not found: {s}", .{family}); } if (config.@"font-family-bold") |family| { @@ -241,8 +244,8 @@ pub fn init( }); defer disco_it.deinit(); if (try disco_it.next()) |face| { - log.info("font bold: {s}", .{try face.name()}); - try group.addFace(alloc, .bold, face); + log.info("font bold: {s}", .{try face.name(&name_buf)}); + try group.addFace(.bold, .{ .deferred = face }); } else log.warn("font-family-bold not found: {s}", .{family}); } if (config.@"font-family-italic") |family| { @@ -253,8 +256,8 @@ pub fn init( }); defer disco_it.deinit(); if (try disco_it.next()) |face| { - log.info("font italic: {s}", .{try face.name()}); - try group.addFace(alloc, .italic, face); + log.info("font italic: {s}", .{try face.name(&name_buf)}); + try group.addFace(.italic, .{ .deferred = face }); } else log.warn("font-family-italic not found: {s}", .{family}); } if (config.@"font-family-bold-italic") |family| { @@ -266,56 +269,35 @@ pub fn init( }); defer disco_it.deinit(); if (try disco_it.next()) |face| { - log.info("font bold+italic: {s}", .{try face.name()}); - try group.addFace(alloc, .bold_italic, face); + log.info("font bold+italic: {s}", .{try face.name(&name_buf)}); + try group.addFace(.bold_italic, .{ .deferred = face }); } else log.warn("font-family-bold-italic not found: {s}", .{family}); } } // Our built-in font will be used as a backup try group.addFace( - alloc, .regular, - font.DeferredFace.initLoaded(try font.Face.init(font_lib, face_ttf, font_size)), + .{ .loaded = try font.Face.init(font_lib, face_ttf, font_size) }, ); try group.addFace( - alloc, .bold, - font.DeferredFace.initLoaded(try font.Face.init(font_lib, face_bold_ttf, font_size)), + .{ .loaded = try font.Face.init(font_lib, face_bold_ttf, font_size) }, ); - // If we support auto-italicization and we don't have an italic face, - // then we can try to auto-italicize our regular face. - if (comptime font.DeferredFace.canItalicize()) { - if (group.getFace(.italic) == null) { - if (group.getFace(.regular)) |regular| { - if (try regular.italicize()) |face| { - log.info("font auto-italicized: {s}", .{try face.name()}); - try group.addFace(alloc, .italic, face); - } - } - } - } else { - // We don't support auto-italics. If we don't have an italic font - // face let the user know so they aren't surprised (if they look - // at logs). - if (group.getFace(.italic) == null) { - log.warn("no italic font face available, italics will not render", .{}); - } - } + // Auto-italicize if we have to. + try group.italicize(); // Emoji fallback. We don't include this on Mac since Mac is expected // to always have the Apple Emoji available. if (builtin.os.tag != .macos or font.Discover == void) { try group.addFace( - alloc, .regular, - font.DeferredFace.initLoaded(try font.Face.init(font_lib, face_emoji_ttf, font_size)), + .{ .loaded = try font.Face.init(font_lib, face_emoji_ttf, font_size) }, ); try group.addFace( - alloc, .regular, - font.DeferredFace.initLoaded(try font.Face.init(font_lib, face_emoji_text_ttf, font_size)), + .{ .loaded = try font.Face.init(font_lib, face_emoji_text_ttf, font_size) }, ); } @@ -328,8 +310,9 @@ pub fn init( }); defer disco_it.deinit(); if (try disco_it.next()) |face| { - log.info("font emoji: {s}", .{try face.name()}); - try group.addFace(alloc, .regular, face); + var name_buf: [256]u8 = undefined; + log.info("font emoji: {s}", .{try face.name(&name_buf)}); + try group.addFace(.regular, .{ .deferred = face }); } } } diff --git a/src/font/DeferredFace.zig b/src/font/DeferredFace.zig index f5f43c48e..6d0d6fe31 100644 --- a/src/font/DeferredFace.zig +++ b/src/font/DeferredFace.zig @@ -30,9 +30,6 @@ const FaceState = switch (options.backend) { .web_canvas => WebCanvas, }; -/// The loaded face (once loaded). -face: ?Face = null, - /// Fontconfig fc: if (options.backend == .fontconfig_freetype) ?Fontconfig else void = if (options.backend == .fontconfig_freetype) null else {}, @@ -72,13 +69,6 @@ pub const CoreText = struct { self.font.release(); self.* = undefined; } - - /// Auto-italicize the font by applying a skew. - pub fn italicize(self: *const CoreText) !CoreText { - const ct_font = try self.font.copyWithAttributes(0.0, &Face.italic_skew, null); - errdefer ct_font.release(); - return .{ .font = ct_font }; - } }; /// WebCanvas specific data. This is only present when building with canvas. @@ -98,14 +88,7 @@ pub const WebCanvas = struct { } }; -/// Initialize a deferred face that is already pre-loaded. The deferred face -/// takes ownership over the loaded face, deinit will deinit the loaded face. -pub fn initLoaded(face: Face) DeferredFace { - return .{ .face = face }; -} - pub fn deinit(self: *DeferredFace) void { - if (self.face) |*face| face.deinit(); switch (options.backend) { .fontconfig_freetype => if (self.fc) |*fc| fc.deinit(), .coretext, .coretext_freetype => if (self.ct) |*ct| ct.deinit(), @@ -115,14 +98,9 @@ pub fn deinit(self: *DeferredFace) void { self.* = undefined; } -/// Returns true if the face has been loaded. -pub inline fn loaded(self: DeferredFace) bool { - return self.face != null; -} - /// Returns the name of this face. The memory is always owned by the /// face so it doesn't have to be freed. -pub fn name(self: DeferredFace) ![:0]const u8 { +pub fn name(self: DeferredFace, buf: []u8) ![]const u8 { switch (options.backend) { .freetype => {}, @@ -136,22 +114,15 @@ pub fn name(self: DeferredFace) ![:0]const u8 { // this to be returned efficiently." In this case, we need // to allocate. But we can't return an allocated string because // we don't have an allocator. Let's use the stack and log it. - var buf: [1024]u8 = undefined; - const buf_name = display_name.cstring(&buf, .utf8) orelse - ""; - - log.info( - "CoreText font required too much space to copy, value = {s}", - .{buf_name}, - ); - break :unsupported ""; + break :unsupported display_name.cstring(buf, .utf8) orelse + return error.OutOfMemory; }; }, .web_canvas => if (self.wc) |wc| return wc.font_str, } - return "TODO: built-in font names"; + return ""; } /// Load the deferred font face. This does nothing if the face is loaded. @@ -159,69 +130,48 @@ pub fn load( self: *DeferredFace, lib: Library, size: font.face.DesiredSize, -) !void { - // No-op if we already loaded - if (self.face != null) return; - - switch (options.backend) { - .fontconfig_freetype => { - try self.loadFontconfig(lib, size); - return; - }, - - .coretext => { - try self.loadCoreText(lib, size); - return; - }, - - .coretext_freetype => { - try self.loadCoreTextFreetype(lib, size); - return; - }, - - .web_canvas => { - try self.loadWebCanvas(size); - return; - }, +) !Face { + return switch (options.backend) { + .fontconfig_freetype => try self.loadFontconfig(lib, size), + .coretext => try self.loadCoreText(lib, size), + .coretext_freetype => try self.loadCoreTextFreetype(lib, size), + .web_canvas => try self.loadWebCanvas(size), // Unreachable because we must be already loaded or have the // proper configuration for one of the other deferred mechanisms. .freetype => unreachable, - } + }; } fn loadFontconfig( self: *DeferredFace, lib: Library, size: font.face.DesiredSize, -) !void { - assert(self.face == null); +) !Face { const fc = self.fc.?; // Filename and index for our face so we can load it const filename = (try fc.pattern.get(.file, 0)).string; const face_index = (try fc.pattern.get(.index, 0)).integer; - self.face = try Face.initFile(lib, filename, face_index, size); + return try Face.initFile(lib, filename, face_index, size); } fn loadCoreText( self: *DeferredFace, lib: Library, size: font.face.DesiredSize, -) !void { +) !Face { _ = lib; - assert(self.face == null); const ct = self.ct.?; - self.face = try Face.initFontCopy(ct.font, size); + return try Face.initFontCopy(ct.font, size); } fn loadCoreTextFreetype( self: *DeferredFace, lib: Library, size: font.face.DesiredSize, -) !void { - assert(self.face == null); +) !Face { const ct = self.ct.?; // Get the URL for the font so we can get the filepath @@ -253,16 +203,15 @@ fn loadCoreTextFreetype( // TODO: face index 0 is not correct long term and we should switch // to using CoreText for rendering, too. //std.log.warn("path={s}", .{path_slice}); - self.face = try Face.initFile(lib, buf[0..path_slice.len :0], 0, size); + return try Face.initFile(lib, buf[0..path_slice.len :0], 0, size); } fn loadWebCanvas( self: *DeferredFace, size: font.face.DesiredSize, -) !void { - assert(self.face == null); +) !Face { const wc = self.wc.?; - self.face = try Face.initNamed(wc.alloc, wc.font_str, size, wc.presentation); + return try Face.initNamed(wc.alloc, wc.font_str, size, wc.presentation); } /// Returns true if this face can satisfy the given codepoint and @@ -273,12 +222,6 @@ fn loadWebCanvas( /// discovery mechanism (i.e. fontconfig). If no discovery is used, /// the face is always expected to be loaded. pub fn hasCodepoint(self: DeferredFace, cp: u32, p: ?Presentation) bool { - // If we have the face, use the face. - if (self.face) |face| { - if (p) |desired| if (face.presentation != desired) return false; - return face.glyphIndex(cp) != null; - } - switch (options.backend) { .fontconfig_freetype => { // If we are using fontconfig, use the fontconfig metadata to @@ -351,40 +294,6 @@ pub fn hasCodepoint(self: DeferredFace, cp: u32, p: ?Presentation) bool { unreachable; } -/// Returns true if our deferred font implementation supports auto-itacilization. -pub fn canItalicize() bool { - return @hasDecl(FaceState, "italicize") and @hasDecl(Face, "italicize"); -} - -/// Returns a new deferred face with the italicized version of this face -/// by applying a skew. This is NOT TRUE italics. You should use the discovery -/// mechanism to try to find an italic font. This is a fallback for when -/// that fails. -pub fn italicize(self: *const DeferredFace) !?DeferredFace { - if (comptime !canItalicize()) return null; - - var result: DeferredFace = .{}; - - if (self.face) |face| { - result.face = try face.italicize(); - } - - switch (options.backend) { - .freetype => {}, - .fontconfig_freetype => if (self.fc) |*fc| { - result.fc = try fc.italicize(); - }, - .coretext, .coretext_freetype => if (self.ct) |*ct| { - result.ct = try ct.italicize(); - }, - .web_canvas => if (self.wc) |*wc| { - result.wc = try wc.italicize(); - }, - } - - return result; -} - /// The wasm-compatible API. pub const Wasm = struct { const wasm = @import("../os/wasm.zig"); @@ -429,30 +338,8 @@ pub const Wasm = struct { return; }; } - - /// Caller should not free this, the face is owned by the deferred face. - export fn deferred_face_face(self: *DeferredFace) ?*Face { - assert(self.loaded()); - return &self.face.?; - } }; -test "preloaded" { - const testing = std.testing; - const testFont = @import("test.zig").fontRegular; - - var lib = try Library.init(); - defer lib.deinit(); - - var face = try Face.init(lib, testFont, .{ .points = 12 }); - errdefer face.deinit(); - - var def = initLoaded(face); - defer def.deinit(); - - try testing.expect(def.hasCodepoint(' ', null)); -} - test "fontconfig" { if (options.backend != .fontconfig_freetype) return error.SkipZigTest; @@ -471,16 +358,15 @@ test "fontconfig" { break :def (try it.next()).?; }; defer def.deinit(); - try testing.expect(!def.loaded()); // Verify we can get the name - const n = try def.name(); + var buf: [1024]u8 = undefined; + const n = try def.name(&buf); try testing.expect(n.len > 0); // Load it and verify it works - try def.load(lib, .{ .points = 12 }); - try testing.expect(def.hasCodepoint(' ', null)); - try testing.expect(def.face.?.glyphIndex(' ') != null); + const face = try def.load(lib, .{ .points = 12 }); + try testing.expect(face.glyphIndex(' ') != null); } test "coretext" { @@ -501,15 +387,14 @@ test "coretext" { break :def (try it.next()).?; }; defer def.deinit(); - try testing.expect(!def.loaded()); try testing.expect(def.hasCodepoint(' ', null)); // Verify we can get the name - const n = try def.name(); + var buf: [1024]u8 = undefined; + const n = try def.name(&buf); try testing.expect(n.len > 0); // Load it and verify it works - try def.load(lib, .{ .points = 12 }); - try testing.expect(def.hasCodepoint(' ', null)); - try testing.expect(def.face.?.glyphIndex(' ') != null); + const face = try def.load(lib, .{ .points = 12 }); + try testing.expect(face.glyphIndex(' ') != null); } diff --git a/src/font/Group.zig b/src/font/Group.zig index 1296db8fc..e14feb33a 100644 --- a/src/font/Group.zig +++ b/src/font/Group.zig @@ -22,6 +22,7 @@ const Glyph = @import("main.zig").Glyph; const Style = @import("main.zig").Style; const Presentation = @import("main.zig").Presentation; const options = @import("main.zig").options; +const quirks = @import("../quirks.zig"); const log = std.log.scoped(.font_group); @@ -30,8 +31,7 @@ const log = std.log.scoped(.font_group); // usually only one font group for the entire process so this isn't the // most important memory efficiency we can look for. This is totally opaque // to the user so we can change this later. -const StyleArray = std.EnumArray(Style, std.ArrayListUnmanaged(DeferredFace)); - +const StyleArray = std.EnumArray(Style, std.ArrayListUnmanaged(GroupFace)); /// The allocator for this group alloc: Allocator, @@ -55,6 +55,19 @@ discover: ?*font.Discover = null, /// terminal rendering will look wrong. sprite: ?font.sprite.Face = null, +/// A face in a group can be deferred or loaded. +pub const GroupFace = union(enum) { + deferred: DeferredFace, // Not loaded + loaded: Face, // Loaded + + pub fn deinit(self: *GroupFace) void { + switch (self.*) { + .deferred => |*v| v.deinit(), + .loaded => |*v| v.deinit(), + } + } +}; + pub fn init( alloc: Allocator, lib: Library, @@ -86,22 +99,55 @@ pub fn deinit(self: *Group) void { /// /// The group takes ownership of the face. The face will be deallocated when /// the group is deallocated. -pub fn addFace(self: *Group, alloc: Allocator, style: Style, face: DeferredFace) !void { +pub fn addFace(self: *Group, style: Style, face: GroupFace) !void { const list = self.faces.getPtr(style); // We have some special indexes so we must never pass those. if (list.items.len >= FontIndex.Special.start - 1) return error.GroupFull; - try list.append(alloc, face); + try list.append(self.alloc, face); } -/// Get the face for the given style. This will always return the first -/// face (if it exists). The returned pointer is only valid as long as -/// the faces do not change. -pub fn getFace(self: *Group, style: Style) ?*DeferredFace { - const list = self.faces.getPtr(style); - if (list.items.len == 0) return null; - return &list.items[0]; +/// Returns true if we have a face for the given style, though the face may +/// not be loaded yet. +pub fn hasFaceForStyle(self: Group, style: Style) bool { + const list = self.faces.get(style); + return list.items.len > 0; +} + +/// This will automatically create an italicized font from the regular +/// font face if we don't have any italicized fonts. +pub fn italicize(self: *Group) !void { + // If we have an italic font, do nothing. + const italic_list = self.faces.getPtr(.italic); + if (italic_list.items.len > 0) return; + + // Not all font backends support auto-italicization. + if (comptime !@hasDecl(Face, "italicize")) { + log.warn("no italic font face available, italics will not render", .{}); + return; + } + + // Our regular font. If we have no regular font we also do nothing. + const regular = regular: { + const list = self.faces.get(.regular); + if (list.items.len == 0) return; + + // The font must be loaded. + break :regular try self.faceFromIndex(.{ + .style = .regular, + .idx = 0, + }); + }; + + // Try to italicize it. + const face = try regular.italicize(); + try italic_list.append(self.alloc, .{ .loaded = face }); + + var buf: [128]u8 = undefined; + if (face.name(&buf)) |name| { + log.info("font auto-italicized: {s}", .{name}); + } else |_| {} } /// Resize the fonts to the desired size. @@ -113,10 +159,10 @@ pub fn setSize(self: *Group, size: font.face.DesiredSize) !void { // Resize all our faces that are loaded var it = self.faces.iterator(); while (it.next()) |entry| { - for (entry.value.items) |*deferred| { - if (!deferred.loaded()) continue; - try deferred.face.?.setSize(size); - } + for (entry.value.items) |*elem| switch (elem.*) { + .deferred => continue, + .loaded => |*f| try f.setSize(size), + }; } // Set our size for future loads @@ -213,11 +259,12 @@ pub fn indexForCodepoint( defer disco_it.deinit(); if (disco_it.next() catch break :discover) |face| { + var buf: [256]u8 = undefined; log.info("found codepoint 0x{x} in fallback face={s}", .{ cp, - face.name() catch "", + face.name(&buf) catch "", }); - self.addFace(self.alloc, style, face) catch break :discover; + self.addFace(style, .{ .deferred = face }) catch break :discover; if (self.indexForCodepointExact(cp, style, p)) |value| return value; } } @@ -231,8 +278,16 @@ pub fn indexForCodepoint( } fn indexForCodepointExact(self: Group, cp: u32, style: Style, p: ?Presentation) ?FontIndex { - for (self.faces.get(style).items, 0..) |deferred, i| { - if (deferred.hasCodepoint(cp, p)) { + for (self.faces.get(style).items, 0..) |elem, i| { + const has_cp = switch (elem) { + .deferred => |v| v.hasCodepoint(cp, p), + .loaded => |face| loaded: { + if (p) |desired| if (face.presentation != desired) break :loaded false; + break :loaded face.glyphIndex(cp) != null; + }, + }; + + if (has_cp) { return FontIndex{ .style = style, .idx = @intCast(i), @@ -246,7 +301,7 @@ fn indexForCodepointExact(self: Group, cp: u32, style: Style, p: ?Presentation) /// Returns the presentation for a specific font index. This is useful for /// determining what atlas is needed. -pub fn presentationFromIndex(self: Group, index: FontIndex) !font.Presentation { +pub fn presentationFromIndex(self: *Group, index: FontIndex) !font.Presentation { if (index.special()) |sp| switch (sp) { .sprite => return .text, }; @@ -256,12 +311,22 @@ pub fn presentationFromIndex(self: Group, index: FontIndex) !font.Presentation { } /// Return the Face represented by a given FontIndex. Note that special -/// fonts (i.e. box glyphs) do not have a face. -pub fn faceFromIndex(self: Group, index: FontIndex) !*Face { +/// fonts (i.e. box glyphs) do not have a face. The returned face pointer is +/// only valid until the set of faces change. +pub fn faceFromIndex(self: *Group, index: FontIndex) !*Face { if (index.special() != null) return error.SpecialHasNoFace; - const deferred = &self.faces.get(index.style).items[@intCast(index.idx)]; - try deferred.load(self.lib, self.size); - return &deferred.face.?; + const list = self.faces.getPtr(index.style); + const item = &list.items[@intCast(index.idx)]; + return switch (item.*) { + .deferred => |*d| deferred: { + const face = try d.load(self.lib, self.size); + d.deinit(); + item.* = .{ .loaded = face }; + break :deferred &item.loaded; + }, + + .loaded => |*f| f, + }; } /// Render a glyph by glyph index into the given font atlas and return @@ -276,7 +341,7 @@ pub fn faceFromIndex(self: Group, index: FontIndex) !*Face { /// doing text shaping, the text shaping library (i.e. HarfBuzz) will automatically /// determine glyph indexes for a text run. pub fn renderGlyph( - self: Group, + self: *Group, alloc: Allocator, atlas: *font.Atlas, index: FontIndex, @@ -292,9 +357,8 @@ pub fn renderGlyph( ), }; - const face = &self.faces.get(index.style).items[@intCast(index.idx)]; - try face.load(self.lib, self.size); - const glyph = try face.face.?.renderGlyph(alloc, atlas, glyph_index, opts); + const face = try self.faceFromIndex(index); + const glyph = try face.renderGlyph(alloc, atlas, glyph_index, opts); // log.warn("GLYPH={}", .{glyph}); return glyph; } @@ -349,7 +413,7 @@ pub const Wasm = struct { } export fn group_add_face(self: *Group, style: u16, face: *font.DeferredFace) void { - return self.addFace(alloc, @enumFromInt(style), face.*) catch |err| { + return self.addFace(@enumFromInt(style), face.*) catch |err| { log.warn("error adding face to group err={}", .{err}); return; }; @@ -422,13 +486,13 @@ test { var group = try init(alloc, lib, .{ .points = 12 }); defer group.deinit(); - try group.addFace(alloc, .regular, DeferredFace.initLoaded(try Face.init(lib, testFont, .{ .points = 12 }))); + try group.addFace(.regular, .{ .loaded = try Face.init(lib, testFont, .{ .points = 12 }) }); if (font.options.backend != .coretext) { // Coretext doesn't support Noto's format - try group.addFace(alloc, .regular, DeferredFace.initLoaded(try Face.init(lib, testEmoji, .{ .points = 12 }))); + try group.addFace(.regular, .{ .loaded = try Face.init(lib, testEmoji, .{ .points = 12 }) }); } - try group.addFace(alloc, .regular, DeferredFace.initLoaded(try Face.init(lib, testEmojiText, .{ .points = 12 }))); + try group.addFace(.regular, .{ .loaded = try Face.init(lib, testEmojiText, .{ .points = 12 }) }); // Should find all visible ASCII var i: u32 = 32; @@ -521,7 +585,7 @@ test "resize" { var group = try init(alloc, lib, .{ .points = 12, .xdpi = 96, .ydpi = 96 }); defer group.deinit(); - try group.addFace(alloc, .regular, DeferredFace.initLoaded(try Face.init(lib, testFont, .{ .points = 12, .xdpi = 96, .ydpi = 96 }))); + try group.addFace(.regular, .{ .loaded = try Face.init(lib, testFont, .{ .points = 12, .xdpi = 96, .ydpi = 96 }) }); // Load a letter { @@ -574,7 +638,7 @@ test "discover monospace with fontconfig and freetype" { defer lib.deinit(); var group = try init(alloc, lib, .{ .points = 12 }); defer group.deinit(); - try group.addFace(alloc, .regular, (try it.next()).?); + try group.addFace(.regular, .{ .deferred = (try it.next()).? }); // Should find all visible ASCII var atlas_greyscale = try font.Atlas.init(alloc, 512, .greyscale); @@ -612,7 +676,7 @@ test "faceFromIndex returns pointer" { var group = try init(alloc, lib, .{ .points = 12, .xdpi = 96, .ydpi = 96 }); defer group.deinit(); - try group.addFace(alloc, .regular, DeferredFace.initLoaded(try Face.init(lib, testFont, .{ .points = 12, .xdpi = 96, .ydpi = 96 }))); + try group.addFace(.regular, .{ .loaded = try Face.init(lib, testFont, .{ .points = 12, .xdpi = 96, .ydpi = 96 }) }); { const idx = group.indexForCodepoint('A', .regular, null).?; diff --git a/src/font/GroupCache.zig b/src/font/GroupCache.zig index af73aac58..b869e2b5f 100644 --- a/src/font/GroupCache.zig +++ b/src/font/GroupCache.zig @@ -183,11 +183,10 @@ test { // Setup group try cache.group.addFace( - alloc, .regular, - DeferredFace.initLoaded(try Face.init(lib, testFont, .{ .points = 12 })), + .{ .loaded = try Face.init(lib, testFont, .{ .points = 12 }) }, ); - const group = cache.group; + var group = cache.group; // Visible ASCII. Do it twice to verify cache. var i: u32 = 32; @@ -340,9 +339,8 @@ test "resize" { // Setup group try cache.group.addFace( - alloc, .regular, - DeferredFace.initLoaded(try Face.init(lib, testFont, .{ .points = 12, .xdpi = 96, .ydpi = 96 })), + .{ .loaded = try Face.init(lib, testFont, .{ .points = 12, .xdpi = 96, .ydpi = 96 }) }, ); // Load a letter diff --git a/src/font/discovery.zig b/src/font/discovery.zig index 548c23af1..67bde3b0d 100644 --- a/src/font/discovery.zig +++ b/src/font/discovery.zig @@ -217,7 +217,6 @@ pub const Fontconfig = struct { defer self.i += 1; return DeferredFace{ - .face = null, .fc = .{ .pattern = font_pattern, .charset = (try font_pattern.get(.charset, 0)).char_set, @@ -301,7 +300,6 @@ pub const CoreText = struct { defer self.i += 1; return DeferredFace{ - .face = null, .ct = .{ .font = font }, }; } @@ -311,14 +309,9 @@ pub const CoreText = struct { test "fontconfig" { if (options.backend != .fontconfig_freetype) return error.SkipZigTest; - const testing = std.testing; - var fc = Fontconfig.init(); var it = try fc.discover(.{ .family = "monospace", .size = 12 }); defer it.deinit(); - while (try it.next()) |face| { - try testing.expect(!face.loaded()); - } } test "fontconfig codepoint" { @@ -333,7 +326,6 @@ test "fontconfig codepoint" { // The first result should have the codepoint. Later ones may not // because fontconfig returns all fonts sorted. const face = (try it.next()).?; - try testing.expect(!face.loaded()); try testing.expect(face.hasCodepoint('A', null)); // Should have other codepoints too @@ -351,9 +343,8 @@ test "coretext" { var it = try ct.discover(.{ .family = "Monaco", .size = 12 }); defer it.deinit(); var count: usize = 0; - while (try it.next()) |face| { + while (try it.next()) |_| { count += 1; - try testing.expect(!face.loaded()); } try testing.expect(count > 0); } @@ -372,7 +363,6 @@ test "coretext codepoint" { // The first result should have the codepoint. Later ones may not // because fontconfig returns all fonts sorted. const face = (try it.next()).?; - try testing.expect(!face.loaded()); try testing.expect(face.hasCodepoint('A', null)); // Should have other codepoints too diff --git a/src/font/face/coretext.zig b/src/font/face/coretext.zig index e8fe1873f..99d074453 100644 --- a/src/font/face/coretext.zig +++ b/src/font/face/coretext.zig @@ -4,6 +4,7 @@ const Allocator = std.mem.Allocator; const macos = @import("macos"); const harfbuzz = @import("harfbuzz"); const font = @import("../main.zig"); +const quirks = @import("../../quirks.zig"); const log = std.log.scoped(.font_face); @@ -20,6 +21,9 @@ pub const Face = struct { /// Metrics for this font face. These are useful for renderers. metrics: font.face.Metrics, + /// Set quirks.disableDefaultFontFeatures + quirks_disable_default_font_features: bool = false, + /// The matrix applied to a regular font to auto-italicize it. pub const italic_skew = macos.graphics.AffineTransform{ .a = 1, @@ -65,12 +69,14 @@ pub const Face = struct { const traits = ct_font.getSymbolicTraits(); - return Face{ + var result: Face = .{ .font = ct_font, .hb_font = hb_font, .presentation = if (traits.color_glyphs) .emoji else .text, .metrics = try calcMetrics(ct_font), }; + result.quirks_disable_default_font_features = quirks.disableDefaultFontFeatures(&result); + return result; } pub fn deinit(self: *Face) void { diff --git a/src/font/face/freetype.zig b/src/font/face/freetype.zig index c292e05b6..7b104eb01 100644 --- a/src/font/face/freetype.zig +++ b/src/font/face/freetype.zig @@ -18,6 +18,7 @@ const Library = font.Library; const Presentation = font.Presentation; const convert = @import("freetype_convert.zig"); const fastmem = @import("../../fastmem.zig"); +const quirks = @import("../../quirks.zig"); const log = std.log.scoped(.font_face); @@ -35,6 +36,9 @@ pub const Face = struct { /// Metrics for this font face. These are useful for renderers. metrics: font.face.Metrics, + /// Set quirks.disableDefaultFontFeatures + quirks_disable_default_font_features: bool = false, + /// Initialize a new font face with the given source in-memory. pub fn initFile(lib: Library, path: [:0]const u8, index: i32, size: font.face.DesiredSize) !Face { const face = try lib.lib.initFace(path, index); @@ -56,12 +60,14 @@ pub const Face = struct { const hb_font = try harfbuzz.freetype.createFont(face.handle); errdefer hb_font.destroy(); - return Face{ + var result: Face = .{ .face = face, .hb_font = hb_font, .presentation = if (face.hasColor()) .emoji else .text, .metrics = calcMetrics(face), }; + result.quirks_disable_default_font_features = quirks.disableDefaultFontFeatures(&result); + return result; } pub fn deinit(self: *Face) void { diff --git a/src/font/shaper/harfbuzz.zig b/src/font/shaper/harfbuzz.zig index 2e3d20ca8..688aed67c 100644 --- a/src/font/shaper/harfbuzz.zig +++ b/src/font/shaper/harfbuzz.zig @@ -12,7 +12,6 @@ const Library = font.Library; const Style = font.Style; const Presentation = font.Presentation; const terminal = @import("../../terminal/main.zig"); -const quirks = @import("../../quirks.zig"); const log = std.log.scoped(.font_shaper); @@ -108,7 +107,7 @@ pub const Shaper = struct { // fonts, the codepoint == glyph_index so we don't need to run any shaping. if (run.font_index.special() == null) { const face = try run.group.group.faceFromIndex(run.font_index); - const i = if (!quirks.disableDefaultFontFeatures(face)) 0 else i: { + const i = if (!face.quirks_disable_default_font_features) 0 else i: { // If we are disabling default font features we just offset // our features by the hardcoded items because always // add those at the beginning. @@ -795,12 +794,13 @@ fn testShaper(alloc: Allocator) !TestShaper { errdefer cache_ptr.*.deinit(alloc); // Setup group - try cache_ptr.group.addFace(alloc, .regular, DeferredFace.initLoaded(try Face.init(lib, testFont, .{ .points = 12 }))); + try cache_ptr.group.addFace(.regular, .{ .loaded = try Face.init(lib, testFont, .{ .points = 12 }) }); + if (font.options.backend != .coretext) { // Coretext doesn't support Noto's format - try cache_ptr.group.addFace(alloc, .regular, DeferredFace.initLoaded(try Face.init(lib, testEmoji, .{ .points = 12 }))); + try cache_ptr.group.addFace(.regular, .{ .loaded = try Face.init(lib, testEmoji, .{ .points = 12 }) }); } - try cache_ptr.group.addFace(alloc, .regular, DeferredFace.initLoaded(try Face.init(lib, testEmojiText, .{ .points = 12 }))); + try cache_ptr.group.addFace(.regular, .{ .loaded = try Face.init(lib, testEmojiText, .{ .points = 12 }) }); var cell_buf = try alloc.alloc(font.shape.Cell, 80); errdefer alloc.free(cell_buf);