1
1
mirror of https://github.com/wez/wezterm.git synced 2024-11-27 12:23:46 +03:00

fonts: use cap-height metric to scale fallback fonts

we now compute the ratio of the cap height (the height of a capital
letter) vs. the em-square (which relates to our chosen point size) to
understand what proportion of the font point-size that a given font
occupies when rendered.

When rendering glyphs from secondary fonts we can use the cap height
ratios of both to scale the secondary font such that its effective
cap height matches that of the primary font.

In plainer-english: if you mix say bold, italic and regular text
style in the same line, and you have different font families for
those fonts, then they will now appear to be the same height where
previously they may have varied more noticeably.

For emoji and symbol fonts there may not be a cap-height metric
encoded in the font.  We can however, improve our scaling: prior
to this commit we'd use the ratio of the cell metrics of the two
fonts to scale the icon/emoji glyph, but this could cause the glyph
to be slightly oversized as seen in https://github.com/wez/wezterm/issues/624

If we know the cap-height of the primary font then we can additionaly
apply that factor to scale the emoji to better fit the cell.

While looking at this, I noticed that the aspect ratio calculation
for when to apply to the allow_square_glyphs_to_overflow_width option
had width and height flipped :-(

See also: https://tonsky.me/blog/font-size/
refs: https://github.com/wez/wezterm/issues/624
This commit is contained in:
Wez Furlong 2021-04-13 22:55:34 -07:00
parent adbe545d44
commit 3fae59b01b
6 changed files with 48 additions and 4 deletions

View File

@ -174,6 +174,18 @@ impl Face {
} }
} }
/// Returns the cap_height/units_per_EM ratio if known
pub fn cap_height(&self) -> Option<f64> {
unsafe {
let os2 = self.get_os2_table()?;
let units_per_em = (*self.face).units_per_EM;
if units_per_em == 0 || os2.sCapHeight == 0 {
return None;
}
Some(os2.sCapHeight as f64 / units_per_em as f64)
}
}
pub fn weight_and_width(&self) -> (u16, u16) { pub fn weight_and_width(&self) -> (u16, u16) {
let (mut weight, mut width) = self let (mut weight, mut width) = self
.get_os2_table() .get_os2_table()

View File

@ -18,6 +18,7 @@ pub struct ParsedFont {
weight: FontWeight, weight: FontWeight,
stretch: FontStretch, stretch: FontStretch,
italic: bool, italic: bool,
cap_height: Option<f64>,
pub handle: FontDataHandle, pub handle: FontDataHandle,
coverage: Mutex<RangeSet<u32>>, coverage: Mutex<RangeSet<u32>>,
} }
@ -30,6 +31,7 @@ impl std::fmt::Debug for ParsedFont {
.field("stretch", &self.stretch) .field("stretch", &self.stretch)
.field("italic", &self.italic) .field("italic", &self.italic)
.field("handle", &self.handle) .field("handle", &self.handle)
.field("cap_height", &self.cap_height)
.finish() .finish()
} }
} }
@ -42,6 +44,7 @@ impl Clone for ParsedFont {
stretch: self.stretch, stretch: self.stretch,
italic: self.italic, italic: self.italic,
handle: self.handle.clone(), handle: self.handle.clone(),
cap_height: self.cap_height.clone(),
coverage: Mutex::new(self.coverage.lock().unwrap().clone()), coverage: Mutex::new(self.coverage.lock().unwrap().clone()),
} }
} }
@ -127,6 +130,7 @@ impl ParsedFont {
let (weight, width) = face.weight_and_width(); let (weight, width) = face.weight_and_width();
let weight = FontWeight::from_opentype_weight(weight); let weight = FontWeight::from_opentype_weight(weight);
let stretch = FontStretch::from_opentype_stretch(width); let stretch = FontStretch::from_opentype_stretch(width);
let cap_height = face.cap_height();
Ok(Self { Ok(Self {
names: Names::from_ft_face(&face), names: Names::from_ft_face(&face),
@ -135,6 +139,7 @@ impl ParsedFont {
italic, italic,
handle, handle,
coverage: Mutex::new(RangeSet::new()), coverage: Mutex::new(RangeSet::new()),
cap_height,
}) })
} }

View File

@ -226,6 +226,7 @@ impl AllsortsParsedFont {
descender, descender,
underline_thickness, underline_thickness,
underline_position, underline_position,
cap_height_ratio: None,
}; };
log::trace!("metrics: {:?}", metrics); log::trace!("metrics: {:?}", metrics);

View File

@ -411,6 +411,7 @@ impl FontShaper for HarfbuzzShaper {
underline_position: PixelLength::new( underline_position: PixelLength::new(
unsafe { (*pair.face.face).underline_position as f64 } * y_scale / 64., unsafe { (*pair.face.face).underline_position as f64 } * y_scale / 64.,
), ),
cap_height_ratio: pair.face.cap_height(),
}; };
self.metrics.borrow_mut().insert(key, metrics.clone()); self.metrics.borrow_mut().insert(key, metrics.clone());

View File

@ -51,6 +51,9 @@ pub struct FontMetrics {
/// Position of underline relative to descender. Negative /// Position of underline relative to descender. Negative
/// values are below the descender. /// values are below the descender.
pub underline_position: PixelLength, pub underline_position: PixelLength,
/// Fraction of the EM square occupied by the cap height
pub cap_height_ratio: Option<f64>,
} }
pub trait FontShaper { pub trait FontShaper {

View File

@ -465,11 +465,33 @@ impl<T: Texture2d> GlyphCache<T> {
idx_metrics = font.metrics_for_idx(info.font_idx)?; idx_metrics = font.metrics_for_idx(info.font_idx)?;
} }
let y_scale = base_metrics.cell_height.get() / idx_metrics.cell_height.get(); let x_scale;
let x_scale = let y_scale;
base_metrics.cell_width.get() / (idx_metrics.cell_width.get() / info.num_cells as f64);
let aspect = (idx_metrics.cell_height / idx_metrics.cell_width).get(); if info.font_idx == 0 {
// The base font is the current font, so there's no additional metrics
// based scaling, however, we may need to scale to accomodate num_cells
x_scale = 1.0 / info.num_cells as f64;
y_scale = 1.0;
} else if let (Some(base_cap), Some(cap)) =
(base_metrics.cap_height_ratio, idx_metrics.cap_height_ratio)
{
// If we know the cap height ratio for both fonts, we can scale
// the second one to match the cap height of the first
x_scale = base_cap / cap;
y_scale = x_scale / info.num_cells as f64;
} else {
// Otherwise, we scale based on the ratio of the metrics for
// the two fonts.
// If we know the cap height ratio for the first, we can adjust
// the overall scale so that the second font isn't oversized
let base_cap = base_metrics.cap_height_ratio.unwrap_or(1.);
y_scale = base_cap * base_metrics.cell_height.get() / idx_metrics.cell_height.get();
x_scale = base_cap * base_metrics.cell_width.get()
/ (idx_metrics.cell_width.get() / info.num_cells as f64);
}
let aspect = (idx_metrics.cell_width / idx_metrics.cell_height).get();
let is_square_or_wide = aspect >= 0.9; let is_square_or_wide = aspect >= 0.9;
let allow_width_overflow = if is_square_or_wide { let allow_width_overflow = if is_square_or_wide {