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:
parent
adbe545d44
commit
3fae59b01b
@ -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()
|
||||||
|
@ -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,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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);
|
||||||
|
@ -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());
|
||||||
|
@ -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 {
|
||||||
|
@ -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 {
|
||||||
|
Loading…
Reference in New Issue
Block a user