mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-07-17 01:06:08 +03:00
Merge pull request #179 from mitchellh/autoitalic
Auto-italicize regular font if no italic font is found (CoreText only)
This commit is contained in:
@ -18,13 +18,18 @@ pub const Font = opaque {
|
||||
) orelse Allocator.Error.OutOfMemory;
|
||||
}
|
||||
|
||||
pub fn copyWithAttributes(self: *Font, size: f32, attrs: ?*text.FontDescriptor) Allocator.Error!*Font {
|
||||
pub fn copyWithAttributes(
|
||||
self: *Font,
|
||||
size: f32,
|
||||
matrix: ?*const graphics.AffineTransform,
|
||||
attrs: ?*text.FontDescriptor,
|
||||
) Allocator.Error!*Font {
|
||||
return @as(
|
||||
?*Font,
|
||||
@ptrFromInt(@intFromPtr(c.CTFontCreateCopyWithAttributes(
|
||||
@ptrCast(self),
|
||||
size,
|
||||
null,
|
||||
@ptrCast(matrix),
|
||||
@ptrCast(attrs),
|
||||
))),
|
||||
) orelse Allocator.Error.OutOfMemory;
|
||||
@ -217,6 +222,6 @@ test "copy" {
|
||||
const font = try Font.createWithFontDescriptor(desc, 12);
|
||||
defer font.release();
|
||||
|
||||
const f2 = try font.copyWithAttributes(14, null);
|
||||
const f2 = try font.copyWithAttributes(14, null, null);
|
||||
defer f2.release();
|
||||
}
|
||||
|
@ -231,7 +231,7 @@ pub fn init(
|
||||
if (try disco_it.next()) |face| {
|
||||
log.info("font regular: {s}", .{try face.name()});
|
||||
try group.addFace(alloc, .regular, face);
|
||||
} else std.log.warn("font-family not found: {s}", .{family});
|
||||
} else log.warn("font-family not found: {s}", .{family});
|
||||
}
|
||||
if (config.@"font-family-bold") |family| {
|
||||
var disco_it = try disco.discover(.{
|
||||
@ -243,7 +243,7 @@ pub fn init(
|
||||
if (try disco_it.next()) |face| {
|
||||
log.info("font bold: {s}", .{try face.name()});
|
||||
try group.addFace(alloc, .bold, face);
|
||||
} else std.log.warn("font-family-bold not found: {s}", .{family});
|
||||
} else log.warn("font-family-bold not found: {s}", .{family});
|
||||
}
|
||||
if (config.@"font-family-italic") |family| {
|
||||
var disco_it = try disco.discover(.{
|
||||
@ -255,7 +255,7 @@ pub fn init(
|
||||
if (try disco_it.next()) |face| {
|
||||
log.info("font italic: {s}", .{try face.name()});
|
||||
try group.addFace(alloc, .italic, face);
|
||||
} else std.log.warn("font-family-italic not found: {s}", .{family});
|
||||
} else log.warn("font-family-italic not found: {s}", .{family});
|
||||
}
|
||||
if (config.@"font-family-bold-italic") |family| {
|
||||
var disco_it = try disco.discover(.{
|
||||
@ -268,7 +268,7 @@ pub fn init(
|
||||
if (try disco_it.next()) |face| {
|
||||
log.info("font bold+italic: {s}", .{try face.name()});
|
||||
try group.addFace(alloc, .bold_italic, face);
|
||||
} else std.log.warn("font-family-bold-italic not found: {s}", .{family});
|
||||
} else log.warn("font-family-bold-italic not found: {s}", .{family});
|
||||
}
|
||||
}
|
||||
|
||||
@ -284,6 +284,26 @@ pub fn init(
|
||||
font.DeferredFace.initLoaded(try font.Face.init(font_lib, face_bold_ttf, font_size)),
|
||||
);
|
||||
|
||||
// If we support auto-italicization and we don't have an italic face,
|
||||
// then we can try to auto-italicize our regular face.
|
||||
if (comptime font.DeferredFace.canItalicize()) {
|
||||
if (group.getFace(.italic) == null) {
|
||||
if (group.getFace(.regular)) |regular| {
|
||||
if (try regular.italicize()) |face| {
|
||||
log.info("font auto-italicized: {s}", .{try face.name()});
|
||||
try group.addFace(alloc, .italic, face);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// We don't support auto-italics. If we don't have an italic font
|
||||
// face let the user know so they aren't surprised (if they look
|
||||
// at logs).
|
||||
if (group.getFace(.italic) == null) {
|
||||
log.warn("no italic font face available, italics will not render", .{});
|
||||
}
|
||||
}
|
||||
|
||||
// Emoji fallback. We don't include this on Mac since Mac is expected
|
||||
// to always have the Apple Emoji available.
|
||||
if (builtin.os.tag != .macos or font.Discover == void) {
|
||||
|
@ -19,6 +19,17 @@ const Presentation = @import("main.zig").Presentation;
|
||||
|
||||
const log = std.log.scoped(.deferred_face);
|
||||
|
||||
/// The struct used for deferred face state.
|
||||
///
|
||||
/// TODO: Change the "fc", "ct", "wc" fields in @This to just use one field
|
||||
/// with the state since there should be no world in which multiple are used.
|
||||
const FaceState = switch (options.backend) {
|
||||
.freetype => void,
|
||||
.fontconfig_freetype => Fontconfig,
|
||||
.coretext_freetype, .coretext => CoreText,
|
||||
.web_canvas => WebCanvas,
|
||||
};
|
||||
|
||||
/// The loaded face (once loaded).
|
||||
face: ?Face = null,
|
||||
|
||||
@ -61,6 +72,13 @@ pub const CoreText = struct {
|
||||
self.font.release();
|
||||
self.* = undefined;
|
||||
}
|
||||
|
||||
/// Auto-italicize the font by applying a skew.
|
||||
pub fn italicize(self: *const CoreText) !CoreText {
|
||||
const ct_font = try self.font.copyWithAttributes(0.0, &Face.italic_skew, null);
|
||||
errdefer ct_font.release();
|
||||
return .{ .font = ct_font };
|
||||
}
|
||||
};
|
||||
|
||||
/// WebCanvas specific data. This is only present when building with canvas.
|
||||
@ -333,6 +351,40 @@ pub fn hasCodepoint(self: DeferredFace, cp: u32, p: ?Presentation) bool {
|
||||
unreachable;
|
||||
}
|
||||
|
||||
/// Returns true if our deferred font implementation supports auto-itacilization.
|
||||
pub fn canItalicize() bool {
|
||||
return @hasDecl(FaceState, "italicize") and @hasDecl(Face, "italicize");
|
||||
}
|
||||
|
||||
/// Returns a new deferred face with the italicized version of this face
|
||||
/// by applying a skew. This is NOT TRUE italics. You should use the discovery
|
||||
/// mechanism to try to find an italic font. This is a fallback for when
|
||||
/// that fails.
|
||||
pub fn italicize(self: *const DeferredFace) !?DeferredFace {
|
||||
if (comptime !canItalicize()) return null;
|
||||
|
||||
var result: DeferredFace = .{};
|
||||
|
||||
if (self.face) |face| {
|
||||
result.face = try face.italicize();
|
||||
}
|
||||
|
||||
switch (options.backend) {
|
||||
.freetype => {},
|
||||
.fontconfig_freetype => if (self.fc) |*fc| {
|
||||
result.fc = try fc.italicize();
|
||||
},
|
||||
.coretext, .coretext_freetype => if (self.ct) |*ct| {
|
||||
result.ct = try ct.italicize();
|
||||
},
|
||||
.web_canvas => if (self.wc) |*wc| {
|
||||
result.wc = try wc.italicize();
|
||||
},
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// The wasm-compatible API.
|
||||
pub const Wasm = struct {
|
||||
const wasm = @import("../os/wasm.zig");
|
||||
|
@ -99,6 +99,15 @@ pub fn addFace(self: *Group, alloc: Allocator, style: Style, face: DeferredFace)
|
||||
try list.append(alloc, face);
|
||||
}
|
||||
|
||||
/// Get the face for the given style. This will always return the first
|
||||
/// face (if it exists). The returned pointer is only valid as long as
|
||||
/// the faces do not change.
|
||||
pub fn getFace(self: *Group, style: Style) ?*DeferredFace {
|
||||
const list = self.faces.getPtr(style);
|
||||
if (list.items.len == 0) return null;
|
||||
return &list.items[0];
|
||||
}
|
||||
|
||||
/// Resize the fonts to the desired size.
|
||||
pub fn setSize(self: *Group, size: font.face.DesiredSize) !void {
|
||||
// Note: there are some issues here with partial failure. We don't
|
||||
@ -189,8 +198,15 @@ pub fn indexForCodepoint(
|
||||
// If we can find the exact value, then return that.
|
||||
if (self.indexForCodepointExact(cp, style, p)) |value| return value;
|
||||
|
||||
// Try looking for another font that will satisfy this request.
|
||||
if (font.Discover != void) {
|
||||
// If we're not a regular font style, try looking for a regular font
|
||||
// that will satisfy this request. Blindly looking for unmatched styled
|
||||
// fonts to satisfy one codepoint results in some ugly rendering.
|
||||
if (style != .regular) {
|
||||
if (self.indexForCodepoint(cp, .regular, p)) |value| return value;
|
||||
}
|
||||
|
||||
// If we are regular, try looking for a fallback using discovery.
|
||||
if (style == .regular and font.Discover != void) {
|
||||
if (self.discover) |*disco| discover: {
|
||||
var disco_it = disco.discover(.{
|
||||
.codepoint = cp,
|
||||
|
@ -20,6 +20,16 @@ pub const Face = struct {
|
||||
/// Metrics for this font face. These are useful for renderers.
|
||||
metrics: font.face.Metrics,
|
||||
|
||||
/// The matrix applied to a regular font to auto-italicize it.
|
||||
pub const italic_skew = macos.graphics.AffineTransform{
|
||||
.a = 1,
|
||||
.b = 0,
|
||||
.c = 0.267949, // approx. tan(15)
|
||||
.d = 1,
|
||||
.tx = 0,
|
||||
.ty = 0,
|
||||
};
|
||||
|
||||
/// 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 {
|
||||
_ = lib;
|
||||
@ -47,7 +57,7 @@ pub const Face = struct {
|
||||
// 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
|
||||
// function called "pixels".
|
||||
const ct_font = try base.copyWithAttributes(@floatFromInt(size.pixels()), null);
|
||||
const ct_font = try base.copyWithAttributes(@floatFromInt(size.pixels()), null, null);
|
||||
errdefer ct_font.release();
|
||||
|
||||
var hb_font = try harfbuzz.coretext.createFont(ct_font);
|
||||
@ -69,6 +79,14 @@ pub const Face = struct {
|
||||
self.* = undefined;
|
||||
}
|
||||
|
||||
/// Return a new face that is the same as this but has a transformation
|
||||
/// matrix applied to italicize it.
|
||||
pub fn italicize(self: *const Face) !Face {
|
||||
const ct_font = try self.font.copyWithAttributes(0.0, &italic_skew, null);
|
||||
defer ct_font.release();
|
||||
return try initFontCopy(ct_font, .{ .points = 0 });
|
||||
}
|
||||
|
||||
/// Resize the font in-place. If this succeeds, the caller is responsible
|
||||
/// for clearing any glyph caches, font atlas data, etc.
|
||||
pub fn setSize(self: *Face, size: font.face.DesiredSize) !void {
|
||||
|
Reference in New Issue
Block a user