From f560b6a9ff4ac3c785650a3ff32784a3b45d372c Mon Sep 17 00:00:00 2001 From: valadaptive Date: Sun, 7 Sep 2025 18:23:34 -0400 Subject: [PATCH] Cache scaled font metrics --- crates/epaint/src/text/font.rs | 73 ++++++++----------------- crates/epaint/src/text/fonts.rs | 5 +- crates/epaint/src/text/text_layout.rs | 79 ++++++++++++++++++++------- 3 files changed, 85 insertions(+), 72 deletions(-) diff --git a/crates/epaint/src/text/font.rs b/crates/epaint/src/text/font.rs index 382b80c4c..5cac8e79a 100644 --- a/crates/epaint/src/text/font.rs +++ b/crates/epaint/src/text/font.rs @@ -229,22 +229,14 @@ impl FontImpl { / pixels_per_point } - /// Height of one row of text in points. - /// - /// Returns a value rounded to [`emath::GUI_ROUNDING`]. #[inline(always)] - pub fn row_height(&self, font_size: f32) -> f32 { + pub fn scaled_metrics(&self, font_size: f32) -> ScaledMetrics { let font = self.ab_glyph_font.pt_scaled(font_size); - - font.ascent().round_ui() - font.descent().round_ui() + font.line_gap().round_ui() - } - - /// This is the distance from the top to the baseline. - /// - /// Unit: points. - #[inline(always)] - pub fn ascent(&self, font_size: f32) -> f32 { - self.ab_glyph_font.pt_scaled(font_size).ascent().round_ui() + ScaledMetrics { + ascent: font.ascent().round_ui(), + row_height: font.ascent().round_ui() - font.descent().round_ui() + + font.line_gap().round_ui(), + } } pub fn allocate_glyph( @@ -384,20 +376,13 @@ impl Font<'_> { }) } - /// Height of one row of text. In points. - /// - /// Returns a value rounded to [`emath::GUI_ROUNDING`]. - #[inline(always)] - pub fn row_height(&self, font_size: f32) -> f32 { - let Some(first_font) = self - .cached_family + pub fn scaled_metrics(&self, font_size: f32) -> ScaledMetrics { + self.cached_family .fonts .first() .and_then(|key| self.fonts_by_id.get(key)) - else { - return 0.0; - }; - first_font.row_height(font_size) + .map(|font_impl| font_impl.scaled_metrics(font_size)) + .unwrap_or_default() } /// Width of this character in points. @@ -423,7 +408,7 @@ impl Font<'_> { } /// `\n` will (intentionally) show up as the replacement character. - fn glyph_info(&mut self, c: char) -> (FontFaceKey, GlyphInfo) { + pub(crate) fn glyph_info(&mut self, c: char) -> (FontFaceKey, GlyphInfo) { if let Some(font_index_glyph_info) = self.cached_family.glyph_info_cache.get(&c) { return *font_index_glyph_info; } @@ -438,32 +423,18 @@ impl Font<'_> { .insert(c, font_index_glyph_info); font_index_glyph_info } +} - #[inline] - pub(crate) fn font_impl_and_glyph_alloc( - &mut self, - pixels_per_point: f32, - c: char, - font_size: f32, - ) -> (Option<&mut FontImpl>, GlyphAllocation) { - if self.cached_family.fonts.is_empty() { - return (None, Default::default()); - } - let (key, glyph_info) = self.glyph_info(c); - let font_impl = self.fonts_by_id.get_mut(&key).expect("Nonexistent font ID"); - let allocated_glyph = - font_impl.allocate_glyph(self.atlas, pixels_per_point, glyph_info, font_size); - (Some(font_impl), allocated_glyph) - } - - pub(crate) fn ascent(&self, font_size: f32) -> f32 { - if let Some(first) = self.cached_family.fonts.first() { - let first = self.fonts_by_id.get(first).expect("Nonexistent font ID"); - first.ascent(font_size) - } else { - self.row_height(font_size) - } - } +#[derive(Clone, Copy, Debug, PartialEq, Default)] +pub struct ScaledMetrics { + /// This is the distance from the top to the baseline. + /// + /// Unit: points. + pub ascent: f32, + /// Height of one row of text in points. + /// + /// Returns a value rounded to [`emath::GUI_ROUNDING`]. + pub row_height: f32, } /// Code points that will always be invisible (zero width). diff --git a/crates/epaint/src/text/fonts.rs b/crates/epaint/src/text/fonts.rs index 844fd019d..0e5acb1e6 100644 --- a/crates/epaint/src/text/fonts.rs +++ b/crates/epaint/src/text/fonts.rs @@ -671,7 +671,10 @@ impl FontsView<'_> { /// /// Returns a value rounded to [`emath::GUI_ROUNDING`]. pub fn row_height(&mut self, font_id: &FontId) -> f32 { - self.fonts.font(&font_id.family).row_height(font_id.size) + self.fonts + .font(&font_id.family) + .scaled_metrics(font_id.size) + .row_height } /// List of all known font families. diff --git a/crates/epaint/src/text/text_layout.rs b/crates/epaint/src/text/text_layout.rs index 7fdd97043..61147c9b0 100644 --- a/crates/epaint/src/text/text_layout.rs +++ b/crates/epaint/src/text/text_layout.rs @@ -2,7 +2,11 @@ use std::sync::Arc; use emath::{Align, GuiRounding as _, NumExt as _, Pos2, Rect, Vec2, pos2, vec2}; -use crate::{Color32, Mesh, Stroke, Vertex, stroke::PathStroke}; +use crate::{ + Color32, Mesh, Stroke, Vertex, + stroke::PathStroke, + text::{font::ScaledMetrics, fonts::FontFaceKey}, +}; use super::{FontsImpl, Galley, Glyph, LayoutJob, LayoutSection, PlacedRow, Row, RowVisuals}; @@ -153,10 +157,11 @@ 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 line_height = section .format .line_height - .unwrap_or_else(|| font.row_height(font_size)); + .unwrap_or(font_metrics.row_height); let extra_letter_spacing = section.format.extra_letter_spacing; let mut paragraph = out_paragraphs.last_mut().unwrap(); @@ -168,14 +173,33 @@ fn layout_section( let mut last_glyph_id = None; + let mut last_font: Option<(FontFaceKey, Option)> = None; for chr in job.text[byte_range.clone()].chars() { if job.break_on_newline && chr == '\n' { out_paragraphs.push(Paragraph::from_section_index(section_index)); paragraph = out_paragraphs.last_mut().unwrap(); paragraph.empty_paragraph_height = line_height; // TODO(emilk): replace this hack with actually including `\n` in the glyphs? } else { - let (font_impl, glyph_alloc) = - font.font_impl_and_glyph_alloc(pixels_per_point, chr, font_size); + 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) + } + _ => { + 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)); + last_font = Some((font_id, scaled_metrics)); + (font_impl, scaled_metrics) + } + }; + let glyph_alloc = match font_impl.as_mut() { + Some(font_impl) => { + font_impl.allocate_glyph(font.atlas, pixels_per_point, glyph_info, font_size) + } + None => Default::default(), + }; if let (Some(font_impl), Some(last_glyph_id)) = (&font_impl, last_glyph_id) { paragraph.cursor_x += font_impl.pair_kerning( @@ -192,10 +216,10 @@ fn layout_section( pos: pos2(paragraph.cursor_x, f32::NAN), advance_width: glyph_alloc.advance_width, line_height, - font_impl_height: font_impl.as_ref().map_or(0.0, |f| f.row_height(font_size)), - font_impl_ascent: font_impl.as_ref().map_or(0.0, |f| f.ascent(font_size)), - font_height: font.row_height(font_size), - font_ascent: font.ascent(font_size), + 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_height: font_metrics.row_height, + font_ascent: font_metrics.ascent, uv_rect: glyph_alloc.uv_rect, section_index, }); @@ -431,8 +455,12 @@ fn replace_last_glyph_with_overflow_character( let mut font = fonts.font(§ion.format.font_id.family); let font_size = section.format.font_id.size; - let (mut font_impl, replacement_glyph_alloc) = - font.font_impl_and_glyph_alloc(pixels_per_point, overflow_character, font_size); + let (font_id, glyph_info) = font.glyph_info(overflow_character); + let mut font_impl = font.fonts_by_id.get_mut(&font_id); + let replacement_glyph_alloc = font_impl + .as_mut() + .map(|f| f.allocate_glyph(font.atlas, pixels_per_point, glyph_info, font_size)) + .unwrap_or_default(); let overflow_glyph_x = if let Some(prev_glyph) = row.glyphs.last() { // Kern the overflow character properly @@ -468,20 +496,25 @@ fn replace_last_glyph_with_overflow_character( // 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_height = font_impl.as_mut().map_or(0.0, |f| f.row_height(font_size)); - let font_impl_ascent = font_impl.as_mut().map_or(0.0, |f| f.ascent(font_size)); - let font_height = font.row_height(font_size); - let line_height = section.format.line_height.unwrap_or(font_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 line_height = section + .format + .line_height + .unwrap_or(font_metrics.row_height); row.glyphs.push(Glyph { chr: overflow_character, pos: pos2(overflow_glyph_x, f32::NAN), advance_width: replacement_glyph_alloc.advance_width, line_height, - font_impl_height, - font_impl_ascent, - font_height, - font_ascent: font.ascent(font_size), + 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: replacement_glyph_alloc.uv_rect, section_index, }); @@ -1170,7 +1203,10 @@ mod tests { ); let font_id = FontId::default(); - let font_height = fonts.font(&font_id.family).row_height(font_id.size); + let font_height = fonts + .font(&font_id.family) + .scaled_metrics(font_id.size) + .row_height; let job = LayoutJob::simple(String::new(), font_id, Color32::WHITE, f32::INFINITY); @@ -1204,7 +1240,10 @@ mod tests { ); let font_id = FontId::default(); - let font_height = fonts.font(&font_id.family).row_height(font_id.size); + let font_height = fonts + .font(&font_id.family) + .scaled_metrics(font_id.size) + .row_height; let job = LayoutJob::simple("Hi!\n".to_owned(), font_id, Color32::WHITE, f32::INFINITY);