mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-08-02 14:57:31 +03:00
font/coretext: ability to set variation axes
This commit is contained in:
@ -39,6 +39,14 @@ pub const Font = opaque {
|
|||||||
c.CFRelease(self);
|
c.CFRelease(self);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn retain(self: *Font) void {
|
||||||
|
_ = c.CFRetain(self);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn copyDescriptor(self: *Font) *text.FontDescriptor {
|
||||||
|
return @ptrCast(@constCast(c.CTFontCopyFontDescriptor(@ptrCast(self))));
|
||||||
|
}
|
||||||
|
|
||||||
pub fn getGlyphsForCharacters(self: *Font, chars: []const u16, glyphs: []graphics.Glyph) bool {
|
pub fn getGlyphsForCharacters(self: *Font, chars: []const u16, glyphs: []graphics.Glyph) bool {
|
||||||
assert(chars.len == glyphs.len);
|
assert(chars.len == glyphs.len);
|
||||||
return c.CTFontGetGlyphsForCharacters(
|
return c.CTFontGetGlyphsForCharacters(
|
||||||
|
@ -31,6 +31,21 @@ pub const FontDescriptor = opaque {
|
|||||||
) orelse Allocator.Error.OutOfMemory;
|
) orelse Allocator.Error.OutOfMemory;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn createCopyWithVariation(
|
||||||
|
original: *FontDescriptor,
|
||||||
|
id: *foundation.Number,
|
||||||
|
value: f64,
|
||||||
|
) Allocator.Error!*FontDescriptor {
|
||||||
|
return @as(
|
||||||
|
?*FontDescriptor,
|
||||||
|
@ptrCast(@constCast(c.CTFontDescriptorCreateCopyWithVariation(
|
||||||
|
@ptrCast(original),
|
||||||
|
@ptrCast(id),
|
||||||
|
value,
|
||||||
|
))),
|
||||||
|
) orelse Allocator.Error.OutOfMemory;
|
||||||
|
}
|
||||||
|
|
||||||
pub fn release(self: *FontDescriptor) void {
|
pub fn release(self: *FontDescriptor) void {
|
||||||
c.CFRelease(self);
|
c.CFRelease(self);
|
||||||
}
|
}
|
||||||
|
@ -5,6 +5,7 @@ const fontconfig = @import("fontconfig");
|
|||||||
const macos = @import("macos");
|
const macos = @import("macos");
|
||||||
const options = @import("main.zig").options;
|
const options = @import("main.zig").options;
|
||||||
const DeferredFace = @import("main.zig").DeferredFace;
|
const DeferredFace = @import("main.zig").DeferredFace;
|
||||||
|
const Variation = @import("main.zig").face.Variation;
|
||||||
|
|
||||||
const log = std.log.scoped(.discovery);
|
const log = std.log.scoped(.discovery);
|
||||||
|
|
||||||
@ -43,6 +44,11 @@ pub const Descriptor = struct {
|
|||||||
bold: bool = false,
|
bold: bool = false,
|
||||||
italic: bool = false,
|
italic: bool = false,
|
||||||
|
|
||||||
|
/// Variation axes to apply to the font. This also impacts searching
|
||||||
|
/// for fonts since fonts with the ability to set these variations
|
||||||
|
/// will be preferred, but not guaranteed.
|
||||||
|
variations: []const Variation = &.{},
|
||||||
|
|
||||||
/// Convert to Fontconfig pattern to use for lookup. The pattern does
|
/// Convert to Fontconfig pattern to use for lookup. The pattern does
|
||||||
/// not have defaults filled/substituted (Fontconfig thing) so callers
|
/// not have defaults filled/substituted (Fontconfig thing) so callers
|
||||||
/// must still do this.
|
/// must still do this.
|
||||||
@ -149,7 +155,21 @@ pub const Descriptor = struct {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return try macos.text.FontDescriptor.createWithAttributes(@ptrCast(attrs));
|
// Build our descriptor from attrs
|
||||||
|
var desc = try macos.text.FontDescriptor.createWithAttributes(@ptrCast(attrs));
|
||||||
|
errdefer desc.release();
|
||||||
|
|
||||||
|
// Variations are built by copying the descriptor. I don't know a way
|
||||||
|
// to set it on attrs directly.
|
||||||
|
for (self.variations) |v| {
|
||||||
|
const id = try macos.foundation.Number.create(.int, @ptrCast(&v.id));
|
||||||
|
defer id.release();
|
||||||
|
const next = try desc.createCopyWithVariation(id, v.value);
|
||||||
|
desc.release();
|
||||||
|
desc = next;
|
||||||
|
}
|
||||||
|
|
||||||
|
return desc;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
const std = @import("std");
|
||||||
const builtin = @import("builtin");
|
const builtin = @import("builtin");
|
||||||
const options = @import("main.zig").options;
|
const options = @import("main.zig").options;
|
||||||
const freetype = @import("face/freetype.zig");
|
const freetype = @import("face/freetype.zig");
|
||||||
@ -36,6 +37,31 @@ pub const DesiredSize = struct {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/// A font variation setting. The best documentation for this I know of
|
||||||
|
/// is actually the CSS font-variation-settings property on MDN:
|
||||||
|
/// https://developer.mozilla.org/en-US/docs/Web/CSS/font-variation-settings
|
||||||
|
pub const Variation = struct {
|
||||||
|
id: Id,
|
||||||
|
value: f64,
|
||||||
|
|
||||||
|
pub const Id = packed struct(u32) {
|
||||||
|
d: u8,
|
||||||
|
c: u8,
|
||||||
|
b: u8,
|
||||||
|
a: u8,
|
||||||
|
|
||||||
|
pub fn init(v: *const [4]u8) Id {
|
||||||
|
return .{ .a = v[0], .b = v[1], .c = v[2], .d = v[3] };
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Converts the ID to a string. The return value is only valid
|
||||||
|
/// for the lifetime of the self pointer.
|
||||||
|
pub fn str(self: Id) [4]u8 {
|
||||||
|
return .{ self.a, self.b, self.c, self.d };
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
/// Metrics associated with the font that are useful for renderers to know.
|
/// Metrics associated with the font that are useful for renderers to know.
|
||||||
pub const Metrics = struct {
|
pub const Metrics = struct {
|
||||||
/// Recommended cell width and height for a monospace grid using this font.
|
/// Recommended cell width and height for a monospace grid using this font.
|
||||||
@ -77,3 +103,17 @@ pub const Foo = if (options.backend == .coretext) coretext.Face else void;
|
|||||||
test {
|
test {
|
||||||
@import("std").testing.refAllDecls(@This());
|
@import("std").testing.refAllDecls(@This());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
test "Variation.Id: wght should be 2003265652" {
|
||||||
|
const testing = std.testing;
|
||||||
|
const id = Variation.Id.init("wght");
|
||||||
|
try testing.expectEqual(@as(u32, 2003265652), @as(u32, @bitCast(id)));
|
||||||
|
try testing.expectEqualStrings("wght", &(id.str()));
|
||||||
|
}
|
||||||
|
|
||||||
|
test "Variation.Id: slnt should be 1936486004" {
|
||||||
|
const testing = std.testing;
|
||||||
|
const id: Variation.Id = .{ .a = 's', .b = 'l', .c = 'n', .d = 't' };
|
||||||
|
try testing.expectEqual(@as(u32, 1936486004), @as(u32, @bitCast(id)));
|
||||||
|
try testing.expectEqualStrings("slnt", &(id.str()));
|
||||||
|
}
|
||||||
|
@ -64,11 +64,33 @@ pub const Face = struct {
|
|||||||
const ct_font = try base.copyWithAttributes(@floatFromInt(size.pixels()), null, null);
|
const ct_font = try base.copyWithAttributes(@floatFromInt(size.pixels()), null, null);
|
||||||
errdefer ct_font.release();
|
errdefer ct_font.release();
|
||||||
|
|
||||||
|
return try initFont(ct_font);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Initialize a face with a CTFont. This will take ownership over
|
||||||
|
/// the CTFont. This does NOT copy or retain the CTFont.
|
||||||
|
pub fn initFont(ct_font: *macos.text.Font) !Face {
|
||||||
var hb_font = try harfbuzz.coretext.createFont(ct_font);
|
var hb_font = try harfbuzz.coretext.createFont(ct_font);
|
||||||
errdefer hb_font.destroy();
|
errdefer hb_font.destroy();
|
||||||
|
|
||||||
const traits = ct_font.getSymbolicTraits();
|
const traits = ct_font.getSymbolicTraits();
|
||||||
|
|
||||||
|
// Get variation axes
|
||||||
|
// if (ct_font.copyAttribute(.variation_axes)) |axes| {
|
||||||
|
// defer axes.release();
|
||||||
|
// const len = axes.getCount();
|
||||||
|
// for (0..len) |i| {
|
||||||
|
// const dict = axes.getValueAtIndex(macos.foundation.Dictionary, i);
|
||||||
|
// const Key = macos.text.FontVariationAxisKey;
|
||||||
|
// const name_ = dict.getValue(Key.name.Value(), Key.name.key());
|
||||||
|
// if (name_) |name_val| {
|
||||||
|
// var buf: [1024]u8 = undefined;
|
||||||
|
// const namestr = name_val.cstring(&buf, .utf8) orelse "";
|
||||||
|
// log.warn("AXES: {s}", .{namestr});
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
var result: Face = .{
|
var result: Face = .{
|
||||||
.font = ct_font,
|
.font = ct_font,
|
||||||
.hb_font = hb_font,
|
.hb_font = hb_font,
|
||||||
@ -89,8 +111,8 @@ pub const Face = struct {
|
|||||||
/// matrix applied to italicize it.
|
/// matrix applied to italicize it.
|
||||||
pub fn italicize(self: *const Face) !Face {
|
pub fn italicize(self: *const Face) !Face {
|
||||||
const ct_font = try self.font.copyWithAttributes(0.0, &italic_skew, null);
|
const ct_font = try self.font.copyWithAttributes(0.0, &italic_skew, null);
|
||||||
defer ct_font.release();
|
errdefer ct_font.release();
|
||||||
return try initFontCopy(ct_font, .{ .points = 0 });
|
return try initFont(ct_font);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the font name. If allocation is required, buf will be used,
|
/// Returns the font name. If allocation is required, buf will be used,
|
||||||
@ -115,6 +137,31 @@ pub const Face = struct {
|
|||||||
self.* = face;
|
self.* = face;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Set the variation axes for this font. This will modify this font
|
||||||
|
/// in-place.
|
||||||
|
pub fn setVariations(
|
||||||
|
self: *Face,
|
||||||
|
vs: []const font.face.Variation,
|
||||||
|
) !void {
|
||||||
|
// Create a new font descriptor with all the variations set.
|
||||||
|
var desc = self.font.copyDescriptor();
|
||||||
|
defer desc.release();
|
||||||
|
for (vs) |v| {
|
||||||
|
const id = try macos.foundation.Number.create(.int, @ptrCast(&v.id));
|
||||||
|
defer id.release();
|
||||||
|
const next = try desc.createCopyWithVariation(id, v.value);
|
||||||
|
desc.release();
|
||||||
|
desc = next;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize a font based on these attributes.
|
||||||
|
const ct_font = try self.font.copyWithAttributes(0, null, desc);
|
||||||
|
errdefer ct_font.release();
|
||||||
|
const face = try initFont(ct_font);
|
||||||
|
self.deinit();
|
||||||
|
self.* = face;
|
||||||
|
}
|
||||||
|
|
||||||
/// Returns the glyph index for the given Unicode code point. If this
|
/// Returns the glyph index for the given Unicode code point. If this
|
||||||
/// face doesn't support this glyph, null is returned.
|
/// face doesn't support this glyph, null is returned.
|
||||||
pub fn glyphIndex(self: Face, cp: u32) ?u32 {
|
pub fn glyphIndex(self: Face, cp: u32) ?u32 {
|
||||||
@ -492,3 +539,55 @@ test "in-memory" {
|
|||||||
_ = try face.renderGlyph(alloc, &atlas, face.glyphIndex(i).?, .{});
|
_ = try face.renderGlyph(alloc, &atlas, face.glyphIndex(i).?, .{});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
test "variable" {
|
||||||
|
const testing = std.testing;
|
||||||
|
const alloc = testing.allocator;
|
||||||
|
const testFont = @import("../test.zig").fontVariable;
|
||||||
|
|
||||||
|
var atlas = try font.Atlas.init(alloc, 512, .greyscale);
|
||||||
|
defer atlas.deinit(alloc);
|
||||||
|
|
||||||
|
var lib = try font.Library.init();
|
||||||
|
defer lib.deinit();
|
||||||
|
|
||||||
|
var face = try Face.init(lib, testFont, .{ .points = 12 });
|
||||||
|
defer face.deinit();
|
||||||
|
|
||||||
|
try testing.expectEqual(font.Presentation.text, face.presentation);
|
||||||
|
|
||||||
|
// Generate all visible ASCII
|
||||||
|
var i: u8 = 32;
|
||||||
|
while (i < 127) : (i += 1) {
|
||||||
|
try testing.expect(face.glyphIndex(i) != null);
|
||||||
|
_ = try face.renderGlyph(alloc, &atlas, face.glyphIndex(i).?, .{});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
test "variable set variation" {
|
||||||
|
const testing = std.testing;
|
||||||
|
const alloc = testing.allocator;
|
||||||
|
const testFont = @import("../test.zig").fontVariable;
|
||||||
|
|
||||||
|
var atlas = try font.Atlas.init(alloc, 512, .greyscale);
|
||||||
|
defer atlas.deinit(alloc);
|
||||||
|
|
||||||
|
var lib = try font.Library.init();
|
||||||
|
defer lib.deinit();
|
||||||
|
|
||||||
|
var face = try Face.init(lib, testFont, .{ .points = 12 });
|
||||||
|
defer face.deinit();
|
||||||
|
|
||||||
|
try testing.expectEqual(font.Presentation.text, face.presentation);
|
||||||
|
|
||||||
|
try face.setVariations(&.{
|
||||||
|
.{ .id = font.face.Variation.Id.init("wght"), .value = 400 },
|
||||||
|
});
|
||||||
|
|
||||||
|
// Generate all visible ASCII
|
||||||
|
var i: u8 = 32;
|
||||||
|
while (i < 127) : (i += 1) {
|
||||||
|
try testing.expect(face.glyphIndex(i) != null);
|
||||||
|
_ = try face.renderGlyph(alloc, &atlas, face.glyphIndex(i).?, .{});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
BIN
src/font/res/Lilex-VF.ttf
Normal file
BIN
src/font/res/Lilex-VF.ttf
Normal file
Binary file not shown.
@ -2,3 +2,4 @@ pub const fontRegular = @embedFile("res/Inconsolata-Regular.ttf");
|
|||||||
pub const fontBold = @embedFile("res/Inconsolata-Bold.ttf");
|
pub const fontBold = @embedFile("res/Inconsolata-Bold.ttf");
|
||||||
pub const fontEmoji = @embedFile("res/NotoColorEmoji.ttf");
|
pub const fontEmoji = @embedFile("res/NotoColorEmoji.ttf");
|
||||||
pub const fontEmojiText = @embedFile("res/NotoEmoji-Regular.ttf");
|
pub const fontEmojiText = @embedFile("res/NotoEmoji-Regular.ttf");
|
||||||
|
pub const fontVariable = @embedFile("res/Lilex-VF.ttf");
|
||||||
|
Reference in New Issue
Block a user