mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-04-22 01:18:36 +03:00

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.
324 lines
11 KiB
Zig
324 lines
11 KiB
Zig
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);
|
||
}
|