diff --git a/src/font/CodepointResolver.zig b/src/font/CodepointResolver.zig index 4efeafbbc..7a03f0e5d 100644 --- a/src/font/CodepointResolver.zig +++ b/src/font/CodepointResolver.zig @@ -72,6 +72,15 @@ pub fn deinit(self: *CodepointResolver, alloc: Allocator) void { /// presentation for the codepoint. /// a code point. /// +/// An allocator is required because certain functionality (codepoint +/// mapping, fallback fonts, etc.) may require memory allocation. Curiously, +/// this function cannot error! If an error occurs for any reason, including +/// memory allocation, the associated functionality is ignored and the +/// resolver attempts to use a different method to satisfy the codepoint. +/// This behavior is intentional to make the resolver apply best-effort +/// logic to satisfy the codepoint since its better to render something +/// than nothing. +/// /// This logic is relatively complex so the exact algorithm is documented /// here. If this gets out of sync with the code, ask questions. /// @@ -118,7 +127,7 @@ pub fn getIndex( } // Codepoint overrides. - if (self.indexForCodepointOverride(alloc, cp)) |idx_| { + if (self.getIndexCodepointOverride(alloc, cp)) |idx_| { if (idx_) |idx| return idx; } else |err| { log.warn("codepoint override failed codepoint={} err={}", .{ cp, err }); @@ -208,7 +217,7 @@ pub fn getIndex( /// Checks if the codepoint is in the map of codepoint overrides, /// finds the override font, and returns it. -fn indexForCodepointOverride( +fn getIndexCodepointOverride( self: *CodepointResolver, alloc: Allocator, cp: u32, @@ -265,7 +274,7 @@ fn indexForCodepointOverride( const idx = idx_ orelse return null; // We need to verify that this index has the codepoint we want. - if (self.collection.hasCodepoint(idx, cp, null)) { + if (self.collection.hasCodepoint(idx, cp, .{ .any = {} })) { log.debug("codepoint override based on config codepoint={} family={s}", .{ cp, desc.family orelse "", @@ -385,3 +394,59 @@ test getIndex { try testing.expect(r.getIndex(alloc, 0x1FB00, .regular, null) == null); } } + +test "getIndex disabled font style" { + const testing = std.testing; + const alloc = testing.allocator; + const testFont = @import("test.zig").fontRegular; + + var atlas_greyscale = try font.Atlas.init(alloc, 512, .greyscale); + defer atlas_greyscale.deinit(alloc); + + var lib = try Library.init(); + defer lib.deinit(); + + var c = try Collection.init(alloc); + c.load_options = .{ .library = lib }; + + _ = try c.add(alloc, .regular, .{ .loaded = try Face.init( + lib, + testFont, + .{ .size = .{ .points = 12, .xdpi = 96, .ydpi = 96 } }, + ) }); + _ = try c.add(alloc, .bold, .{ .loaded = try Face.init( + lib, + testFont, + .{ .size = .{ .points = 12, .xdpi = 96, .ydpi = 96 } }, + ) }); + _ = try c.add(alloc, .italic, .{ .loaded = try Face.init( + lib, + testFont, + .{ .size = .{ .points = 12, .xdpi = 96, .ydpi = 96 } }, + ) }); + + var r: CodepointResolver = .{ .collection = c }; + defer r.deinit(alloc); + r.styles.set(.bold, false); // Disable bold + + // Regular should work fine + { + const idx = r.getIndex(alloc, 'A', .regular, null).?; + try testing.expectEqual(Style.regular, idx.style); + try testing.expectEqual(@as(Collection.Index.IndexInt, 0), idx.idx); + } + + // Bold should go to regular + { + const idx = r.getIndex(alloc, 'A', .bold, null).?; + try testing.expectEqual(Style.regular, idx.style); + try testing.expectEqual(@as(Collection.Index.IndexInt, 0), idx.idx); + } + + // Italic should still work + { + const idx = r.getIndex(alloc, 'A', .italic, null).?; + try testing.expectEqual(Style.italic, idx.style); + try testing.expectEqual(@as(Collection.Index.IndexInt, 0), idx.idx); + } +} diff --git a/src/font/Collection.zig b/src/font/Collection.zig index 4d623967a..f75356bc9 100644 --- a/src/font/Collection.zig +++ b/src/font/Collection.zig @@ -18,6 +18,7 @@ const Collection = @This(); const std = @import("std"); const Allocator = std.mem.Allocator; const font = @import("main.zig"); +const options = font.options; const DeferredFace = font.DeferredFace; const DesiredSize = font.face.DesiredSize; const Face = font.Face; @@ -160,14 +161,11 @@ pub fn hasCodepoint( self: *const Collection, index: Index, cp: u32, - p: ?Presentation, + p_mode: PresentationMode, ) bool { const list = self.faces.get(index.style); if (index.idx >= list.items.len) return false; - return list.items[index.idx].hasCodepoint( - cp, - if (p) |v| .{ .explicit = v } else .{ .any = {} }, - ); + return list.items[index.idx].hasCodepoint(cp, p_mode); } /// Automatically create an italicized font from the regular @@ -601,3 +599,50 @@ test setSize { try c.setSize(.{ .points = 24 }); try testing.expectEqual(@as(u32, 24), c.load_options.?.size.points); } + +test hasCodepoint { + const testing = std.testing; + const alloc = testing.allocator; + const testFont = @import("test.zig").fontRegular; + + var lib = try Library.init(); + defer lib.deinit(); + + var c = try init(alloc); + defer c.deinit(alloc); + c.load_options = .{ .library = lib }; + + const idx = try c.add(alloc, .regular, .{ .loaded = try Face.init( + lib, + testFont, + .{ .size = .{ .points = 12, .xdpi = 96, .ydpi = 96 } }, + ) }); + + try testing.expect(c.hasCodepoint(idx, 'A', .{ .any = {} })); + try testing.expect(!c.hasCodepoint(idx, '🥸', .{ .any = {} })); +} + +test "hasCodepoint emoji default graphical" { + if (options.backend != .fontconfig_freetype) return error.SkipZigTest; + + const testing = std.testing; + const alloc = testing.allocator; + const testEmoji = @import("test.zig").fontEmoji; + + var lib = try Library.init(); + defer lib.deinit(); + + var c = try init(alloc); + defer c.deinit(alloc); + c.load_options = .{ .library = lib }; + + const idx = try c.add(alloc, .regular, .{ .loaded = try Face.init( + lib, + testEmoji, + .{ .size = .{ .points = 12, .xdpi = 96, .ydpi = 96 } }, + ) }); + + try testing.expect(!c.hasCodepoint(idx, 'A', .{ .any = {} })); + try testing.expect(c.hasCodepoint(idx, '🥸', .{ .any = {} })); + // TODO(fontmem): test explicit/implicit +}