mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-08-02 14:57:31 +03:00
font: more robust extraction of vertical metrics from tables
Previously always assuming the typo metrics were good caused some fonts to have abnormally short cell heights.
This commit is contained in:
@ -217,6 +217,7 @@ pub const SfntTag = enum(c_int) {
|
||||
.os2 => c.TT_OS2,
|
||||
.head => c.TT_Header,
|
||||
.post => c.TT_Postscript,
|
||||
.hhea => c.TT_HoriHeader,
|
||||
else => unreachable, // As-needed...
|
||||
};
|
||||
}
|
||||
|
@ -536,6 +536,7 @@ pub const Face = struct {
|
||||
InvalidPostTable,
|
||||
InvalidOS2Table,
|
||||
OS2VersionNotSupported,
|
||||
InvalidHheaTable,
|
||||
};
|
||||
|
||||
fn calcMetrics(ct_font: *macos.text.Font) CalcMetricsError!font.face.Metrics {
|
||||
@ -563,7 +564,7 @@ pub const Face = struct {
|
||||
const len = data.getLength();
|
||||
break :post opentype.Post.init(ptr[0..len]) catch |err| {
|
||||
return switch (err) {
|
||||
error.EndOfStream => error.InvalidOS2Table,
|
||||
error.EndOfStream => error.InvalidPostTable,
|
||||
};
|
||||
};
|
||||
};
|
||||
@ -583,13 +584,73 @@ pub const Face = struct {
|
||||
};
|
||||
};
|
||||
|
||||
// Read the 'hhea' table out of the font data.
|
||||
const hhea: opentype.Hhea = hhea: {
|
||||
const tag = macos.text.FontTableTag.init("hhea");
|
||||
const data = ct_font.copyTable(tag) orelse return error.CopyTableError;
|
||||
defer data.release();
|
||||
const ptr = data.getPointer();
|
||||
const len = data.getLength();
|
||||
break :hhea opentype.Hhea.init(ptr[0..len]) catch |err| {
|
||||
return switch (err) {
|
||||
error.EndOfStream => error.InvalidHheaTable,
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
const units_per_em: f64 = @floatFromInt(head.unitsPerEm);
|
||||
const px_per_em: f64 = ct_font.getSize();
|
||||
const px_per_unit: f64 = px_per_em / units_per_em;
|
||||
|
||||
const ascent = @as(f64, @floatFromInt(os2.sTypoAscender)) * px_per_unit;
|
||||
const descent = @as(f64, @floatFromInt(os2.sTypoDescender)) * px_per_unit;
|
||||
const line_gap = @as(f64, @floatFromInt(os2.sTypoLineGap)) * px_per_unit;
|
||||
const ascent: f64, const descent: f64, const line_gap: f64 = vertical_metrics: {
|
||||
const os2_ascent: f64 = @floatFromInt(os2.sTypoAscender);
|
||||
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_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) {
|
||||
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_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 .{
|
||||
win_ascent * px_per_unit,
|
||||
win_descent * px_per_unit,
|
||||
0.0,
|
||||
};
|
||||
};
|
||||
|
||||
// Some fonts have degenerate 'post' tables where the underline
|
||||
// thickness (and often position) are 0. We consider them null
|
||||
|
@ -631,6 +631,9 @@ pub const Face = struct {
|
||||
// Read the 'OS/2' table out of the font data.
|
||||
const os2 = face.getSfntTable(.os2) orelse return error.CopyTableError;
|
||||
|
||||
// Read the 'hhea' table out of the font data.
|
||||
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
|
||||
@ -640,9 +643,56 @@ pub const Face = struct {
|
||||
const px_per_em: f64 = @floatFromInt(size_metrics.y_ppem);
|
||||
const px_per_unit = px_per_em / @as(f64, @floatFromInt(units_per_em));
|
||||
|
||||
const ascent = @as(f64, @floatFromInt(os2.sTypoAscender)) * px_per_unit;
|
||||
const descent = @as(f64, @floatFromInt(os2.sTypoDescender)) * px_per_unit;
|
||||
const line_gap = @as(f64, @floatFromInt(os2.sTypoLineGap)) * px_per_unit;
|
||||
const ascent: f64, const descent: f64, const line_gap: f64 = vertical_metrics: {
|
||||
const os2_ascent: f64 = @floatFromInt(os2.sTypoAscender);
|
||||
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) {
|
||||
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 .{
|
||||
win_ascent * px_per_unit,
|
||||
win_descent * px_per_unit,
|
||||
0.0,
|
||||
};
|
||||
};
|
||||
|
||||
// Some fonts have degenerate 'post' tables where the underline
|
||||
// thickness (and often position) are 0. We consider them null
|
||||
|
Reference in New Issue
Block a user