1
0
mirror of https://github.com/emilk/egui.git synced 2026-06-26 22:53:14 -04:00

Stop using ab_glyph scaling entirely

This commit is contained in:
valadaptive
2025-09-07 21:04:48 -04:00
parent f560b6a9ff
commit 95458f4b52
3 changed files with 96 additions and 98 deletions

View File

@@ -1,6 +1,6 @@
use std::collections::BTreeMap;
use ab_glyph::{Font as _, PxScaleFont, ScaleFont as _};
use ab_glyph::{Font as _, OutlinedGlyph, PxScale};
use emath::{GuiRounding as _, OrderedFloat, Vec2, vec2};
use crate::{
@@ -86,28 +86,18 @@ pub struct FontImpl {
}
trait FontExt {
fn pt_scaled(&self, scale: f32) -> PxScaleFont<&'_ Self>;
fn pt_scale_factor(&self, scale: f32) -> f32;
fn px_scale_factor(&self, scale: f32) -> f32;
}
impl<T> FontExt for T
where
T: ab_glyph::Font,
{
fn pt_scaled(&self, scale: f32) -> PxScaleFont<&'_ Self> {
PxScaleFont {
font: self,
scale: self.pt_scale_factor(scale).into(),
}
}
fn pt_scale_factor(&self, scale: f32) -> f32 {
fn px_scale_factor(&self, scale: f32) -> f32 {
let units_per_em = self.units_per_em().unwrap_or_else(|| {
panic!("The font unit size exceeds the expected range (16..=16384)")
});
let font_scaling = self.height_unscaled() / units_per_em;
scale * font_scaling
scale / units_per_em
}
}
@@ -216,51 +206,66 @@ impl FontImpl {
#[inline]
pub fn pair_kerning(
&self,
pixels_per_point: f32,
metrics: &ScaledMetrics,
last_glyph_id: ab_glyph::GlyphId,
glyph_id: ab_glyph::GlyphId,
font_size: f32,
) -> f32 {
// Round to an even number of physical pixels to get even kerning.
// See https://github.com/emilk/egui/issues/382
self.ab_glyph_font
.pt_scaled((font_size * self.tweak.scale * pixels_per_point).round())
.kern(last_glyph_id, glyph_id)
/ pixels_per_point
self.ab_glyph_font.kern_unscaled(last_glyph_id, glyph_id) * metrics.px_scale_factor
/ metrics.pixels_per_point
}
#[inline(always)]
pub fn scaled_metrics(&self, font_size: f32) -> ScaledMetrics {
let font = self.ab_glyph_font.pt_scaled(font_size);
pub fn scaled_metrics(&self, pixels_per_point: f32, font_size: f32) -> ScaledMetrics {
let pt_scale_factor = self
.ab_glyph_font
.px_scale_factor(font_size * self.tweak.scale);
let ascent = (self.ab_glyph_font.ascent_unscaled() * pt_scale_factor).round_ui();
let descent = (self.ab_glyph_font.descent_unscaled() * pt_scale_factor).round_ui();
let line_gap = (self.ab_glyph_font.line_gap_unscaled() * pt_scale_factor).round_ui();
// Round to an even number of physical pixels to get even kerning.
// See https://github.com/emilk/egui/issues/382
let px_scale_factor = self
.ab_glyph_font
.px_scale_factor((font_size * self.tweak.scale * pixels_per_point).round());
let y_offset_points = ((font_size * self.tweak.scale * self.tweak.y_offset_factor)
+ self.tweak.y_offset)
.round_ui();
// Center scaled glyphs properly:
let height = ascent + descent;
let y_offset_points = y_offset_points - (1.0 - self.tweak.scale) * 0.5 * height;
// Round to closest pixel:
let y_offset_in_points = (y_offset_points * pixels_per_point).round() / pixels_per_point;
ScaledMetrics {
ascent: font.ascent().round_ui(),
row_height: font.ascent().round_ui() - font.descent().round_ui()
+ font.line_gap().round_ui(),
pixels_per_point,
px_scale_factor,
y_offset_in_points,
ascent,
row_height: ascent - descent + line_gap,
}
}
pub fn allocate_glyph(
&mut self,
atlas: &mut TextureAtlas,
pixels_per_point: f32,
metrics: &ScaledMetrics,
glyph_info: GlyphInfo,
font_size: f32,
) -> GlyphAllocation {
let Some(glyph_id) = glyph_info.id else {
// Invisible.
return GlyphAllocation::default();
};
// Round to an even number of physical pixels to get even kerning.
// See https://github.com/emilk/egui/issues/382
let scale = self
.ab_glyph_font
.pt_scale_factor(font_size * self.tweak.scale * pixels_per_point)
.round();
let entry = match self.glyph_alloc_cache.entry((
glyph_info,
font_size.into(),
pixels_per_point.into(),
metrics.px_scale_factor.into(),
metrics.pixels_per_point.into(),
)) {
std::collections::hash_map::Entry::Occupied(glyph_alloc) => {
return *glyph_alloc.get();
@@ -270,27 +275,24 @@ impl FontImpl {
debug_assert!(glyph_id.0 != 0, "Can't allocate glyph for id 0");
let glyph = glyph_id.with_scale_and_position(scale, ab_glyph::Point { x: 0.0, y: 0.0 });
// Tweak the scale as the user desired
let y_offset_in_points = {
let logically_scaled = self.ab_glyph_font.pt_scaled(font_size * pixels_per_point);
let scale_in_points = scale / pixels_per_point;
let y_offset_points =
((scale_in_points * self.tweak.y_offset_factor) + self.tweak.y_offset).round_ui();
// Center scaled glyphs properly:
let height = (logically_scaled.ascent() / pixels_per_point).round_ui()
+ (logically_scaled.descent() / pixels_per_point).round_ui();
let y_offset_points = y_offset_points - (1.0 - self.tweak.scale) * 0.5 * height;
// Round to closest pixel:
(y_offset_points * pixels_per_point).round() / pixels_per_point
};
let uv_rect = self.ab_glyph_font.outline_glyph(glyph).map(|glyph| {
let bb = glyph.px_bounds();
let uv_rect = self.ab_glyph_font.outline(glyph_id).map(|outline| {
let glyph = ab_glyph::Glyph {
id: glyph_id,
// We bypass ab-glyph's scaling method because it uses the wrong scale
// (https://github.com/alexheretic/ab-glyph/issues/15), and this field is never accessed when
// rasterizing. We can just put anything here.
scale: PxScale::from(0.0),
position: ab_glyph::Point::default(),
};
let outlined = OutlinedGlyph::new(
glyph,
outline,
ab_glyph::PxScaleFactor {
horizontal: metrics.px_scale_factor,
vertical: metrics.px_scale_factor,
},
);
let bb = outlined.px_bounds();
let glyph_width = bb.width() as usize;
let glyph_height = bb.height() as usize;
if glyph_width == 0 || glyph_height == 0 {
@@ -299,7 +301,7 @@ impl FontImpl {
let glyph_pos = {
let text_alpha_from_coverage = atlas.text_alpha_from_coverage;
let (glyph_pos, image) = atlas.allocate((glyph_width, glyph_height));
glyph.draw(|x, y, v| {
outlined.draw(|x, y, v| {
if 0.0 < v {
let px = glyph_pos.0 + x as usize;
let py = glyph_pos.1 + y as usize;
@@ -310,10 +312,11 @@ impl FontImpl {
};
let offset_in_pixels = vec2(bb.min.x, bb.min.y);
let offset = offset_in_pixels / pixels_per_point + y_offset_in_points * Vec2::Y;
let offset = offset_in_pixels / metrics.pixels_per_point
+ metrics.y_offset_in_points * Vec2::Y;
UvRect {
offset,
size: vec2(glyph_width as f32, glyph_height as f32) / pixels_per_point,
size: vec2(glyph_width as f32, glyph_height as f32) / metrics.pixels_per_point,
min: [glyph_pos.0 as u16, glyph_pos.1 as u16],
max: [
(glyph_pos.0 + glyph_width) as u16,
@@ -326,9 +329,8 @@ impl FontImpl {
let allocation = GlyphAllocation {
id: glyph_id,
advance_width: (glyph_info.advance_width_unscaled.0 * scale
/ self.ab_glyph_font.height_unscaled())
/ pixels_per_point,
advance_width: (glyph_info.advance_width_unscaled.0 * metrics.px_scale_factor)
/ metrics.pixels_per_point,
uv_rect,
};
entry.insert(allocation);
@@ -376,12 +378,12 @@ impl Font<'_> {
})
}
pub fn scaled_metrics(&self, font_size: f32) -> ScaledMetrics {
pub fn scaled_metrics(&self, pixels_per_point: f32, font_size: f32) -> ScaledMetrics {
self.cached_family
.fonts
.first()
.and_then(|key| self.fonts_by_id.get(key))
.map(|font_impl| font_impl.scaled_metrics(font_size))
.map(|font_impl| font_impl.scaled_metrics(pixels_per_point, font_size))
.unwrap_or_default()
}
@@ -393,8 +395,7 @@ impl Font<'_> {
.get(&key)
.expect("Nonexistent font ID")
.ab_glyph_font;
glyph_info.advance_width_unscaled.0 * font.pt_scale_factor(font_size)
/ font.height_unscaled()
glyph_info.advance_width_unscaled.0 * font.px_scale_factor(font_size)
}
/// Can we display this glyph?
@@ -427,6 +428,9 @@ impl Font<'_> {
#[derive(Clone, Copy, Debug, PartialEq, Default)]
pub struct ScaledMetrics {
pub pixels_per_point: f32,
pub px_scale_factor: f32,
pub y_offset_in_points: f32,
/// This is the distance from the top to the baseline.
///
/// Unit: points.

View File

@@ -673,7 +673,7 @@ impl FontsView<'_> {
pub fn row_height(&mut self, font_id: &FontId) -> f32 {
self.fonts
.font(&font_id.family)
.scaled_metrics(font_id.size)
.scaled_metrics(self.pixels_per_point, font_id.size)
.row_height
}

View File

@@ -157,7 +157,7 @@ fn layout_section(
} = section;
let mut font = fonts.font(&format.font_id.family);
let font_size = format.font_id.size;
let font_metrics = font.scaled_metrics(font_size);
let font_metrics = font.scaled_metrics(pixels_per_point, font_size);
let line_height = section
.format
.line_height
@@ -182,32 +182,29 @@ fn layout_section(
} else {
let (font_id, glyph_info) = font.glyph_info(chr);
let (mut font_impl, font_impl_metrics) = match last_font {
Some((last_font_id, last_font_metrics)) if last_font_id == font_id => {
(font.fonts_by_id.get_mut(&font_id), last_font_metrics)
}
Some((last_font_id, last_font_metrics)) if last_font_id == font_id => (
font.fonts_by_id.get_mut(&font_id),
last_font_metrics.unwrap_or_default(),
),
_ => {
let font_impl = font.fonts_by_id.get_mut(&font_id);
let scaled_metrics = font_impl
.as_ref()
.map(|font_impl| font_impl.scaled_metrics(font_size));
.map(|font_impl| font_impl.scaled_metrics(pixels_per_point, font_size));
last_font = Some((font_id, scaled_metrics));
(font_impl, scaled_metrics)
(font_impl, scaled_metrics.unwrap_or_default())
}
};
let glyph_alloc = match font_impl.as_mut() {
Some(font_impl) => {
font_impl.allocate_glyph(font.atlas, pixels_per_point, glyph_info, font_size)
font_impl.allocate_glyph(font.atlas, &font_impl_metrics, glyph_info)
}
None => Default::default(),
};
if let (Some(font_impl), Some(last_glyph_id)) = (&font_impl, last_glyph_id) {
paragraph.cursor_x += font_impl.pair_kerning(
pixels_per_point,
last_glyph_id,
glyph_alloc.id,
font_size,
);
paragraph.cursor_x +=
font_impl.pair_kerning(&font_impl_metrics, last_glyph_id, glyph_alloc.id);
paragraph.cursor_x += extra_letter_spacing;
}
@@ -216,8 +213,8 @@ fn layout_section(
pos: pos2(paragraph.cursor_x, f32::NAN),
advance_width: glyph_alloc.advance_width,
line_height,
font_impl_height: font_impl_metrics.map_or(0.0, |m| m.row_height),
font_impl_ascent: font_impl_metrics.map_or(0.0, |m| m.ascent),
font_impl_height: font_impl_metrics.row_height,
font_impl_ascent: font_impl_metrics.ascent,
font_height: font_metrics.row_height,
font_ascent: font_metrics.ascent,
uv_rect: glyph_alloc.uv_rect,
@@ -457,9 +454,17 @@ fn replace_last_glyph_with_overflow_character(
let (font_id, glyph_info) = font.glyph_info(overflow_character);
let mut font_impl = font.fonts_by_id.get_mut(&font_id);
let font_impl_metrics = font_impl
.as_ref()
.map(|f| f.scaled_metrics(pixels_per_point, font_size))
.unwrap_or_default();
let replacement_glyph_alloc = font_impl
.as_mut()
.map(|f| f.allocate_glyph(font.atlas, pixels_per_point, glyph_info, font_size))
.map(|f| f.allocate_glyph(font.atlas, &font_impl_metrics, glyph_info))
.unwrap_or_default();
let font_impl_metrics = font_impl
.as_mut()
.map(|f| f.scaled_metrics(pixels_per_point, font_size))
.unwrap_or_default();
let overflow_glyph_x = if let Some(prev_glyph) = row.glyphs.last() {
@@ -471,12 +476,7 @@ fn replace_last_glyph_with_overflow_character(
font_impl.glyph_info(prev_glyph.chr).and_then(|g| g.id),
font_impl.glyph_info(overflow_character).and_then(|g| g.id),
) {
font_impl.pair_kerning(
pixels_per_point,
prev_glyph_id,
overflow_glyph_id,
font_size,
)
font_impl.pair_kerning(&font_impl_metrics, prev_glyph_id, overflow_glyph_id)
} else {
0.0
}
@@ -494,13 +494,7 @@ fn replace_last_glyph_with_overflow_character(
{
// we are done
// We need to calculate these first since `font_impl` is mutably borrowed from `font`, which is later used
// to calculate the row height
let font_impl_metrics = font_impl
.as_mut()
.map(|f| f.scaled_metrics(font_size))
.unwrap_or_default();
let font_metrics = font.scaled_metrics(font_size);
let font_metrics = font.scaled_metrics(pixels_per_point, font_size);
let line_height = section
.format
.line_height
@@ -1205,7 +1199,7 @@ mod tests {
let font_id = FontId::default();
let font_height = fonts
.font(&font_id.family)
.scaled_metrics(font_id.size)
.scaled_metrics(pixels_per_point, font_id.size)
.row_height;
let job = LayoutJob::simple(String::new(), font_id, Color32::WHITE, f32::INFINITY);
@@ -1242,7 +1236,7 @@ mod tests {
let font_id = FontId::default();
let font_height = fonts
.font(&font_id.family)
.scaled_metrics(font_id.size)
.scaled_metrics(pixels_per_point, font_id.size)
.row_height;
let job = LayoutJob::simple("Hi!\n".to_owned(), font_id, Color32::WHITE, f32::INFINITY);