font: rework font init to use a struct with modifiersets everywhere

This commit is contained in:
Mitchell Hashimoto
2023-10-04 17:23:57 -07:00
parent 969960a10b
commit 54b9b45a7f
8 changed files with 118 additions and 33 deletions

View File

@ -224,6 +224,13 @@ pub fn init(
var group = try font.Group.init(alloc, font_lib, font_size); var group = try font.Group.init(alloc, font_lib, font_size);
errdefer group.deinit(); errdefer group.deinit();
// Setup our font metric modifiers if we have any.
group.metric_modifiers = set: {
var set: font.face.Metrics.ModifierSet = .{};
errdefer set.deinit(alloc);
break :set null;
};
// If we have codepoint mappings, set those. // If we have codepoint mappings, set those.
if (config.@"font-codepoint-map".map.list.len > 0) { if (config.@"font-codepoint-map".map.list.len > 0) {
group.codepoint_map = config.@"font-codepoint-map".map; group.codepoint_map = config.@"font-codepoint-map".map;
@ -306,11 +313,11 @@ pub fn init(
// Our built-in font will be used as a backup // Our built-in font will be used as a backup
_ = try group.addFace( _ = try group.addFace(
.regular, .regular,
.{ .loaded = try font.Face.init(font_lib, face_ttf, font_size) }, .{ .loaded = try font.Face.init(font_lib, face_ttf, group.faceOptions()) },
); );
_ = try group.addFace( _ = try group.addFace(
.bold, .bold,
.{ .loaded = try font.Face.init(font_lib, face_bold_ttf, font_size) }, .{ .loaded = try font.Face.init(font_lib, face_bold_ttf, group.faceOptions()) },
); );
// Auto-italicize if we have to. // Auto-italicize if we have to.
@ -321,11 +328,11 @@ pub fn init(
if (builtin.os.tag != .macos or font.Discover == void) { if (builtin.os.tag != .macos or font.Discover == void) {
_ = try group.addFace( _ = try group.addFace(
.regular, .regular,
.{ .loaded = try font.Face.init(font_lib, face_emoji_ttf, font_size) }, .{ .loaded = try font.Face.init(font_lib, face_emoji_ttf, group.faceOptions()) },
); );
_ = try group.addFace( _ = try group.addFace(
.regular, .regular,
.{ .loaded = try font.Face.init(font_lib, face_emoji_text_ttf, font_size) }, .{ .loaded = try font.Face.init(font_lib, face_emoji_text_ttf, group.faceOptions()) },
); );
} }

View File

@ -15,6 +15,7 @@ const cli = @import("../cli.zig");
const Key = @import("key.zig").Key; const Key = @import("key.zig").Key;
const KeyValue = @import("key.zig").Value; const KeyValue = @import("key.zig").Value;
const ErrorList = @import("ErrorList.zig"); const ErrorList = @import("ErrorList.zig");
const MetricModifier = fontpkg.face.Metrics.Modifier;
const log = std.log.scoped(.config); const log = std.log.scoped(.config);
@ -122,6 +123,22 @@ const c = @cImport({
/// currently on macOS. /// currently on macOS.
@"font-thicken": bool = false, @"font-thicken": bool = false,
/// All of the configurations behavior adjust various metrics determined
/// by the font. The values can be integers (1, -1, etc.) or a percentage
/// (20%, -15%, etc.). In each case, the values represent the amount to
/// change the original value.
///
/// For example, a value of "1" increases the value by 1; it does not set
/// it to literally 1. A value of "20%" increases the value by 20%. And so
/// on.
///
/// There is little to no validation on these values so the wrong values
/// (i.e. "-100%") can cause the terminal to be unusable. Use with caution
/// and reason.
@"adjust-cell-width": ?MetricModifier = null,
@"adjust-cell-height": ?MetricModifier = null,
@"adjust-font-baseline": ?MetricModifier = null,
/// Background color for the window. /// Background color for the window.
background: Color = .{ .r = 0x28, .g = 0x2C, .b = 0x34 }, background: Color = .{ .r = 0x28, .g = 0x2C, .b = 0x34 },

View File

@ -192,7 +192,8 @@ fn loadCoreText(
) !Face { ) !Face {
_ = lib; _ = lib;
const ct = self.ct.?; const ct = self.ct.?;
return try Face.initFontCopy(ct.font, size); // TODO: make options
return try Face.initFontCopy(ct.font, .{ .size = size });
} }
fn loadCoreTextFreetype( fn loadCoreTextFreetype(

View File

@ -71,6 +71,10 @@ lib: Library,
/// The desired font size. All fonts in a group must share the same size. /// The desired font size. All fonts in a group must share the same size.
size: font.face.DesiredSize, size: font.face.DesiredSize,
/// Metric modifiers to apply to loaded fonts. The Group takes ownership
/// over the memory and will use the associated allocator to free it.
metric_modifiers: ?font.face.Metrics.ModifierSet = null,
/// The available faces we have. This shouldn't be modified manually. /// The available faces we have. This shouldn't be modified manually.
/// Instead, use the functions available on Group. /// Instead, use the functions available on Group.
faces: StyleArray, faces: StyleArray,
@ -139,9 +143,20 @@ pub fn deinit(self: *Group) void {
} }
} }
if (self.metric_modifiers) |*v| v.deinit(self.alloc);
self.descriptor_cache.deinit(self.alloc); self.descriptor_cache.deinit(self.alloc);
} }
/// Returns the options for initializing a face based on the options associated
/// with this font group.
pub fn faceOptions(self: *const Group) font.face.Options {
return .{
.size = self.size,
.metric_modifiers = if (self.metric_modifiers) |*v| v else null,
};
}
/// Add a face to the list for the given style. This face will be added as /// Add a face to the list for the given style. This face will be added as
/// next in priority if others exist already, i.e. it'll be the _last_ to be /// next in priority if others exist already, i.e. it'll be the _last_ to be
/// searched for a glyph in that list. /// searched for a glyph in that list.
@ -205,7 +220,7 @@ pub fn setSize(self: *Group, size: font.face.DesiredSize) !void {
while (it.next()) |entry| { while (it.next()) |entry| {
for (entry.value.items) |*elem| switch (elem.*) { for (entry.value.items) |*elem| switch (elem.*) {
.deferred => continue, .deferred => continue,
.loaded => |*f| try f.setSize(size), .loaded => |*f| try f.setSize(.{ .size = size }),
}; };
} }
@ -623,13 +638,22 @@ test {
var group = try init(alloc, lib, .{ .points = 12 }); var group = try init(alloc, lib, .{ .points = 12 });
defer group.deinit(); defer group.deinit();
_ = try group.addFace(.regular, .{ .loaded = try Face.init(lib, testFont, .{ .points = 12 }) }); _ = try group.addFace(
.regular,
.{ .loaded = try Face.init(lib, testFont, .{ .size = .{ .points = 12 } }) },
);
if (font.options.backend != .coretext) { if (font.options.backend != .coretext) {
// Coretext doesn't support Noto's format // Coretext doesn't support Noto's format
_ = try group.addFace(.regular, .{ .loaded = try Face.init(lib, testEmoji, .{ .points = 12 }) }); _ = try group.addFace(
.regular,
.{ .loaded = try Face.init(lib, testEmoji, .{ .size = .{ .points = 12 } }) },
);
} }
_ = try group.addFace(.regular, .{ .loaded = try Face.init(lib, testEmojiText, .{ .points = 12 }) }); _ = try group.addFace(
.regular,
.{ .loaded = try Face.init(lib, testEmojiText, .{ .size = .{ .points = 12 } }) },
);
// Should find all visible ASCII // Should find all visible ASCII
var i: u32 = 32; var i: u32 = 32;
@ -694,9 +718,10 @@ test "disabled font style" {
group.styles.set(.bold, false); group.styles.set(.bold, false);
// Same font but we can test the style in the index // Same font but we can test the style in the index
_ = try group.addFace(.regular, .{ .loaded = try Face.init(lib, testFont, .{ .points = 12 }) }); const opts: font.face.Options = .{ .size = .{ .points = 12 } };
_ = try group.addFace(.bold, .{ .loaded = try Face.init(lib, testFont, .{ .points = 12 }) }); _ = try group.addFace(.regular, .{ .loaded = try Face.init(lib, testFont, opts) });
_ = try group.addFace(.italic, .{ .loaded = try Face.init(lib, testFont, .{ .points = 12 }) }); _ = try group.addFace(.bold, .{ .loaded = try Face.init(lib, testFont, opts) });
_ = try group.addFace(.italic, .{ .loaded = try Face.init(lib, testFont, opts) });
// Regular should work fine // Regular should work fine
{ {
@ -731,16 +756,17 @@ test "face count limit" {
var lib = try Library.init(); var lib = try Library.init();
defer lib.deinit(); defer lib.deinit();
var group = try init(alloc, lib, .{ .points = 12 }); const opts: font.face.Options = .{ .size = .{ .points = 12 } };
var group = try init(alloc, lib, opts.size);
defer group.deinit(); defer group.deinit();
for (0..FontIndex.Special.start - 1) |_| { for (0..FontIndex.Special.start - 1) |_| {
_ = try group.addFace(.regular, .{ .loaded = try Face.init(lib, testFont, .{ .points = 12 }) }); _ = try group.addFace(.regular, .{ .loaded = try Face.init(lib, testFont, opts) });
} }
try testing.expectError(error.GroupFull, group.addFace( try testing.expectError(error.GroupFull, group.addFace(
.regular, .regular,
.{ .loaded = try Face.init(lib, testFont, .{ .points = 12 }) }, .{ .loaded = try Face.init(lib, testFont, opts) },
)); ));
} }
@ -790,7 +816,11 @@ test "resize" {
var group = try init(alloc, lib, .{ .points = 12, .xdpi = 96, .ydpi = 96 }); var group = try init(alloc, lib, .{ .points = 12, .xdpi = 96, .ydpi = 96 });
defer group.deinit(); defer group.deinit();
_ = try group.addFace(.regular, .{ .loaded = try Face.init(lib, testFont, .{ .points = 12, .xdpi = 96, .ydpi = 96 }) }); _ = try group.addFace(.regular, .{ .loaded = try Face.init(
lib,
testFont,
.{ .size = .{ .points = 12, .xdpi = 96, .ydpi = 96 } },
) });
// Load a letter // Load a letter
{ {
@ -881,7 +911,11 @@ test "faceFromIndex returns pointer" {
var group = try init(alloc, lib, .{ .points = 12, .xdpi = 96, .ydpi = 96 }); var group = try init(alloc, lib, .{ .points = 12, .xdpi = 96, .ydpi = 96 });
defer group.deinit(); defer group.deinit();
_ = try group.addFace(.regular, .{ .loaded = try Face.init(lib, testFont, .{ .points = 12, .xdpi = 96, .ydpi = 96 }) }); _ = try group.addFace(.regular, .{ .loaded = try Face.init(
lib,
testFont,
.{ .size = .{ .points = 12, .xdpi = 96, .ydpi = 96 } },
) });
{ {
const idx = group.indexForCodepoint('A', .regular, null).?; const idx = group.indexForCodepoint('A', .regular, null).?;

View File

@ -184,7 +184,7 @@ test {
// Setup group // Setup group
_ = try cache.group.addFace( _ = try cache.group.addFace(
.regular, .regular,
.{ .loaded = try Face.init(lib, testFont, .{ .points = 12 }) }, .{ .loaded = try Face.init(lib, testFont, .{ .size = .{ .points = 12 } }) },
); );
var group = cache.group; var group = cache.group;
@ -340,7 +340,11 @@ test "resize" {
// Setup group // Setup group
_ = try cache.group.addFace( _ = try cache.group.addFace(
.regular, .regular,
.{ .loaded = try Face.init(lib, testFont, .{ .points = 12, .xdpi = 96, .ydpi = 96 }) }, .{ .loaded = try Face.init(
lib,
testFont,
.{ .size = .{ .points = 12, .xdpi = 96, .ydpi = 96 } },
) },
); );
// Load a letter // Load a letter

View File

@ -22,6 +22,12 @@ pub const Face = switch (options.backend) {
/// using whatever platform method you can. /// using whatever platform method you can.
pub const default_dpi = if (builtin.os.tag == .macos) 72 else 96; pub const default_dpi = if (builtin.os.tag == .macos) 72 else 96;
/// Options for initializing a font face.
pub const Options = struct {
size: DesiredSize,
metric_modifiers: ?*const Metrics.ModifierSet = null,
};
/// The desired size for loading a font. /// The desired size for loading a font.
pub const DesiredSize = struct { pub const DesiredSize = struct {
// Desired size in points // Desired size in points

View File

@ -36,7 +36,7 @@ pub const Face = struct {
}; };
/// Initialize a CoreText-based font from a TTF/TTC in memory. /// Initialize a CoreText-based font from a TTF/TTC in memory.
pub fn init(lib: font.Library, source: [:0]const u8, size: font.face.DesiredSize) !Face { pub fn init(lib: font.Library, source: [:0]const u8, opts: font.face.Options) !Face {
_ = lib; _ = lib;
const data = try macos.foundation.Data.createWithBytesNoCopy(source); const data = try macos.foundation.Data.createWithBytesNoCopy(source);
@ -51,18 +51,22 @@ pub const Face = struct {
const ct_font = try macos.text.Font.createWithFontDescriptor(desc, 12); const ct_font = try macos.text.Font.createWithFontDescriptor(desc, 12);
defer ct_font.release(); defer ct_font.release();
return try initFontCopy(ct_font, size); return try initFontCopy(ct_font, opts);
} }
/// Initialize a CoreText-based face from another initialized font face /// Initialize a CoreText-based face from another initialized font face
/// but with a new size. This is often how CoreText fonts are initialized /// but with a new size. This is often how CoreText fonts are initialized
/// because the font is loaded at a default size during discovery, and then /// because the font is loaded at a default size during discovery, and then
/// adjusted to the final size for final load. /// adjusted to the final size for final load.
pub fn initFontCopy(base: *macos.text.Font, size: font.face.DesiredSize) !Face { pub fn initFontCopy(base: *macos.text.Font, opts: font.face.Options) !Face {
// Create a copy. The copyWithAttributes docs say the size is in points, // Create a copy. The copyWithAttributes docs say the size is in points,
// but we need to scale the points by the DPI and to do that we use our // but we need to scale the points by the DPI and to do that we use our
// function called "pixels". // function called "pixels".
const ct_font = try base.copyWithAttributes(@floatFromInt(size.pixels()), null, null); const ct_font = try base.copyWithAttributes(
@floatFromInt(opts.size.pixels()),
null,
null,
);
errdefer ct_font.release(); errdefer ct_font.release();
return try initFont(ct_font); return try initFont(ct_font);
@ -161,9 +165,9 @@ pub const Face = struct {
/// Resize the font in-place. If this succeeds, the caller is responsible /// Resize the font in-place. If this succeeds, the caller is responsible
/// for clearing any glyph caches, font atlas data, etc. /// for clearing any glyph caches, font atlas data, etc.
pub fn setSize(self: *Face, size: font.face.DesiredSize) !void { pub fn setSize(self: *Face, opts: font.face.Options) !void {
// We just create a copy and replace ourself // We just create a copy and replace ourself
const face = try initFontCopy(self.font, size); const face = try initFontCopy(self.font, opts);
self.deinit(); self.deinit();
self.* = face; self.* = face;
} }
@ -514,7 +518,7 @@ test {
const ct_font = try macos.text.Font.createWithFontDescriptor(desc, 12); const ct_font = try macos.text.Font.createWithFontDescriptor(desc, 12);
defer ct_font.release(); defer ct_font.release();
var face = try Face.initFontCopy(ct_font, .{ .points = 12 }); var face = try Face.initFontCopy(ct_font, .{ .size = .{ .points = 12 } });
defer face.deinit(); defer face.deinit();
try testing.expectEqual(font.Presentation.text, face.presentation); try testing.expectEqual(font.Presentation.text, face.presentation);
@ -537,7 +541,7 @@ test "emoji" {
const ct_font = try macos.text.Font.createWithFontDescriptor(desc, 12); const ct_font = try macos.text.Font.createWithFontDescriptor(desc, 12);
defer ct_font.release(); defer ct_font.release();
var face = try Face.initFontCopy(ct_font, .{ .points = 18 }); var face = try Face.initFontCopy(ct_font, .{ .size = .{ .points = 18 } });
defer face.deinit(); defer face.deinit();
// Presentation // Presentation
@ -558,7 +562,7 @@ test "in-memory" {
var lib = try font.Library.init(); var lib = try font.Library.init();
defer lib.deinit(); defer lib.deinit();
var face = try Face.init(lib, testFont, .{ .points = 12 }); var face = try Face.init(lib, testFont, .{ .size = .{ .points = 12 } });
defer face.deinit(); defer face.deinit();
try testing.expectEqual(font.Presentation.text, face.presentation); try testing.expectEqual(font.Presentation.text, face.presentation);
@ -582,7 +586,7 @@ test "variable" {
var lib = try font.Library.init(); var lib = try font.Library.init();
defer lib.deinit(); defer lib.deinit();
var face = try Face.init(lib, testFont, .{ .points = 12 }); var face = try Face.init(lib, testFont, .{ .size = .{ .points = 12 } });
defer face.deinit(); defer face.deinit();
try testing.expectEqual(font.Presentation.text, face.presentation); try testing.expectEqual(font.Presentation.text, face.presentation);
@ -606,7 +610,7 @@ test "variable set variation" {
var lib = try font.Library.init(); var lib = try font.Library.init();
defer lib.deinit(); defer lib.deinit();
var face = try Face.init(lib, testFont, .{ .points = 12 }); var face = try Face.init(lib, testFont, .{ .size = .{ .points = 12 } });
defer face.deinit(); defer face.deinit();
try testing.expectEqual(font.Presentation.text, face.presentation); try testing.expectEqual(font.Presentation.text, face.presentation);

View File

@ -899,11 +899,19 @@ fn testShaper(alloc: Allocator) !TestShaper {
errdefer cache_ptr.*.deinit(alloc); errdefer cache_ptr.*.deinit(alloc);
// Setup group // Setup group
_ = try cache_ptr.group.addFace(.regular, .{ .loaded = try Face.init(lib, testFont, .{ .points = 12 }) }); _ = try cache_ptr.group.addFace(.regular, .{ .loaded = try Face.init(
lib,
testFont,
.{ .size = .{ .points = 12 } },
) });
if (font.options.backend != .coretext) { if (font.options.backend != .coretext) {
// Coretext doesn't support Noto's format // Coretext doesn't support Noto's format
_ = try cache_ptr.group.addFace(.regular, .{ .loaded = try Face.init(lib, testEmoji, .{ .points = 12 }) }); _ = try cache_ptr.group.addFace(.regular, .{ .loaded = try Face.init(
lib,
testEmoji,
.{ .size = .{ .points = 12 } },
) });
} else { } else {
// On CoreText we want to load Apple Emoji, we should have it. // On CoreText we want to load Apple Emoji, we should have it.
var disco = font.Discover.init(); var disco = font.Discover.init();
@ -918,7 +926,11 @@ fn testShaper(alloc: Allocator) !TestShaper {
errdefer face.deinit(); errdefer face.deinit();
_ = try cache_ptr.group.addFace(.regular, .{ .deferred = face }); _ = try cache_ptr.group.addFace(.regular, .{ .deferred = face });
} }
_ = try cache_ptr.group.addFace(.regular, .{ .loaded = try Face.init(lib, testEmojiText, .{ .points = 12 }) }); _ = try cache_ptr.group.addFace(.regular, .{ .loaded = try Face.init(
lib,
testEmojiText,
.{ .size = .{ .points = 12 } },
) });
var shaper = try Shaper.init(alloc, .{}); var shaper = try Shaper.init(alloc, .{});
errdefer shaper.deinit(); errdefer shaper.deinit();