mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-07-17 09:16:11 +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;
|
) 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(
|
return @as(
|
||||||
?*Font,
|
?*Font,
|
||||||
@ptrFromInt(@intFromPtr(c.CTFontCreateCopyWithAttributes(
|
@ptrFromInt(@intFromPtr(c.CTFontCreateCopyWithAttributes(
|
||||||
@ptrCast(self),
|
@ptrCast(self),
|
||||||
size,
|
size,
|
||||||
null,
|
@ptrCast(matrix),
|
||||||
@ptrCast(attrs),
|
@ptrCast(attrs),
|
||||||
))),
|
))),
|
||||||
) orelse Allocator.Error.OutOfMemory;
|
) orelse Allocator.Error.OutOfMemory;
|
||||||
@ -217,6 +222,6 @@ test "copy" {
|
|||||||
const font = try Font.createWithFontDescriptor(desc, 12);
|
const font = try Font.createWithFontDescriptor(desc, 12);
|
||||||
defer font.release();
|
defer font.release();
|
||||||
|
|
||||||
const f2 = try font.copyWithAttributes(14, null);
|
const f2 = try font.copyWithAttributes(14, null, null);
|
||||||
defer f2.release();
|
defer f2.release();
|
||||||
}
|
}
|
||||||
|
@ -231,7 +231,7 @@ pub fn init(
|
|||||||
if (try disco_it.next()) |face| {
|
if (try disco_it.next()) |face| {
|
||||||
log.info("font regular: {s}", .{try face.name()});
|
log.info("font regular: {s}", .{try face.name()});
|
||||||
try group.addFace(alloc, .regular, face);
|
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| {
|
if (config.@"font-family-bold") |family| {
|
||||||
var disco_it = try disco.discover(.{
|
var disco_it = try disco.discover(.{
|
||||||
@ -243,7 +243,7 @@ pub fn init(
|
|||||||
if (try disco_it.next()) |face| {
|
if (try disco_it.next()) |face| {
|
||||||
log.info("font bold: {s}", .{try face.name()});
|
log.info("font bold: {s}", .{try face.name()});
|
||||||
try group.addFace(alloc, .bold, face);
|
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| {
|
if (config.@"font-family-italic") |family| {
|
||||||
var disco_it = try disco.discover(.{
|
var disco_it = try disco.discover(.{
|
||||||
@ -255,7 +255,7 @@ pub fn init(
|
|||||||
if (try disco_it.next()) |face| {
|
if (try disco_it.next()) |face| {
|
||||||
log.info("font italic: {s}", .{try face.name()});
|
log.info("font italic: {s}", .{try face.name()});
|
||||||
try group.addFace(alloc, .italic, face);
|
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| {
|
if (config.@"font-family-bold-italic") |family| {
|
||||||
var disco_it = try disco.discover(.{
|
var disco_it = try disco.discover(.{
|
||||||
@ -268,7 +268,7 @@ pub fn init(
|
|||||||
if (try disco_it.next()) |face| {
|
if (try disco_it.next()) |face| {
|
||||||
log.info("font bold+italic: {s}", .{try face.name()});
|
log.info("font bold+italic: {s}", .{try face.name()});
|
||||||
try group.addFace(alloc, .bold_italic, face);
|
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)),
|
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
|
// Emoji fallback. We don't include this on Mac since Mac is expected
|
||||||
// to always have the Apple Emoji available.
|
// to always have the Apple Emoji available.
|
||||||
if (builtin.os.tag != .macos or font.Discover == void) {
|
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);
|
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).
|
/// The loaded face (once loaded).
|
||||||
face: ?Face = null,
|
face: ?Face = null,
|
||||||
|
|
||||||
@ -61,6 +72,13 @@ pub const CoreText = struct {
|
|||||||
self.font.release();
|
self.font.release();
|
||||||
self.* = undefined;
|
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.
|
/// 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;
|
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.
|
/// The wasm-compatible API.
|
||||||
pub const Wasm = struct {
|
pub const Wasm = struct {
|
||||||
const wasm = @import("../os/wasm.zig");
|
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);
|
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.
|
/// Resize the fonts to the desired size.
|
||||||
pub fn setSize(self: *Group, size: font.face.DesiredSize) !void {
|
pub fn setSize(self: *Group, size: font.face.DesiredSize) !void {
|
||||||
// Note: there are some issues here with partial failure. We don't
|
// 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 we can find the exact value, then return that.
|
||||||
if (self.indexForCodepointExact(cp, style, p)) |value| return value;
|
if (self.indexForCodepointExact(cp, style, p)) |value| return value;
|
||||||
|
|
||||||
// Try looking for another font that will satisfy this request.
|
// If we're not a regular font style, try looking for a regular font
|
||||||
if (font.Discover != void) {
|
// 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: {
|
if (self.discover) |*disco| discover: {
|
||||||
var disco_it = disco.discover(.{
|
var disco_it = disco.discover(.{
|
||||||
.codepoint = cp,
|
.codepoint = cp,
|
||||||
|
@ -20,6 +20,16 @@ pub const Face = struct {
|
|||||||
/// Metrics for this font face. These are useful for renderers.
|
/// Metrics for this font face. These are useful for renderers.
|
||||||
metrics: font.face.Metrics,
|
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.
|
/// 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, size: font.face.DesiredSize) !Face {
|
||||||
_ = lib;
|
_ = lib;
|
||||||
@ -47,7 +57,7 @@ pub const Face = struct {
|
|||||||
// 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);
|
const ct_font = try base.copyWithAttributes(@floatFromInt(size.pixels()), null, null);
|
||||||
errdefer ct_font.release();
|
errdefer ct_font.release();
|
||||||
|
|
||||||
var hb_font = try harfbuzz.coretext.createFont(ct_font);
|
var hb_font = try harfbuzz.coretext.createFont(ct_font);
|
||||||
@ -69,6 +79,14 @@ pub const Face = struct {
|
|||||||
self.* = undefined;
|
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
|
/// 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, size: font.face.DesiredSize) !void {
|
||||||
|
Reference in New Issue
Block a user