diff --git a/pkg/macos/foundation/dictionary.zig b/pkg/macos/foundation/dictionary.zig index d467cb9ed..710fd1e4f 100644 --- a/pkg/macos/foundation/dictionary.zig +++ b/pkg/macos/foundation/dictionary.zig @@ -51,6 +51,14 @@ pub const MutableDictionary = opaque { ))) orelse Allocator.Error.OutOfMemory; } + pub fn createMutableCopy(cap: usize, src: *Dictionary) Allocator.Error!*MutableDictionary { + return @intToPtr(?*MutableDictionary, @ptrToInt(c.CFDictionaryCreateMutableCopy( + null, + @intCast(c.CFIndex, cap), + @ptrCast(c.CFDictionaryRef, src), + ))) orelse Allocator.Error.OutOfMemory; + } + pub fn release(self: *MutableDictionary) void { foundation.CFRelease(self); } diff --git a/pkg/macos/text/font_descriptor.zig b/pkg/macos/text/font_descriptor.zig index 22057db58..78685801b 100644 --- a/pkg/macos/text/font_descriptor.zig +++ b/pkg/macos/text/font_descriptor.zig @@ -18,6 +18,19 @@ pub const FontDescriptor = opaque { ) orelse Allocator.Error.OutOfMemory; } + pub fn createCopyWithAttributes( + original: *FontDescriptor, + dict: *foundation.Dictionary, + ) Allocator.Error!*FontDescriptor { + return @intToPtr( + ?*FontDescriptor, + @ptrToInt(c.CTFontDescriptorCreateCopyWithAttributes( + @ptrCast(c.CTFontDescriptorRef, original), + @ptrCast(c.CFDictionaryRef, dict), + )), + ) orelse Allocator.Error.OutOfMemory; + } + pub fn release(self: *FontDescriptor) void { c.CFRelease(self); } @@ -28,6 +41,12 @@ pub const FontDescriptor = opaque { @ptrCast(c.CFStringRef, attr.key()), ))); } + + pub fn copyAttributes(self: *FontDescriptor) *foundation.Dictionary { + return @intToPtr(*foundation.Dictionary, @ptrToInt(c.CTFontDescriptorCopyAttributes( + @ptrCast(c.CTFontDescriptorRef, self), + ))); + } }; pub const FontAttribute = enum { diff --git a/src/Window.zig b/src/Window.zig index 9985dcb9b..c4ec2a012 100644 --- a/src/Window.zig +++ b/src/Window.zig @@ -189,7 +189,7 @@ pub fn create(alloc: Allocator, app: *App, config: *const Config) !*Window { }); defer disco_it.deinit(); if (try disco_it.next()) |face| { - log.debug("font regular: {s}", .{try face.name()}); + log.info("font regular: {s}", .{try face.name()}); try group.addFace(alloc, .regular, face); } } @@ -201,7 +201,7 @@ pub fn create(alloc: Allocator, app: *App, config: *const Config) !*Window { }); defer disco_it.deinit(); if (try disco_it.next()) |face| { - log.debug("font bold: {s}", .{try face.name()}); + log.info("font bold: {s}", .{try face.name()}); try group.addFace(alloc, .bold, face); } } @@ -213,7 +213,7 @@ pub fn create(alloc: Allocator, app: *App, config: *const Config) !*Window { }); defer disco_it.deinit(); if (try disco_it.next()) |face| { - log.debug("font italic: {s}", .{try face.name()}); + log.info("font italic: {s}", .{try face.name()}); try group.addFace(alloc, .italic, face); } } @@ -226,7 +226,7 @@ pub fn create(alloc: Allocator, app: *App, config: *const Config) !*Window { }); defer disco_it.deinit(); if (try disco_it.next()) |face| { - log.debug("font bold+italic: {s}", .{try face.name()}); + log.info("font bold+italic: {s}", .{try face.name()}); try group.addFace(alloc, .bold_italic, face); } } diff --git a/src/font/Group.zig b/src/font/Group.zig index abacf385c..b7173fc61 100644 --- a/src/font/Group.zig +++ b/src/font/Group.zig @@ -256,10 +256,10 @@ test "resize" { var lib = try Library.init(); defer lib.deinit(); - var group = try init(alloc, lib, .{ .points = 12 }); + var group = try init(alloc, lib, .{ .points = 12, .xdpi = 96, .ydpi = 96 }); defer group.deinit(alloc); - try group.addFace(alloc, .regular, DeferredFace.initLoaded(try Face.init(lib, testFont, .{ .points = 12 }))); + try group.addFace(alloc, .regular, DeferredFace.initLoaded(try Face.init(lib, testFont, .{ .points = 12, .xdpi = 96, .ydpi = 96 }))); // Load a letter { @@ -278,7 +278,7 @@ test "resize" { } // Resize - try group.setSize(.{ .points = 24 }); + try group.setSize(.{ .points = 24, .xdpi = 96, .ydpi = 96 }); { const idx = group.indexForCodepoint('A', .regular, null).?; const face = try group.faceFromIndex(idx); diff --git a/src/font/GroupCache.zig b/src/font/GroupCache.zig index d4c627807..44e7d8337 100644 --- a/src/font/GroupCache.zig +++ b/src/font/GroupCache.zig @@ -253,7 +253,7 @@ test "resize" { try cache.group.addFace( alloc, .regular, - DeferredFace.initLoaded(try Face.init(lib, testFont, .{ .points = 12 })), + DeferredFace.initLoaded(try Face.init(lib, testFont, .{ .points = 12, .xdpi = 96, .ydpi = 96 })), ); // Load a letter @@ -272,7 +272,7 @@ test "resize" { } // Resize - try cache.setSize(.{ .points = 24 }); + try cache.setSize(.{ .points = 24, .xdpi = 96, .ydpi = 96 }); { const idx = (try cache.indexForCodepoint(alloc, 'A', .regular, null)).?; const face = try cache.group.faceFromIndex(idx); diff --git a/src/font/discovery.zig b/src/font/discovery.zig index e13d06fa0..eefb857b3 100644 --- a/src/font/discovery.zig +++ b/src/font/discovery.zig @@ -6,7 +6,7 @@ const macos = @import("macos"); const options = @import("main.zig").options; const DeferredFace = @import("main.zig").DeferredFace; -const log = std.log.named(.discovery); +const log = std.log.scoped(.discovery); /// Discover implementation for the compile options. pub const Discover = switch (options.backend) { @@ -27,12 +27,15 @@ pub const Descriptor = struct { /// /// On systems that use fontconfig (Linux), this can be a full /// fontconfig pattern, such as "Fira Code-14:bold". - family: [:0]const u8, + family: ?[:0]const u8 = null, + + /// A codepoint that this font must be able to render. + codepoint: u32 = 0, /// Font size in points that the font should support. For conversion /// to pixels, we will use 72 DPI for Mac and 96 DPI for everything else. /// (If pixel conversion is necessary, i.e. emoji fonts) - size: u16, + size: u16 = 0, /// True if we want to search specifically for a font that supports /// bold, italic, or both. @@ -44,7 +47,15 @@ pub const Descriptor = struct { /// must still do this. pub fn toFcPattern(self: Descriptor) *fontconfig.Pattern { const pat = fontconfig.Pattern.create(); - assert(pat.add(.family, .{ .string = self.family }, false)); + if (self.family) |family| { + assert(pat.add(.family, .{ .string = family }, false)); + } + if (self.codepoint > 0) { + const cs = fontconfig.CharSet.create(); + defer cs.destroy(); + assert(cs.addChar(self.codepoint)); + assert(pat.add(.charset, .{ .char_set = cs }, false)); + } if (self.size > 0) assert(pat.add( .size, .{ .integer = self.size }, @@ -70,13 +81,28 @@ pub const Descriptor = struct { const attrs = try macos.foundation.MutableDictionary.create(0); defer attrs.release(); - // Family is always set - const family = try macos.foundation.String.createWithBytes(self.family, .utf8, false); - defer family.release(); - attrs.setValue( - macos.text.FontAttribute.family_name.key(), - family, - ); + // Family + if (self.family) |family_bytes| { + const family = try macos.foundation.String.createWithBytes(family_bytes, .utf8, false); + defer family.release(); + attrs.setValue( + macos.text.FontAttribute.family_name.key(), + family, + ); + } + + // Codepoint support + if (self.codepoint > 0) { + const cs = try macos.foundation.CharacterSet.createWithCharactersInRange(.{ + .location = self.codepoint, + .length = 1, + }); + defer cs.release(); + attrs.setValue( + macos.text.FontAttribute.character_set.key(), + cs, + ); + } // Set our size attribute if set if (self.size > 0) { @@ -254,9 +280,26 @@ pub const CoreText = struct { pub fn next(self: *DiscoverIterator) !?DeferredFace { if (self.i >= self.list.getCount()) return null; + // Get our descriptor. We need to remove the character set + // limitation because we may have used that to filter but we + // don't want it anymore because it'll restrict the characters + // available. + //const desc = self.list.getValueAtIndex(macos.text.FontDescriptor, self.i); + const desc = desc: { + const original = self.list.getValueAtIndex(macos.text.FontDescriptor, self.i); + + // For some reason simply copying the attributes and recreating + // the descriptor removes the charset restriction. This is tested. + const attrs = original.copyAttributes(); + defer attrs.release(); + break :desc try macos.text.FontDescriptor.createWithAttributes( + @ptrCast(*macos.foundation.Dictionary, attrs), + ); + }; + defer desc.release(); + // Create our font. We need a size to initialize it so we use size // 12 but we will alter the size later. - const desc = self.list.getValueAtIndex(macos.text.FontDescriptor, self.i); const font = try macos.text.Font.createWithFontDescriptor(desc, 12); errdefer font.release(); @@ -284,7 +327,26 @@ test "fontconfig" { } } -test "core text" { +test "fontconfig codepoint" { + if (options.backend != .fontconfig_freetype) return error.SkipZigTest; + + const testing = std.testing; + + var fc = Fontconfig.init(); + var it = try fc.discover(.{ .codepoint = 'A', .size = 12 }); + defer it.deinit(); + + // 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 + try testing.expect(face.hasCodepoint('B', null)); +} + +test "coretext" { if (options.backend != .coretext) return error.SkipZigTest; const testing = std.testing; @@ -300,3 +362,23 @@ test "core text" { } try testing.expect(count > 0); } + +test "coretext codepoint" { + if (options.backend != .coretext) return error.SkipZigTest; + + const testing = std.testing; + + var ct = CoreText.init(); + defer ct.deinit(); + var it = try ct.discover(.{ .codepoint = 'A', .size = 12 }); + defer it.deinit(); + + // 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 + try testing.expect(face.hasCodepoint('B', null)); +} diff --git a/src/font/face/freetype.zig b/src/font/face/freetype.zig index 4c726b16f..ccb057ce4 100644 --- a/src/font/face/freetype.zig +++ b/src/font/face/freetype.zig @@ -461,7 +461,7 @@ test { var atlas = try Atlas.init(alloc, 512, .greyscale); defer atlas.deinit(alloc); - var ft_font = try Face.init(lib, testFont, .{ .points = 12 }); + var ft_font = try Face.init(lib, testFont, .{ .points = 12, .xdpi = 96, .ydpi = 96 }); defer ft_font.deinit(); try testing.expectEqual(Presentation.text, ft_font.presentation); @@ -477,7 +477,7 @@ test { const g1 = try ft_font.renderGlyph(alloc, &atlas, ft_font.glyphIndex('A').?, null); try testing.expectEqual(@as(u32, 11), g1.height); - try ft_font.setSize(.{ .points = 24 }); + try ft_font.setSize(.{ .points = 24, .xdpi = 96, .ydpi = 96 }); const g2 = try ft_font.renderGlyph(alloc, &atlas, ft_font.glyphIndex('A').?, null); try testing.expectEqual(@as(u32, 21), g2.height); } @@ -493,7 +493,7 @@ test "color emoji" { var atlas = try Atlas.init(alloc, 512, .rgba); defer atlas.deinit(alloc); - var ft_font = try Face.init(lib, testFont, .{ .points = 12 }); + var ft_font = try Face.init(lib, testFont, .{ .points = 12, .xdpi = 96, .ydpi = 96 }); defer ft_font.deinit(); try testing.expectEqual(Presentation.emoji, ft_font.presentation); @@ -517,7 +517,7 @@ test "metrics" { var atlas = try Atlas.init(alloc, 512, .greyscale); defer atlas.deinit(alloc); - var ft_font = try Face.init(lib, testFont, .{ .points = 12 }); + var ft_font = try Face.init(lib, testFont, .{ .points = 12, .xdpi = 96, .ydpi = 96 }); defer ft_font.deinit(); try testing.expectEqual(font.face.Metrics{ @@ -531,7 +531,7 @@ test "metrics" { }, ft_font.metrics); // Resize should change metrics - try ft_font.setSize(.{ .points = 24 }); + try ft_font.setSize(.{ .points = 24, .xdpi = 96, .ydpi = 96 }); try testing.expectEqual(font.face.Metrics{ .cell_width = 16, .cell_height = 35,