mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-07-14 15:56:13 +03:00
Handle fonts with missing or invalid OS/2 tables (#2985)
It turns out this happens more than I thought. Most notably, apple's
fallback font for a bunch of symbols, `Apple Gothic`, has no OS/2 table
🙃. I've gone ahead and fixed it so that both CoreText and FreeType
handle this gracefully.
Speaking of graceful handling, this problem came to attention due to our
very *un*-graceful handling of when a font fails to load due to a
metrics calculation error. It results in a bunch of blanked out text,
very bad, we should fix that.
*At some point I'll try to collect a group of test fonts with all sorts
of weirdnesses to exercise the weird edge cases in our handling.*
This commit is contained in:
@ -217,6 +217,10 @@ pub const FontOrientation = enum(c_uint) {
|
|||||||
|
|
||||||
pub const FontTableTag = enum(u32) {
|
pub const FontTableTag = enum(u32) {
|
||||||
svg = c.kCTFontTableSVG,
|
svg = c.kCTFontTableSVG,
|
||||||
|
os2 = c.kCTFontTableOS2,
|
||||||
|
head = c.kCTFontTableHead,
|
||||||
|
hhea = c.kCTFontTableHhea,
|
||||||
|
post = c.kCTFontTablePost,
|
||||||
_,
|
_,
|
||||||
|
|
||||||
pub fn init(v: *const [4]u8) FontTableTag {
|
pub fn init(v: *const [4]u8) FontTableTag {
|
||||||
|
@ -534,8 +534,6 @@ pub const Face = struct {
|
|||||||
CopyTableError,
|
CopyTableError,
|
||||||
InvalidHeadTable,
|
InvalidHeadTable,
|
||||||
InvalidPostTable,
|
InvalidPostTable,
|
||||||
InvalidOS2Table,
|
|
||||||
OS2VersionNotSupported,
|
|
||||||
InvalidHheaTable,
|
InvalidHheaTable,
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -569,18 +567,16 @@ pub const Face = struct {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
// Read the 'OS/2' table out of the font data.
|
// Read the 'OS/2' table out of the font data if it's available.
|
||||||
const os2: opentype.OS2 = os2: {
|
const os2_: ?opentype.OS2 = os2: {
|
||||||
const tag = macos.text.FontTableTag.init("OS/2");
|
const tag = macos.text.FontTableTag.init("OS/2");
|
||||||
const data = ct_font.copyTable(tag) orelse return error.CopyTableError;
|
const data = ct_font.copyTable(tag) orelse break :os2 null;
|
||||||
defer data.release();
|
defer data.release();
|
||||||
const ptr = data.getPointer();
|
const ptr = data.getPointer();
|
||||||
const len = data.getLength();
|
const len = data.getLength();
|
||||||
break :os2 opentype.OS2.init(ptr[0..len]) catch |err| {
|
break :os2 opentype.OS2.init(ptr[0..len]) catch |err| {
|
||||||
return switch (err) {
|
log.warn("error parsing OS/2 table: {}", .{err});
|
||||||
error.EndOfStream => error.InvalidOS2Table,
|
break :os2 null;
|
||||||
error.OS2VersionNotSupported => error.OS2VersionNotSupported,
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -603,54 +599,59 @@ pub const Face = struct {
|
|||||||
const px_per_unit: f64 = px_per_em / units_per_em;
|
const px_per_unit: f64 = px_per_em / units_per_em;
|
||||||
|
|
||||||
const ascent: f64, const descent: f64, const line_gap: f64 = vertical_metrics: {
|
const ascent: f64, const descent: f64, const line_gap: f64 = vertical_metrics: {
|
||||||
const os2_ascent: f64 = @floatFromInt(os2.sTypoAscender);
|
const hhea_ascent: f64 = @floatFromInt(hhea.ascender);
|
||||||
const os2_descent: f64 = @floatFromInt(os2.sTypoDescender);
|
const hhea_descent: f64 = @floatFromInt(hhea.descender);
|
||||||
const os2_line_gap: f64 = @floatFromInt(os2.sTypoLineGap);
|
const hhea_line_gap: f64 = @floatFromInt(hhea.lineGap);
|
||||||
|
|
||||||
// If the font says to use typo metrics, trust it.
|
if (os2_) |os2| {
|
||||||
if (os2.fsSelection.use_typo_metrics) {
|
const os2_ascent: f64 = @floatFromInt(os2.sTypoAscender);
|
||||||
break :vertical_metrics .{
|
const os2_descent: f64 = @floatFromInt(os2.sTypoDescender);
|
||||||
|
const os2_line_gap: f64 = @floatFromInt(os2.sTypoLineGap);
|
||||||
|
|
||||||
|
// If the font says to use typo metrics, trust it.
|
||||||
|
if (os2.fsSelection.use_typo_metrics) break :vertical_metrics .{
|
||||||
os2_ascent * px_per_unit,
|
os2_ascent * px_per_unit,
|
||||||
os2_descent * px_per_unit,
|
os2_descent * px_per_unit,
|
||||||
os2_line_gap * px_per_unit,
|
os2_line_gap * px_per_unit,
|
||||||
};
|
};
|
||||||
}
|
|
||||||
|
|
||||||
// Otherwise we prefer the height metrics from 'hhea' if they
|
// Otherwise we prefer the height metrics from 'hhea' if they
|
||||||
// are available, or else OS/2 sTypo* metrics, and if all else
|
// are available, or else OS/2 sTypo* metrics, and if all else
|
||||||
// fails then we use OS/2 usWin* metrics.
|
// fails then we use OS/2 usWin* metrics.
|
||||||
//
|
//
|
||||||
// This is not "standard" behavior, but it's our best bet to
|
// This is not "standard" behavior, but it's our best bet to
|
||||||
// account for fonts being... just weird. It's pretty much what
|
// account for fonts being... just weird. It's pretty much what
|
||||||
// FreeType does to get its generic ascent and descent metrics.
|
// FreeType does to get its generic ascent and descent metrics.
|
||||||
|
|
||||||
if (hhea.ascender != 0 or hhea.descender != 0) {
|
if (hhea.ascender != 0 or hhea.descender != 0) break :vertical_metrics .{
|
||||||
const hhea_ascent: f64 = @floatFromInt(hhea.ascender);
|
|
||||||
const hhea_descent: f64 = @floatFromInt(hhea.descender);
|
|
||||||
const hhea_line_gap: f64 = @floatFromInt(hhea.lineGap);
|
|
||||||
break :vertical_metrics .{
|
|
||||||
hhea_ascent * px_per_unit,
|
hhea_ascent * px_per_unit,
|
||||||
hhea_descent * px_per_unit,
|
hhea_descent * px_per_unit,
|
||||||
hhea_line_gap * px_per_unit,
|
hhea_line_gap * px_per_unit,
|
||||||
};
|
};
|
||||||
}
|
|
||||||
|
|
||||||
if (os2_ascent != 0 or os2_descent != 0) {
|
if (os2_ascent != 0 or os2_descent != 0) break :vertical_metrics .{
|
||||||
break :vertical_metrics .{
|
|
||||||
os2_ascent * px_per_unit,
|
os2_ascent * px_per_unit,
|
||||||
os2_descent * px_per_unit,
|
os2_descent * px_per_unit,
|
||||||
os2_line_gap * px_per_unit,
|
os2_line_gap * px_per_unit,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const win_ascent: f64 = @floatFromInt(os2.usWinAscent);
|
||||||
|
const win_descent: f64 = @floatFromInt(os2.usWinDescent);
|
||||||
|
break :vertical_metrics .{
|
||||||
|
win_ascent * px_per_unit,
|
||||||
|
// usWinDescent is *positive* -> down unlike sTypoDescender
|
||||||
|
// and hhea.Descender, so we flip its sign to fix this.
|
||||||
|
-win_descent * px_per_unit,
|
||||||
|
0.0,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const win_ascent: f64 = @floatFromInt(os2.usWinAscent);
|
// If our font has no OS/2 table, then we just
|
||||||
const win_descent: f64 = @floatFromInt(os2.usWinDescent);
|
// blindly use the metrics from the hhea table.
|
||||||
break :vertical_metrics .{
|
break :vertical_metrics .{
|
||||||
win_ascent * px_per_unit,
|
hhea_ascent * px_per_unit,
|
||||||
// usWinDescent is *positive* -> down unlike sTypoDescender
|
hhea_descent * px_per_unit,
|
||||||
// and hhea.Descender, so we flip its sign to fix this.
|
hhea_line_gap * px_per_unit,
|
||||||
-win_descent * px_per_unit,
|
|
||||||
0.0,
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -672,30 +673,44 @@ pub const Face = struct {
|
|||||||
@as(f64, @floatFromInt(post.underlineThickness)) * px_per_unit;
|
@as(f64, @floatFromInt(post.underlineThickness)) * px_per_unit;
|
||||||
|
|
||||||
// Similar logic to the underline above.
|
// Similar logic to the underline above.
|
||||||
const has_broken_strikethrough = os2.yStrikeoutSize == 0;
|
const strikethrough_position, const strikethrough_thickness = st: {
|
||||||
|
const os2 = os2_ orelse break :st .{ null, null };
|
||||||
|
|
||||||
const strikethrough_position: ?f64 = if (has_broken_strikethrough and os2.yStrikeoutPosition == 0)
|
const has_broken_strikethrough = os2.yStrikeoutSize == 0;
|
||||||
null
|
|
||||||
else
|
|
||||||
@as(f64, @floatFromInt(os2.yStrikeoutPosition)) * px_per_unit;
|
|
||||||
|
|
||||||
const strikethrough_thickness: ?f64 = if (has_broken_strikethrough)
|
const pos: ?f64 = if (has_broken_strikethrough and os2.yStrikeoutPosition == 0)
|
||||||
null
|
null
|
||||||
else
|
else
|
||||||
@as(f64, @floatFromInt(os2.yStrikeoutSize)) * px_per_unit;
|
@as(f64, @floatFromInt(os2.yStrikeoutPosition)) * px_per_unit;
|
||||||
|
|
||||||
// We fall back to whatever CoreText does if
|
const thick: ?f64 = if (has_broken_strikethrough)
|
||||||
// the OS/2 table doesn't specify a cap height.
|
null
|
||||||
const cap_height: f64 = if (os2.sCapHeight) |sCapHeight|
|
else
|
||||||
@as(f64, @floatFromInt(sCapHeight)) * px_per_unit
|
@as(f64, @floatFromInt(os2.yStrikeoutSize)) * px_per_unit;
|
||||||
else
|
|
||||||
ct_font.getCapHeight();
|
|
||||||
|
|
||||||
// Ditto for ex height.
|
break :st .{ pos, thick };
|
||||||
const ex_height: f64 = if (os2.sxHeight) |sxHeight|
|
};
|
||||||
@as(f64, @floatFromInt(sxHeight)) * px_per_unit
|
|
||||||
else
|
// We fall back to whatever CoreText does if the
|
||||||
ct_font.getXHeight();
|
// OS/2 table doesn't specify a cap or ex height.
|
||||||
|
const cap_height: f64, const ex_height: f64 = heights: {
|
||||||
|
const os2 = os2_ orelse break :heights .{
|
||||||
|
ct_font.getCapHeight(),
|
||||||
|
ct_font.getXHeight(),
|
||||||
|
};
|
||||||
|
|
||||||
|
break :heights .{
|
||||||
|
if (os2.sCapHeight) |sCapHeight|
|
||||||
|
@as(f64, @floatFromInt(sCapHeight)) * px_per_unit
|
||||||
|
else
|
||||||
|
ct_font.getCapHeight(),
|
||||||
|
|
||||||
|
if (os2.sxHeight) |sxHeight|
|
||||||
|
@as(f64, @floatFromInt(sxHeight)) * px_per_unit
|
||||||
|
else
|
||||||
|
ct_font.getXHeight(),
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
// Cell width is calculated by calculating the widest width of the
|
// Cell width is calculated by calculating the widest width of the
|
||||||
// visible ASCII characters. Usually 'M' is widest but we just take
|
// visible ASCII characters. Usually 'M' is widest but we just take
|
||||||
|
@ -600,7 +600,6 @@ pub const Face = struct {
|
|||||||
|
|
||||||
const CalcMetricsError = error{
|
const CalcMetricsError = error{
|
||||||
CopyTableError,
|
CopyTableError,
|
||||||
MissingOS2Table,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Calculate the metrics associated with a face. This is not public because
|
/// Calculate the metrics associated with a face. This is not public because
|
||||||
@ -629,70 +628,80 @@ pub const Face = struct {
|
|||||||
const post = face.getSfntTable(.post) orelse return error.CopyTableError;
|
const post = face.getSfntTable(.post) orelse return error.CopyTableError;
|
||||||
|
|
||||||
// Read the 'OS/2' table out of the font data.
|
// Read the 'OS/2' table out of the font data.
|
||||||
const os2 = face.getSfntTable(.os2) orelse return error.CopyTableError;
|
const os2_: ?*freetype.c.TT_OS2 = os2: {
|
||||||
|
const os2 = face.getSfntTable(.os2) orelse break :os2 null;
|
||||||
|
if (os2.version == 0xFFFF) break :os2 null;
|
||||||
|
break :os2 os2;
|
||||||
|
};
|
||||||
|
|
||||||
// Read the 'hhea' table out of the font data.
|
// Read the 'hhea' table out of the font data.
|
||||||
const hhea = face.getSfntTable(.hhea) orelse return error.CopyTableError;
|
const hhea = face.getSfntTable(.hhea) orelse return error.CopyTableError;
|
||||||
|
|
||||||
// Some fonts don't actually have an OS/2 table, which
|
|
||||||
// we need in order to do the metrics calculations, in
|
|
||||||
// such cases FreeType sets the version to 0xFFFF
|
|
||||||
if (os2.version == 0xFFFF) return error.MissingOS2Table;
|
|
||||||
|
|
||||||
const units_per_em = head.Units_Per_EM;
|
const units_per_em = head.Units_Per_EM;
|
||||||
const px_per_em: f64 = @floatFromInt(size_metrics.y_ppem);
|
const px_per_em: f64 = @floatFromInt(size_metrics.y_ppem);
|
||||||
const px_per_unit = px_per_em / @as(f64, @floatFromInt(units_per_em));
|
const px_per_unit = px_per_em / @as(f64, @floatFromInt(units_per_em));
|
||||||
|
|
||||||
const ascent: f64, const descent: f64, const line_gap: f64 = vertical_metrics: {
|
const ascent: f64, const descent: f64, const line_gap: f64 = vertical_metrics: {
|
||||||
const os2_ascent: f64 = @floatFromInt(os2.sTypoAscender);
|
const hhea_ascent: f64 = @floatFromInt(hhea.Ascender);
|
||||||
const os2_descent: f64 = @floatFromInt(os2.sTypoDescender);
|
const hhea_descent: f64 = @floatFromInt(hhea.Descender);
|
||||||
const os2_line_gap: f64 = @floatFromInt(os2.sTypoLineGap);
|
const hhea_line_gap: f64 = @floatFromInt(hhea.Line_Gap);
|
||||||
|
|
||||||
// If the font says to use typo metrics, trust it.
|
if (os2_) |os2| {
|
||||||
// (The USE_TYPO_METRICS bit is bit 7)
|
const os2_ascent: f64 = @floatFromInt(os2.sTypoAscender);
|
||||||
if (os2.fsSelection & (1 << 7) != 0) {
|
const os2_descent: f64 = @floatFromInt(os2.sTypoDescender);
|
||||||
|
const os2_line_gap: f64 = @floatFromInt(os2.sTypoLineGap);
|
||||||
|
|
||||||
|
// If the font says to use typo metrics, trust it.
|
||||||
|
// (The USE_TYPO_METRICS bit is bit 7)
|
||||||
|
if (os2.fsSelection & (1 << 7) != 0) {
|
||||||
|
break :vertical_metrics .{
|
||||||
|
os2_ascent * px_per_unit,
|
||||||
|
os2_descent * px_per_unit,
|
||||||
|
os2_line_gap * px_per_unit,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise we prefer the height metrics from 'hhea' if they
|
||||||
|
// are available, or else OS/2 sTypo* metrics, and if all else
|
||||||
|
// fails then we use OS/2 usWin* metrics.
|
||||||
|
//
|
||||||
|
// This is not "standard" behavior, but it's our best bet to
|
||||||
|
// account for fonts being... just weird. It's pretty much what
|
||||||
|
// FreeType does to get its generic ascent and descent metrics.
|
||||||
|
|
||||||
|
if (hhea.Ascender != 0 or hhea.Descender != 0) {
|
||||||
|
break :vertical_metrics .{
|
||||||
|
hhea_ascent * px_per_unit,
|
||||||
|
hhea_descent * px_per_unit,
|
||||||
|
hhea_line_gap * px_per_unit,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (os2_ascent != 0 or os2_descent != 0) {
|
||||||
|
break :vertical_metrics .{
|
||||||
|
os2_ascent * px_per_unit,
|
||||||
|
os2_descent * px_per_unit,
|
||||||
|
os2_line_gap * px_per_unit,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const win_ascent: f64 = @floatFromInt(os2.usWinAscent);
|
||||||
|
const win_descent: f64 = @floatFromInt(os2.usWinDescent);
|
||||||
break :vertical_metrics .{
|
break :vertical_metrics .{
|
||||||
os2_ascent * px_per_unit,
|
win_ascent * px_per_unit,
|
||||||
os2_descent * px_per_unit,
|
// usWinDescent is *positive* -> down unlike sTypoDescender
|
||||||
os2_line_gap * px_per_unit,
|
// and hhea.Descender, so we flip its sign to fix this.
|
||||||
|
-win_descent * px_per_unit,
|
||||||
|
0.0,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// Otherwise we prefer the height metrics from 'hhea' if they
|
// If our font has no OS/2 table, then we just
|
||||||
// are available, or else OS/2 sTypo* metrics, and if all else
|
// blindly use the metrics from the hhea table.
|
||||||
// fails then we use OS/2 usWin* metrics.
|
|
||||||
//
|
|
||||||
// This is not "standard" behavior, but it's our best bet to
|
|
||||||
// account for fonts being... just weird. It's pretty much what
|
|
||||||
// FreeType does to get its generic ascent and descent metrics.
|
|
||||||
|
|
||||||
if (hhea.Ascender != 0 or hhea.Descender != 0) {
|
|
||||||
const hhea_ascent: f64 = @floatFromInt(hhea.Ascender);
|
|
||||||
const hhea_descent: f64 = @floatFromInt(hhea.Descender);
|
|
||||||
const hhea_line_gap: f64 = @floatFromInt(hhea.Line_Gap);
|
|
||||||
break :vertical_metrics .{
|
|
||||||
hhea_ascent * px_per_unit,
|
|
||||||
hhea_descent * px_per_unit,
|
|
||||||
hhea_line_gap * px_per_unit,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
if (os2_ascent != 0 or os2_descent != 0) {
|
|
||||||
break :vertical_metrics .{
|
|
||||||
os2_ascent * px_per_unit,
|
|
||||||
os2_descent * px_per_unit,
|
|
||||||
os2_line_gap * px_per_unit,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const win_ascent: f64 = @floatFromInt(os2.usWinAscent);
|
|
||||||
const win_descent: f64 = @floatFromInt(os2.usWinDescent);
|
|
||||||
break :vertical_metrics .{
|
break :vertical_metrics .{
|
||||||
win_ascent * px_per_unit,
|
hhea_ascent * px_per_unit,
|
||||||
// usWinDescent is *positive* -> down unlike sTypoDescender
|
hhea_descent * px_per_unit,
|
||||||
// and hhea.Descender, so we flip its sign to fix this.
|
hhea_line_gap * px_per_unit,
|
||||||
-win_descent * px_per_unit,
|
|
||||||
0.0,
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -714,17 +723,23 @@ pub const Face = struct {
|
|||||||
@as(f64, @floatFromInt(post.underlineThickness)) * px_per_unit;
|
@as(f64, @floatFromInt(post.underlineThickness)) * px_per_unit;
|
||||||
|
|
||||||
// Similar logic to the underline above.
|
// Similar logic to the underline above.
|
||||||
const has_broken_strikethrough = os2.yStrikeoutSize == 0;
|
const strikethrough_position, const strikethrough_thickness = st: {
|
||||||
|
const os2 = os2_ orelse break :st .{ null, null };
|
||||||
|
|
||||||
const strikethrough_position = if (has_broken_strikethrough and os2.yStrikeoutPosition == 0)
|
const has_broken_strikethrough = os2.yStrikeoutSize == 0;
|
||||||
null
|
|
||||||
else
|
|
||||||
@as(f64, @floatFromInt(os2.yStrikeoutPosition)) * px_per_unit;
|
|
||||||
|
|
||||||
const strikethrough_thickness = if (has_broken_strikethrough)
|
const pos: ?f64 = if (has_broken_strikethrough and os2.yStrikeoutPosition == 0)
|
||||||
null
|
null
|
||||||
else
|
else
|
||||||
@as(f64, @floatFromInt(os2.yStrikeoutSize)) * px_per_unit;
|
@as(f64, @floatFromInt(os2.yStrikeoutPosition)) * px_per_unit;
|
||||||
|
|
||||||
|
const thick: ?f64 = if (has_broken_strikethrough)
|
||||||
|
null
|
||||||
|
else
|
||||||
|
@as(f64, @floatFromInt(os2.yStrikeoutSize)) * px_per_unit;
|
||||||
|
|
||||||
|
break :st .{ pos, thick };
|
||||||
|
};
|
||||||
|
|
||||||
// Cell width is calculated by calculating the widest width of the
|
// Cell width is calculated by calculating the widest width of the
|
||||||
// visible ASCII characters. Usually 'M' is widest but we just take
|
// visible ASCII characters. Usually 'M' is widest but we just take
|
||||||
@ -754,37 +769,37 @@ pub const Face = struct {
|
|||||||
break :cell_width max;
|
break :cell_width max;
|
||||||
};
|
};
|
||||||
|
|
||||||
// The OS/2 table does not include sCapHeight or sxHeight in version 1.
|
// We use the cap and ex heights specified by the font if they're
|
||||||
const has_os2_height_metrics = os2.version >= 2;
|
// available, otherwise we try to measure the `H` and `x` glyphs.
|
||||||
|
const cap_height: ?f64, const ex_height: ?f64 = heights: {
|
||||||
// We use the cap height specified by the font if it's
|
if (os2_) |os2| {
|
||||||
// available, otherwise we try to measure the `H` glyph.
|
// The OS/2 table does not include these metrics in version 1.
|
||||||
const cap_height: ?f64 = cap_height: {
|
if (os2.version >= 2) {
|
||||||
if (has_os2_height_metrics) {
|
break :heights .{
|
||||||
break :cap_height @as(f64, @floatFromInt(os2.sCapHeight)) * px_per_unit;
|
@as(f64, @floatFromInt(os2.sCapHeight)) * px_per_unit,
|
||||||
}
|
@as(f64, @floatFromInt(os2.sxHeight)) * px_per_unit,
|
||||||
if (face.getCharIndex('H')) |glyph_index| {
|
};
|
||||||
if (face.loadGlyph(glyph_index, .{ .render = true })) {
|
}
|
||||||
break :cap_height f26dot6ToF64(face.handle.*.glyph.*.metrics.height);
|
|
||||||
} else |_| {}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
break :cap_height null;
|
break :heights .{
|
||||||
};
|
cap: {
|
||||||
|
if (face.getCharIndex('H')) |glyph_index| {
|
||||||
// We use the ex height specified by the font if it's
|
if (face.loadGlyph(glyph_index, .{ .render = true })) {
|
||||||
// available, otherwise we try to measure the `x` glyph.
|
break :cap f26dot6ToF64(face.handle.*.glyph.*.metrics.height);
|
||||||
const ex_height: ?f64 = ex_height: {
|
} else |_| {}
|
||||||
if (has_os2_height_metrics) {
|
}
|
||||||
break :ex_height @as(f64, @floatFromInt(os2.sxHeight)) * px_per_unit;
|
break :cap null;
|
||||||
}
|
},
|
||||||
if (face.getCharIndex('x')) |glyph_index| {
|
ex: {
|
||||||
if (face.loadGlyph(glyph_index, .{ .render = true })) {
|
if (face.getCharIndex('x')) |glyph_index| {
|
||||||
break :ex_height f26dot6ToF64(face.handle.*.glyph.*.metrics.height);
|
if (face.loadGlyph(glyph_index, .{ .render = true })) {
|
||||||
} else |_| {}
|
break :ex f26dot6ToF64(face.handle.*.glyph.*.metrics.height);
|
||||||
}
|
} else |_| {}
|
||||||
|
}
|
||||||
break :ex_height null;
|
break :ex null;
|
||||||
|
},
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
var result = font.face.Metrics.calc(.{
|
var result = font.face.Metrics.calc(.{
|
||||||
|
Reference in New Issue
Block a user