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;
|
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 {
|
pub const Version = struct {
|
||||||
major: i32,
|
major: i32,
|
||||||
minor: i32,
|
minor: i32,
|
||||||
|
@ -24,6 +24,12 @@ pub const Face = struct {
|
|||||||
return c.FT_HAS_COLOR(self.handle);
|
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
|
/// A macro that returns true whenever a face object contains a scalable
|
||||||
/// font face (true for TrueType, Type 1, Type 42, CID, OpenType/CFF,
|
/// font face (true for TrueType, Type 1, Type 42, CID, OpenType/CFF,
|
||||||
/// and PFR font formats).
|
/// and PFR font formats).
|
||||||
@ -95,6 +101,33 @@ pub const Face = struct {
|
|||||||
const res = c.FT_Get_Sfnt_Name(self.handle, @intCast(i), &name);
|
const res = c.FT_Get_Sfnt_Name(self.handle, @intCast(i), &name);
|
||||||
return if (intToError(res)) |_| name else |err| err;
|
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
|
/// An enumeration to specify indices of SFNT tables loaded and parsed by
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
#include <ft2build.h>
|
#include <ft2build.h>
|
||||||
#include FT_FREETYPE_H
|
#include FT_FREETYPE_H
|
||||||
#include FT_TRUETYPE_TABLES_H
|
#include FT_TRUETYPE_TABLES_H
|
||||||
|
#include <freetype/ftmm.h>
|
||||||
#include <freetype/ftsnames.h>
|
#include <freetype/ftsnames.h>
|
||||||
#include <freetype/ttnameid.h>
|
#include <freetype/ttnameid.h>
|
||||||
|
@ -54,6 +54,9 @@ pub const Fontconfig = struct {
|
|||||||
charset: *const fontconfig.CharSet,
|
charset: *const fontconfig.CharSet,
|
||||||
langset: *const fontconfig.LangSet,
|
langset: *const fontconfig.LangSet,
|
||||||
|
|
||||||
|
/// Variations to apply to this font.
|
||||||
|
variations: []const font.face.Variation,
|
||||||
|
|
||||||
pub fn deinit(self: *Fontconfig) void {
|
pub fn deinit(self: *Fontconfig) void {
|
||||||
self.pattern.destroy();
|
self.pattern.destroy();
|
||||||
self.* = undefined;
|
self.* = undefined;
|
||||||
@ -154,7 +157,10 @@ fn loadFontconfig(
|
|||||||
const filename = (try fc.pattern.get(.file, 0)).string;
|
const filename = (try fc.pattern.get(.file, 0)).string;
|
||||||
const face_index = (try fc.pattern.get(.index, 0)).integer;
|
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(
|
fn loadCoreText(
|
||||||
|
@ -205,6 +205,7 @@ pub const Fontconfig = struct {
|
|||||||
.pattern = pat,
|
.pattern = pat,
|
||||||
.set = res.fs,
|
.set = res.fs,
|
||||||
.fonts = res.fs.fonts(),
|
.fonts = res.fs.fonts(),
|
||||||
|
.variations = desc.variations,
|
||||||
.i = 0,
|
.i = 0,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -214,6 +215,7 @@ pub const Fontconfig = struct {
|
|||||||
pattern: *fontconfig.Pattern,
|
pattern: *fontconfig.Pattern,
|
||||||
set: *fontconfig.FontSet,
|
set: *fontconfig.FontSet,
|
||||||
fonts: []*fontconfig.Pattern,
|
fonts: []*fontconfig.Pattern,
|
||||||
|
variations: []const Variation,
|
||||||
i: usize,
|
i: usize,
|
||||||
|
|
||||||
pub fn deinit(self: *DiscoverIterator) void {
|
pub fn deinit(self: *DiscoverIterator) void {
|
||||||
@ -241,6 +243,7 @@ pub const Fontconfig = struct {
|
|||||||
.pattern = font_pattern,
|
.pattern = font_pattern,
|
||||||
.charset = (try font_pattern.get(.charset, 0)).char_set,
|
.charset = (try font_pattern.get(.charset, 0)).char_set,
|
||||||
.langset = (try font_pattern.get(.lang, 0)).lang_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);
|
const log = std.log.scoped(.font_face);
|
||||||
|
|
||||||
pub const Face = struct {
|
pub const Face = struct {
|
||||||
|
/// Our freetype library
|
||||||
|
lib: freetype.Library,
|
||||||
|
|
||||||
/// Our font face.
|
/// Our font face.
|
||||||
face: freetype.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 {
|
pub fn initFile(lib: Library, path: [:0]const u8, index: i32, size: font.face.DesiredSize) !Face {
|
||||||
const face = try lib.lib.initFace(path, index);
|
const face = try lib.lib.initFace(path, index);
|
||||||
errdefer face.deinit();
|
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.
|
/// 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 {
|
pub fn init(lib: Library, source: [:0]const u8, size: font.face.DesiredSize) !Face {
|
||||||
const face = try lib.lib.initMemoryFace(source, 0);
|
const face = try lib.lib.initMemoryFace(source, 0);
|
||||||
errdefer face.deinit();
|
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 face.selectCharmap(.unicode);
|
||||||
try setSize_(face, size);
|
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();
|
errdefer hb_font.destroy();
|
||||||
|
|
||||||
var result: Face = .{
|
var result: Face = .{
|
||||||
|
.lib = lib.lib,
|
||||||
.face = face,
|
.face = face,
|
||||||
.hb_font = hb_font,
|
.hb_font = hb_font,
|
||||||
.presentation = if (face.hasColor()) .emoji else .text,
|
.presentation = if (face.hasColor()) .emoji else .text,
|
||||||
.metrics = calcMetrics(face),
|
.metrics = calcMetrics(face),
|
||||||
};
|
};
|
||||||
result.quirks_disable_default_font_features = quirks.disableDefaultFontFeatures(&result);
|
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;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -132,6 +160,50 @@ pub const Face = struct {
|
|||||||
try face.selectSize(best_i);
|
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
|
/// 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 {
|
||||||
|
Reference in New Issue
Block a user