mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-08-02 14:57:31 +03:00
font: freetype supports font variation settings
This commit is contained in:
@ -57,6 +57,11 @@ pub fn initMemoryFace(self: Library, data: []const u8, index: i32) Error!Face {
|
||||
return face;
|
||||
}
|
||||
|
||||
/// Call when you're done with a loaded MM var.
|
||||
pub fn doneMMVar(self: Library, mm: *c.FT_MM_Var) void {
|
||||
_ = c.FT_Done_MM_Var(self.handle, mm);
|
||||
}
|
||||
|
||||
pub const Version = struct {
|
||||
major: i32,
|
||||
minor: i32,
|
||||
|
@ -24,6 +24,12 @@ pub const Face = struct {
|
||||
return c.FT_HAS_COLOR(self.handle);
|
||||
}
|
||||
|
||||
/// A macro that returns true whenever a face object contains some
|
||||
/// multiple masters.
|
||||
pub fn hasMultipleMasters(self: Face) bool {
|
||||
return c.FT_HAS_MULTIPLE_MASTERS(self.handle);
|
||||
}
|
||||
|
||||
/// A macro that returns true whenever a face object contains a scalable
|
||||
/// font face (true for TrueType, Type 1, Type 42, CID, OpenType/CFF,
|
||||
/// and PFR font formats).
|
||||
@ -95,6 +101,33 @@ pub const Face = struct {
|
||||
const res = c.FT_Get_Sfnt_Name(self.handle, @intCast(i), &name);
|
||||
return if (intToError(res)) |_| name else |err| err;
|
||||
}
|
||||
|
||||
/// Retrieve the font variation descriptor for a font.
|
||||
pub fn getMMVar(self: Face) Error!*c.FT_MM_Var {
|
||||
var result: *c.FT_MM_Var = undefined;
|
||||
const res = c.FT_Get_MM_Var(self.handle, @ptrCast(&result));
|
||||
return if (intToError(res)) |_| result else |err| err;
|
||||
}
|
||||
|
||||
/// Get the design coordinates of the currently selected interpolated font.
|
||||
pub fn getVarDesignCoordinates(self: Face, coords: []c.FT_Fixed) Error!void {
|
||||
const res = c.FT_Get_Var_Design_Coordinates(
|
||||
self.handle,
|
||||
@intCast(coords.len),
|
||||
coords.ptr,
|
||||
);
|
||||
return intToError(res);
|
||||
}
|
||||
|
||||
/// Choose an interpolated font design through design coordinates.
|
||||
pub fn setVarDesignCoordinates(self: Face, coords: []c.FT_Fixed) Error!void {
|
||||
const res = c.FT_Set_Var_Design_Coordinates(
|
||||
self.handle,
|
||||
@intCast(coords.len),
|
||||
coords.ptr,
|
||||
);
|
||||
return intToError(res);
|
||||
}
|
||||
};
|
||||
|
||||
/// An enumeration to specify indices of SFNT tables loaded and parsed by
|
||||
|
@ -1,5 +1,6 @@
|
||||
#include <ft2build.h>
|
||||
#include FT_FREETYPE_H
|
||||
#include FT_TRUETYPE_TABLES_H
|
||||
#include <freetype/ftmm.h>
|
||||
#include <freetype/ftsnames.h>
|
||||
#include <freetype/ttnameid.h>
|
||||
|
@ -54,6 +54,9 @@ pub const Fontconfig = struct {
|
||||
charset: *const fontconfig.CharSet,
|
||||
langset: *const fontconfig.LangSet,
|
||||
|
||||
/// Variations to apply to this font.
|
||||
variations: []const font.face.Variation,
|
||||
|
||||
pub fn deinit(self: *Fontconfig) void {
|
||||
self.pattern.destroy();
|
||||
self.* = undefined;
|
||||
@ -154,7 +157,10 @@ fn loadFontconfig(
|
||||
const filename = (try fc.pattern.get(.file, 0)).string;
|
||||
const face_index = (try fc.pattern.get(.index, 0)).integer;
|
||||
|
||||
return try Face.initFile(lib, filename, face_index, size);
|
||||
var face = try Face.initFile(lib, filename, face_index, size);
|
||||
errdefer face.deinit();
|
||||
try face.setVariations(fc.variations);
|
||||
return face;
|
||||
}
|
||||
|
||||
fn loadCoreText(
|
||||
|
@ -205,6 +205,7 @@ pub const Fontconfig = struct {
|
||||
.pattern = pat,
|
||||
.set = res.fs,
|
||||
.fonts = res.fs.fonts(),
|
||||
.variations = desc.variations,
|
||||
.i = 0,
|
||||
};
|
||||
}
|
||||
@ -214,6 +215,7 @@ pub const Fontconfig = struct {
|
||||
pattern: *fontconfig.Pattern,
|
||||
set: *fontconfig.FontSet,
|
||||
fonts: []*fontconfig.Pattern,
|
||||
variations: []const Variation,
|
||||
i: usize,
|
||||
|
||||
pub fn deinit(self: *DiscoverIterator) void {
|
||||
@ -241,6 +243,7 @@ pub const Fontconfig = struct {
|
||||
.pattern = font_pattern,
|
||||
.charset = (try font_pattern.get(.charset, 0)).char_set,
|
||||
.langset = (try font_pattern.get(.lang, 0)).lang_set,
|
||||
.variations = self.variations,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
@ -23,6 +23,9 @@ const quirks = @import("../../quirks.zig");
|
||||
const log = std.log.scoped(.font_face);
|
||||
|
||||
pub const Face = struct {
|
||||
/// Our freetype library
|
||||
lib: freetype.Library,
|
||||
|
||||
/// Our font face.
|
||||
face: freetype.Face,
|
||||
|
||||
@ -43,30 +46,55 @@ pub const Face = struct {
|
||||
pub fn initFile(lib: Library, path: [:0]const u8, index: i32, size: font.face.DesiredSize) !Face {
|
||||
const face = try lib.lib.initFace(path, index);
|
||||
errdefer face.deinit();
|
||||
return try initFace(face, size);
|
||||
return try initFace(lib, face, size);
|
||||
}
|
||||
|
||||
/// Initialize a new font face with the given source in-memory.
|
||||
pub fn init(lib: Library, source: [:0]const u8, size: font.face.DesiredSize) !Face {
|
||||
const face = try lib.lib.initMemoryFace(source, 0);
|
||||
errdefer face.deinit();
|
||||
return try initFace(face, size);
|
||||
return try initFace(lib, face, size);
|
||||
}
|
||||
|
||||
fn initFace(face: freetype.Face, size: font.face.DesiredSize) !Face {
|
||||
fn initFace(lib: Library, face: freetype.Face, size: font.face.DesiredSize) !Face {
|
||||
try face.selectCharmap(.unicode);
|
||||
try setSize_(face, size);
|
||||
|
||||
const hb_font = try harfbuzz.freetype.createFont(face.handle);
|
||||
var hb_font = try harfbuzz.freetype.createFont(face.handle);
|
||||
errdefer hb_font.destroy();
|
||||
|
||||
var result: Face = .{
|
||||
.lib = lib.lib,
|
||||
.face = face,
|
||||
.hb_font = hb_font,
|
||||
.presentation = if (face.hasColor()) .emoji else .text,
|
||||
.metrics = calcMetrics(face),
|
||||
};
|
||||
result.quirks_disable_default_font_features = quirks.disableDefaultFontFeatures(&result);
|
||||
|
||||
// In debug mode, we output information about available variation axes,
|
||||
// if they exist.
|
||||
if (comptime builtin.mode == .Debug) mm: {
|
||||
if (!face.hasMultipleMasters()) break :mm;
|
||||
var buf: [1024]u8 = undefined;
|
||||
log.debug("variation axes font={s}", .{try result.name(&buf)});
|
||||
|
||||
const mm = try face.getMMVar();
|
||||
defer lib.lib.doneMMVar(mm);
|
||||
for (0..mm.num_axis) |i| {
|
||||
const axis = mm.axis[i];
|
||||
const id_raw = std.math.cast(c_int, axis.tag) orelse continue;
|
||||
const id: font.face.Variation.Id = @bitCast(id_raw);
|
||||
log.debug("variation axis: name={s} id={s} min={} max={} def={}", .{
|
||||
std.mem.sliceTo(axis.name, 0),
|
||||
id.str(),
|
||||
axis.minimum >> 16,
|
||||
axis.maximum >> 16,
|
||||
axis.def >> 16,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@ -132,6 +160,50 @@ pub const Face = struct {
|
||||
try face.selectSize(best_i);
|
||||
}
|
||||
|
||||
/// 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 {
|
||||
// If this font doesn't support variations, we can't do anything.
|
||||
if (!self.face.hasMultipleMasters() or vs.len == 0) return;
|
||||
|
||||
// Freetype requires that we send ALL coordinates in at once so the
|
||||
// first thing we have to do is get all the vars and put them into
|
||||
// an array.
|
||||
const mm = try self.face.getMMVar();
|
||||
defer self.lib.doneMMVar(mm);
|
||||
|
||||
// To avoid allocations, we cap the number of variation axes we can
|
||||
// support. This is arbitrary but Firefox caps this at 16 so I
|
||||
// feel like that's probably safe... and we do double cause its
|
||||
// cheap.
|
||||
var coords_buf: [32]freetype.c.FT_Fixed = undefined;
|
||||
var coords = coords_buf[0..@min(coords_buf.len, mm.num_axis)];
|
||||
try self.face.getVarDesignCoordinates(coords);
|
||||
|
||||
// Now we go through each axis and see if its set. This is slow
|
||||
// but there usually aren't many axes and usually not many set
|
||||
// variations, either.
|
||||
for (0..mm.num_axis) |i| {
|
||||
const axis = mm.axis[i];
|
||||
const id = std.math.cast(u32, axis.tag) orelse continue;
|
||||
for (vs) |v| {
|
||||
if (id == @as(u32, @bitCast(v.id))) {
|
||||
coords[i] = @intFromFloat(v.value * 65536);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Set them!
|
||||
try self.face.setVarDesignCoordinates(coords);
|
||||
|
||||
// We need to recalculate font metrics which may have changed.
|
||||
self.metrics = calcMetrics(self.face);
|
||||
}
|
||||
|
||||
/// Returns the glyph index for the given Unicode code point. If this
|
||||
/// face doesn't support this glyph, null is returned.
|
||||
pub fn glyphIndex(self: Face, cp: u32) ?u32 {
|
||||
|
Reference in New Issue
Block a user