font: specific codepoint lookup in internals

This commit is contained in:
Mitchell Hashimoto
2022-11-17 15:49:14 -08:00
parent b91cd8e41c
commit da2942e083
7 changed files with 136 additions and 27 deletions

View File

@ -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);
}

View File

@ -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 {

View File

@ -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);
}
}

View File

@ -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);

View File

@ -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);

View File

@ -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));
}

View File

@ -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,