ghostty/pkg/freetype/face.zig
Qwerasd 6f84a5d682 font/freetype: disable SVG glyphs, simplify color check
We don't currently support rendering SVG glyphs so they should be
ignored when loading. Additionally, the check for whether a glyph is
colored has been simplified by just checking the pixel mode of the
rendered bitmap.

This commit also fixes a bug caused by calling the color check inside of
`renderGlyph`, which caused the bitmap to be freed creating a chance for
memory corruption and garbled glyphs.
2025-03-19 12:43:18 -06:00

324 lines
11 KiB
Zig
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

const std = @import("std");
const Allocator = std.mem.Allocator;
const c = @import("c.zig").c;
const errors = @import("errors.zig");
const Library = @import("Library.zig");
const Tag = @import("tag.zig").Tag;
const Error = errors.Error;
const intToError = errors.intToError;
pub const Face = struct {
handle: c.FT_Face,
pub fn deinit(self: Face) void {
_ = c.FT_Done_Face(self.handle);
}
/// Increment the counter of the face.
pub fn ref(self: Face) void {
_ = c.FT_Reference_Face(self.handle);
}
/// A macro that returns true whenever a face object contains some
/// embedded bitmaps. See the available_sizes field of the FT_FaceRec structure.
pub fn hasFixedSizes(self: Face) bool {
return c.FT_HAS_FIXED_SIZES(self.handle);
}
/// A macro that returns true whenever a face object contains tables for
/// color glyphs.
pub fn hasColor(self: Face) bool {
return c.FT_HAS_COLOR(self.handle);
}
/// A macro that returns true whenever a face object contains an sbix
/// OpenType table and outline glyphs.
pub fn hasSBIX(self: Face) bool {
return c.FT_HAS_SBIX(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).
pub fn isScalable(self: Face) bool {
return c.FT_IS_SCALABLE(self.handle);
}
/// Select a given charmap by its encoding tag (as listed in freetype.h).
pub fn selectCharmap(self: Face, encoding: Encoding) Error!void {
return intToError(c.FT_Select_Charmap(self.handle, @intFromEnum(encoding)));
}
/// Call FT_Request_Size to request the nominal size (in points).
pub fn setCharSize(
self: Face,
char_width: i32,
char_height: i32,
horz_resolution: u16,
vert_resolution: u16,
) Error!void {
return intToError(c.FT_Set_Char_Size(
self.handle,
char_width,
char_height,
horz_resolution,
vert_resolution,
));
}
/// Select a bitmap strike. To be more precise, this function sets the
/// scaling factors of the active FT_Size object in a face so that bitmaps
/// from this particular strike are taken by FT_Load_Glyph and friends.
pub fn selectSize(self: Face, idx: i32) Error!void {
return intToError(c.FT_Select_Size(self.handle, idx));
}
/// Return the glyph index of a given character code. This function uses
/// the currently selected charmap to do the mapping.
pub fn getCharIndex(self: Face, char: u32) ?u32 {
const i = c.FT_Get_Char_Index(self.handle, char);
return if (i == 0) null else i;
}
/// Load a glyph into the glyph slot of a face object.
pub fn loadGlyph(self: Face, glyph_index: u32, load_flags: LoadFlags) Error!void {
return intToError(c.FT_Load_Glyph(
self.handle,
glyph_index,
@bitCast(load_flags),
));
}
/// Convert a given glyph image to a bitmap.
pub fn renderGlyph(self: Face, render_mode: RenderMode) Error!void {
return intToError(c.FT_Render_Glyph(
self.handle.*.glyph,
@intFromEnum(render_mode),
));
}
/// Return a pointer to a given SFNT table stored within a face.
pub fn getSfntTable(self: Face, comptime tag: SfntTag) ?*tag.DataType() {
return @ptrCast(@alignCast(c.FT_Get_Sfnt_Table(
self.handle,
@intFromEnum(tag),
)));
}
/// Retrieve the number of name strings in the SFNT name table.
pub fn getSfntNameCount(self: Face) usize {
return @intCast(c.FT_Get_Sfnt_Name_Count(self.handle));
}
/// Retrieve a string of the SFNT name table for a given index.
pub fn getSfntName(self: Face, i: usize) Error!c.FT_SfntName {
var name: c.FT_SfntName = undefined;
const res = c.FT_Get_Sfnt_Name(self.handle, @intCast(i), &name);
return if (intToError(res)) |_| name else |err| err;
}
/// Load any SFNT font table into client memory.
pub fn loadSfntTable(
self: Face,
alloc: Allocator,
tag: Tag,
) (Allocator.Error || Error)!?[]u8 {
const tag_c: c_ulong = @intCast(@as(u32, @bitCast(tag)));
// Get the length of the table in bytes
var len: c_ulong = 0;
var res = c.FT_Load_Sfnt_Table(self.handle, tag_c, 0, null, &len);
_ = intToError(res) catch |err| return err;
// If our length is zero we don't have a table.
if (len == 0) return null;
// Allocate a buffer to hold the table and load it
const buf = try alloc.alloc(u8, len);
errdefer alloc.free(buf);
res = c.FT_Load_Sfnt_Table(self.handle, tag_c, 0, buf.ptr, &len);
_ = intToError(res) catch |err| return err;
return buf;
}
/// Check whether a given SFNT table is available in a face.
pub fn hasSfntTable(self: Face, tag: Tag) bool {
const tag_c: c_ulong = @intCast(@as(u32, @bitCast(tag)));
var len: c_ulong = 0;
const res = c.FT_Load_Sfnt_Table(self.handle, tag_c, 0, null, &len);
_ = intToError(res) catch return false;
return len != 0;
}
/// 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);
}
/// Set the transformation that is applied to glyph images when they are
/// loaded into a glyph slot through FT_Load_Glyph.
pub fn setTransform(
self: Face,
matrix: ?*const c.FT_Matrix,
delta: ?*const c.FT_Vector,
) void {
c.FT_Set_Transform(
self.handle,
@constCast(@ptrCast(matrix)),
@constCast(@ptrCast(delta)),
);
}
};
/// An enumeration to specify indices of SFNT tables loaded and parsed by
/// FreeType during initialization of an SFNT font. Used in the
/// FT_Get_Sfnt_Table API function.
pub const SfntTag = enum(c_int) {
head = c.FT_SFNT_HEAD,
maxp = c.FT_SFNT_MAXP,
os2 = c.FT_SFNT_OS2,
hhea = c.FT_SFNT_HHEA,
vhea = c.FT_SFNT_VHEA,
post = c.FT_SFNT_POST,
pclt = c.FT_SFNT_PCLT,
/// The data type for a given sfnt tag.
pub fn DataType(comptime self: SfntTag) type {
return switch (self) {
.os2 => c.TT_OS2,
.head => c.TT_Header,
.post => c.TT_Postscript,
.hhea => c.TT_HoriHeader,
else => unreachable, // As-needed...
};
}
};
/// An enumeration to specify character sets supported by charmaps. Used in the
/// FT_Select_Charmap API function.
pub const Encoding = enum(u31) {
none = c.FT_ENCODING_NONE,
ms_symbol = c.FT_ENCODING_MS_SYMBOL,
unicode = c.FT_ENCODING_UNICODE,
sjis = c.FT_ENCODING_SJIS,
prc = c.FT_ENCODING_PRC,
big5 = c.FT_ENCODING_BIG5,
wansung = c.FT_ENCODING_WANSUNG,
johab = c.FT_ENCODING_JOHAB,
adobe_standard = c.FT_ENCODING_ADOBE_STANDARD,
adobe_expert = c.FT_ENCODING_ADOBE_EXPERT,
adobe_custom = c.FT_ENCODING_ADOBE_CUSTOM,
adobe_latin_1 = c.FT_ENCODING_ADOBE_LATIN_1,
old_latin_2 = c.FT_ENCODING_OLD_LATIN_2,
apple_roman = c.FT_ENCODING_APPLE_ROMAN,
};
/// https://freetype.org/freetype2/docs/reference/ft2-glyph_retrieval.html#ft_render_mode
pub const RenderMode = enum(c_uint) {
normal = c.FT_RENDER_MODE_NORMAL,
light = c.FT_RENDER_MODE_LIGHT,
mono = c.FT_RENDER_MODE_MONO,
lcd = c.FT_RENDER_MODE_LCD,
lcd_v = c.FT_RENDER_MODE_LCD_V,
sdf = c.FT_RENDER_MODE_SDF,
};
/// A list of bit field constants for FT_Load_Glyph to indicate what kind of
/// operations to perform during glyph loading.
pub const LoadFlags = packed struct {
no_scale: bool = false,
no_hinting: bool = false,
render: bool = false,
no_bitmap: bool = false,
vertical_layout: bool = false,
force_autohint: bool = false,
crop_bitmap: bool = false,
pedantic: bool = false,
ignore_global_advance_with: bool = false,
no_recurse: bool = false,
ignore_transform: bool = false,
monochrome: bool = false,
linear_design: bool = false,
no_autohint: bool = false,
_padding1: u1 = 0,
target_normal: bool = false,
target_light: bool = false,
target_mono: bool = false,
target_lcd: bool = false,
target_lcd_v: bool = false,
color: bool = false,
compute_metrics: bool = false,
bitmap_metrics_only: bool = false,
_padding2: u1 = 0,
no_svg: bool = false,
_padding3: u7 = 0,
test {
// This must always be an i32 size so we can bitcast directly.
const testing = std.testing;
try testing.expectEqual(@sizeOf(i32), @sizeOf(LoadFlags));
}
test "bitcast" {
const testing = std.testing;
const cval: i32 = c.FT_LOAD_RENDER | c.FT_LOAD_PEDANTIC | c.FT_LOAD_COLOR;
const flags = @as(LoadFlags, @bitCast(cval));
try testing.expect(!flags.no_hinting);
try testing.expect(flags.render);
try testing.expect(flags.pedantic);
try testing.expect(flags.color);
}
};
test "loading memory font" {
const testing = std.testing;
const font_data = @import("test.zig").font_regular;
var lib = try Library.init();
defer lib.deinit();
var face = try lib.initMemoryFace(font_data, 0);
defer face.deinit();
// Try APIs
try face.selectCharmap(.unicode);
try testing.expect(!face.hasFixedSizes());
try face.setCharSize(12, 0, 0, 0);
// Try loading
const idx = face.getCharIndex('A').?;
try face.loadGlyph(idx, .{});
// Try getting a truetype table
const os2 = face.getSfntTable(.os2);
try testing.expect(os2 != null);
}