mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-07-25 13:16:11 +03:00
font: Collection autoItalicize
This commit is contained in:
@ -2,6 +2,17 @@
|
|||||||
//! ordered by priority (per style). All fonts in a collection share the same
|
//! ordered by priority (per style). All fonts in a collection share the same
|
||||||
//! size so they can be used interchangeably in cases a glyph is missing in one
|
//! size so they can be used interchangeably in cases a glyph is missing in one
|
||||||
//! and present in another.
|
//! and present in another.
|
||||||
|
//!
|
||||||
|
//! The purpose of a collection is to store a list of fonts by style
|
||||||
|
//! and priority order. A collection does not handle searching for font
|
||||||
|
//! callbacks, rasterization, etc.
|
||||||
|
//!
|
||||||
|
//! The collection can contain both loaded and deferred faces. Deferred faces
|
||||||
|
//! typically use less memory while still providing some necessary information
|
||||||
|
//! such as codepoint support, presentation, etc. This is useful for looking
|
||||||
|
//! for fallback fonts as efficiently as possible. For example, when the glyph
|
||||||
|
//! "X" is not found, we can quickly search through deferred fonts rather
|
||||||
|
//! than loading the font completely.
|
||||||
const Collection = @This();
|
const Collection = @This();
|
||||||
|
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
@ -15,6 +26,8 @@ const Metrics = font.face.Metrics;
|
|||||||
const Presentation = font.Presentation;
|
const Presentation = font.Presentation;
|
||||||
const Style = font.Style;
|
const Style = font.Style;
|
||||||
|
|
||||||
|
const log = std.log.scoped(.font_collection);
|
||||||
|
|
||||||
/// 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 Collection.
|
/// Instead, use the functions available on Collection.
|
||||||
faces: StyleArray,
|
faces: StyleArray,
|
||||||
@ -139,6 +152,57 @@ pub fn getIndex(
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Automatically create an italicized font from the regular
|
||||||
|
/// font face if we don't have one already. If we already have
|
||||||
|
/// an italicized font face, this does nothing.
|
||||||
|
pub fn autoItalicize(self: *Collection, alloc: Allocator) !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;
|
||||||
|
|
||||||
|
// Find our first font that is text. This will force
|
||||||
|
// loading any deferred faces but we only load them until
|
||||||
|
// we find a text face. A text face is almost always the
|
||||||
|
// first face in the list.
|
||||||
|
for (0..list.items.len) |i| {
|
||||||
|
const face = try self.getFace(.{
|
||||||
|
.style = .regular,
|
||||||
|
.idx = @intCast(i),
|
||||||
|
});
|
||||||
|
if (face.presentation == .text) break :regular face;
|
||||||
|
}
|
||||||
|
|
||||||
|
// No regular text face found.
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
// We require loading options to auto-italicize.
|
||||||
|
const opts = self.load_options orelse return error.DeferredLoadingUnavailable;
|
||||||
|
|
||||||
|
// Try to italicize it.
|
||||||
|
const face = try regular.italicize(opts.faceOptions());
|
||||||
|
try italic_list.append(alloc, .{ .loaded = face });
|
||||||
|
|
||||||
|
var buf: [256]u8 = undefined;
|
||||||
|
if (face.name(&buf)) |name| {
|
||||||
|
log.info("font auto-italicized: {s}", .{name});
|
||||||
|
} else |_| {}
|
||||||
|
}
|
||||||
|
|
||||||
/// Packed array of all Style enum cases mapped to a growable list of faces.
|
/// Packed array of all Style enum cases mapped to a growable list of faces.
|
||||||
///
|
///
|
||||||
/// We use this data structure because there aren't many styles and all
|
/// We use this data structure because there aren't many styles and all
|
||||||
@ -444,3 +508,26 @@ test getIndex {
|
|||||||
try testing.expect(idx == null);
|
try testing.expect(idx == null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
test autoItalicize {
|
||||||
|
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 };
|
||||||
|
|
||||||
|
_ = try c.add(alloc, .regular, .{ .loaded = try Face.init(
|
||||||
|
lib,
|
||||||
|
testFont,
|
||||||
|
.{ .size = .{ .points = 12, .xdpi = 96, .ydpi = 96 } },
|
||||||
|
) });
|
||||||
|
|
||||||
|
try testing.expect(c.getIndex('A', .italic, .{ .any = {} }) == null);
|
||||||
|
try c.autoItalicize(alloc);
|
||||||
|
try testing.expect(c.getIndex('A', .italic, .{ .any = {} }) != null);
|
||||||
|
}
|
||||||
|
Reference in New Issue
Block a user