From ad6c2b6cc8eaefad06fb5edfce741de8bf110fe0 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Fri, 25 Aug 2023 13:16:42 -0700 Subject: [PATCH] font: move auto-italicization to Group --- src/Surface.zig | 35 ++++--------------- src/font/DeferredFace.zig | 41 ---------------------- src/font/Group.zig | 67 +++++++++++++++++++++++++++--------- src/font/GroupCache.zig | 2 -- src/font/shaper/harfbuzz.zig | 6 ++-- 5 files changed, 60 insertions(+), 91 deletions(-) diff --git a/src/Surface.zig b/src/Surface.zig index 004a2c987..7f5582292 100644 --- a/src/Surface.zig +++ b/src/Surface.zig @@ -230,7 +230,7 @@ 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); + try group.addFace(.regular, face); } else log.warn("font-family not found: {s}", .{family}); } if (config.@"font-family-bold") |family| { @@ -242,7 +242,7 @@ 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); + try group.addFace(.bold, face); } else log.warn("font-family-bold not found: {s}", .{family}); } if (config.@"font-family-italic") |family| { @@ -254,7 +254,7 @@ 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); + try group.addFace(.italic, face); } else log.warn("font-family-italic not found: {s}", .{family}); } if (config.@"font-family-bold-italic") |family| { @@ -267,53 +267,32 @@ 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); + try group.addFace(.bold_italic, 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)), ); try group.addFace( - alloc, .bold, font.DeferredFace.initLoaded(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)), ); try group.addFace( - alloc, .regular, font.DeferredFace.initLoaded(try font.Face.init(font_lib, face_emoji_text_ttf, font_size)), ); @@ -329,7 +308,7 @@ 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); + try group.addFace(.regular, face); } } } diff --git a/src/font/DeferredFace.zig b/src/font/DeferredFace.zig index f5f43c48e..4ebfe5ad7 100644 --- a/src/font/DeferredFace.zig +++ b/src/font/DeferredFace.zig @@ -72,13 +72,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. @@ -351,40 +344,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"); diff --git a/src/font/Group.zig b/src/font/Group.zig index 90b4edb5f..298e77086 100644 --- a/src/font/Group.zig +++ b/src/font/Group.zig @@ -98,22 +98,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: DeferredFace) !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, .{ .deferred = face }); + try list.append(self.alloc, .{ .deferred = 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. @@ -229,7 +262,7 @@ pub fn indexForCodepoint( cp, face.name() catch "", }); - self.addFace(self.alloc, style, face) catch break :discover; + self.addFace(style, face) catch break :discover; if (self.indexForCodepointExact(cp, style, p)) |value| return value; } } @@ -377,7 +410,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; }; @@ -450,13 +483,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, DeferredFace.initLoaded(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, DeferredFace.initLoaded(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, DeferredFace.initLoaded(try Face.init(lib, testEmojiText, .{ .points = 12 }))); // Should find all visible ASCII var i: u32 = 32; @@ -549,7 +582,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, DeferredFace.initLoaded(try Face.init(lib, testFont, .{ .points = 12, .xdpi = 96, .ydpi = 96 }))); // Load a letter { @@ -602,7 +635,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, (try it.next()).?); // Should find all visible ASCII var atlas_greyscale = try font.Atlas.init(alloc, 512, .greyscale); @@ -640,7 +673,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, DeferredFace.initLoaded(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 ecbab183b..dc4d1df2c 100644 --- a/src/font/GroupCache.zig +++ b/src/font/GroupCache.zig @@ -183,7 +183,6 @@ test { // Setup group try cache.group.addFace( - alloc, .regular, DeferredFace.initLoaded(try Face.init(lib, testFont, .{ .points = 12 })), ); @@ -340,7 +339,6 @@ test "resize" { // Setup group try cache.group.addFace( - alloc, .regular, DeferredFace.initLoaded(try Face.init(lib, testFont, .{ .points = 12, .xdpi = 96, .ydpi = 96 })), ); diff --git a/src/font/shaper/harfbuzz.zig b/src/font/shaper/harfbuzz.zig index 2e3d20ca8..590cbbe0c 100644 --- a/src/font/shaper/harfbuzz.zig +++ b/src/font/shaper/harfbuzz.zig @@ -795,12 +795,12 @@ 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, DeferredFace.initLoaded(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, DeferredFace.initLoaded(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, DeferredFace.initLoaded(try Face.init(lib, testEmojiText, .{ .points = 12 }))); var cell_buf = try alloc.alloc(font.shape.Cell, 80); errdefer alloc.free(cell_buf);