Measure ascii height and use to upper bound ic_width

pull/8720/head
Daniel Wennberg 2025-09-17 08:57:43 -07:00
parent 0f0a61c38d
commit 6781fbda93
3 changed files with 60 additions and 10 deletions

View File

@ -117,6 +117,16 @@ pub const FaceMetrics = struct {
/// lowercase x glyph. /// lowercase x glyph.
ex_height: ?f64 = null, ex_height: ?f64 = null,
/// The measured height of the bounding box containing all printable
/// ASCII characters. This can be different from ascent - descent for
/// two reasons: non-letter symbols like @ and $ often exceed the
/// the ascender and descender lines; and fonts often bake the line
/// gap into the ascent and descent metrics (as per, e.g., the Google
/// Fonts guidelines: https://simoncozens.github.io/gf-docs/metrics.html).
///
/// Positive value in px
ascii_height: ?f64 = null,
/// The width of the character "" (CJK water ideograph, U+6C34), /// The width of the character "" (CJK water ideograph, U+6C34),
/// if present. This is used for font size adjustment, to normalize /// if present. This is used for font size adjustment, to normalize
/// the width of CJK fonts mixed with latin fonts. /// the width of CJK fonts mixed with latin fonts.
@ -144,11 +154,20 @@ pub const FaceMetrics = struct {
return 0.75 * self.capHeight(); return 0.75 * self.capHeight();
} }
/// Convenience function for getting the ASCII height. If we
/// couldn't measure this, we use 1.5 * cap_height as our
/// estimator, based on measurements across programming fonts.
pub inline fn asciiHeight(self: FaceMetrics) f64 {
if (self.ascii_height) |value| if (value > 0) return value;
return 1.5 * self.capHeight();
}
/// Convenience function for getting the ideograph width. If this is /// Convenience function for getting the ideograph width. If this is
/// not defined in the font, we estimate it as two cell widths. /// not defined in the font, we estimate it as the minimum of the
/// ascii height and two cell widths.
pub inline fn icWidth(self: FaceMetrics) f64 { pub inline fn icWidth(self: FaceMetrics) f64 {
if (self.ic_width) |value| if (value > 0) return value; if (self.ic_width) |value| if (value > 0) return value;
return 2 * self.cell_width; return @min(self.asciiHeight(), 2 * self.cell_width);
} }
/// Convenience function for getting the underline thickness. If /// Convenience function for getting the underline thickness. If

View File

@ -775,7 +775,10 @@ pub const Face = struct {
// 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
// whatever is widest. // whatever is widest.
const cell_width: f64 = cell_width: { //
// ASCII height is calculated as the height of the overall bounding
// box of the same characters.
const cell_width: f64, const ascii_height: f64 = measurements: {
// Build a comptime array of all the ASCII chars // Build a comptime array of all the ASCII chars
const unichars = comptime unichars: { const unichars = comptime unichars: {
const len = 127 - 32; const len = 127 - 32;
@ -803,7 +806,10 @@ pub const Face = struct {
max = @max(advances[i].width, max); max = @max(advances[i].width, max);
} }
break :cell_width max; // Get the overall bounding rect for the glyphs
const rect = ct_font.getBoundingRectsForGlyphs(.horizontal, &glyphs, null);
break :measurements .{ max, rect.size.height };
}; };
// Measure "" (CJK water ideograph, U+6C34) for our ic width. // Measure "" (CJK water ideograph, U+6C34) for our ic width.
@ -864,6 +870,7 @@ pub const Face = struct {
.cap_height = cap_height, .cap_height = cap_height,
.ex_height = ex_height, .ex_height = ex_height,
.ascii_height = ascii_height,
.ic_width = ic_width, .ic_width = ic_width,
}; };
} }

View File

@ -960,13 +960,19 @@ pub const Face = struct {
// visible ASCII characters. Usually 'M' is widest but we just take // visible ASCII characters. Usually 'M' is widest but we just take
// whatever is widest. // whatever is widest.
// //
// ASCII height is calculated as the height of the overall bounding
// box of the same characters.
//
// If we fail to load any visible ASCII we just use max_advance from // If we fail to load any visible ASCII we just use max_advance from
// the metrics provided by FreeType. // the metrics provided by FreeType, and set ascii_height to null as
const cell_width: f64 = cell_width: { // it's optional.
const cell_width: f64, const ascii_height: ?f64 = measurements: {
self.ft_mutex.lock(); self.ft_mutex.lock();
defer self.ft_mutex.unlock(); defer self.ft_mutex.unlock();
var max: f64 = 0.0; var max: f64 = 0.0;
var top: f64 = 0.0;
var bottom: f64 = 0.0;
var c: u8 = ' '; var c: u8 = ' ';
while (c < 127) : (c += 1) { while (c < 127) : (c += 1) {
if (face.getCharIndex(c)) |glyph_index| { if (face.getCharIndex(c)) |glyph_index| {
@ -974,20 +980,37 @@ pub const Face = struct {
.render = false, .render = false,
.no_svg = true, .no_svg = true,
})) { })) {
const glyph = face.handle.*.glyph;
max = @max( max = @max(
f26dot6ToF64(face.handle.*.glyph.*.advance.x), f26dot6ToF64(glyph.*.advance.x),
max, max,
); );
top = @max(
f26dot6ToF64(glyph.*.metrics.horiBearingY),
top,
);
bottom = @min(
f26dot6ToF64(glyph.*.metrics.horiBearingY - glyph.*.metrics.height),
bottom,
);
} else |_| {} } else |_| {}
} }
} }
// If we couldn't get any widths, just use FreeType's max_advance. // If we couldn't get valid measurements, just use
// FreeType's max_advance and null, respectively.
if (max == 0.0) { if (max == 0.0) {
break :cell_width f26dot6ToF64(size_metrics.max_advance); max = f26dot6ToF64(size_metrics.max_advance);
} }
const rect_height: ?f64 = rect_height: {
const estimate = top - bottom;
if (estimate <= 0.0) {
break :rect_height null;
}
break :rect_height estimate;
};
break :cell_width max; break :measurements .{ max, rect_height };
}; };
// We use the cap and ex heights specified by the font if they're // We use the cap and ex heights specified by the font if they're
@ -1089,6 +1112,7 @@ pub const Face = struct {
.cap_height = cap_height, .cap_height = cap_height,
.ex_height = ex_height, .ex_height = ex_height,
.ascii_height = ascii_height,
.ic_width = ic_width, .ic_width = ic_width,
}; };
} }