From 02d45d70bcea9636192a038dad806c4047e65abe Mon Sep 17 00:00:00 2001 From: valadaptive Date: Thu, 3 Jul 2025 23:10:11 -0400 Subject: [PATCH] Make Font+FontImpl resolution and size independent --- crates/egui/src/context.rs | 7 +- crates/egui_demo_lib/src/demo/font_book.rs | 6 +- crates/epaint/src/text/font.rs | 293 ++++++++++++--------- crates/epaint/src/text/fonts.rs | 137 +++------- crates/epaint/src/text/text_layout.rs | 158 +++++++---- 5 files changed, 308 insertions(+), 293 deletions(-) diff --git a/crates/egui/src/context.rs b/crates/egui/src/context.rs index 4b08f6eeb..ce2c7e167 100644 --- a/crates/egui/src/context.rs +++ b/crates/egui/src/context.rs @@ -641,7 +641,10 @@ impl ContextImpl { // Preload the most common characters for the most common fonts. // This is not very important to do, but may save a few GPU operations. for font_id in self.memory.options.style().text_styles.values() { - fonts.fonts.font(font_id).preload_common_characters(); + fonts + .fonts + .font(&font_id.family) + .preload_common_characters(); } } } @@ -1585,7 +1588,7 @@ impl Context { let font_id = TextStyle::Body.resolve(&self.style()); self.fonts_mut(|f| { - let font = f.fonts.font(&font_id); + let font = f.fonts.font(&font_id.family); font.has_glyphs(alt) && font.has_glyphs(ctrl) && font.has_glyphs(shift) diff --git a/crates/egui_demo_lib/src/demo/font_book.rs b/crates/egui_demo_lib/src/demo/font_book.rs index c2f606176..39acf844a 100644 --- a/crates/egui_demo_lib/src/demo/font_book.rs +++ b/crates/egui_demo_lib/src/demo/font_book.rs @@ -77,7 +77,7 @@ impl crate::View for FontBook { let available_glyphs = self .available_glyphs .entry(self.font_id.family.clone()) - .or_insert_with(|| available_characters(ui, self.font_id.family.clone())); + .or_insert_with(|| available_characters(ui, &self.font_id.family)); ui.separator(); @@ -140,10 +140,10 @@ fn char_info_ui(ui: &mut egui::Ui, chr: char, glyph_info: &GlyphInfo, font_id: e }); } -fn available_characters(ui: &egui::Ui, family: egui::FontFamily) -> BTreeMap { +fn available_characters(ui: &egui::Ui, family: &egui::FontFamily) -> BTreeMap { ui.fonts_mut(|f| { f.fonts - .font(&egui::FontId::new(10.0, family)) // size is arbitrary for getting the characters + .font(family) .characters() .iter() .filter(|(chr, _fonts)| !chr.is_whitespace() && !chr.is_ascii_control()) diff --git a/crates/epaint/src/text/font.rs b/crates/epaint/src/text/font.rs index a8dde7b09..30e7f4757 100644 --- a/crates/epaint/src/text/font.rs +++ b/crates/epaint/src/text/font.rs @@ -1,7 +1,8 @@ use std::collections::BTreeMap; use std::sync::Arc; -use emath::{GuiRounding as _, Vec2, vec2}; +use ab_glyph::{Font as _, PxScaleFont, ScaleFont as _}; +use emath::{GuiRounding as _, OrderedFloat, Vec2, vec2}; use crate::{ TextureAtlas, @@ -34,7 +35,7 @@ impl UvRect { } } -#[derive(Clone, Copy, Debug, PartialEq)] +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] pub struct GlyphInfo { /// Used for pair-kerning. /// @@ -42,24 +43,39 @@ pub struct GlyphInfo { /// Use `ab_glyph::GlyphId(0)` if you just want to have an id, and don't care. pub(crate) id: ab_glyph::GlyphId, + /// Unscaled. + pub advance_width_unscaled: OrderedFloat, + + /// Whether this glyph has any outlines. + pub visible: bool, +} + +impl Default for GlyphInfo { + /// Basically a zero-width space. + fn default() -> Self { + Self { + id: ab_glyph::GlyphId(0), + advance_width_unscaled: 0.0.into(), + visible: false, + } + } +} + +#[derive(Clone, Copy, Debug, PartialEq, Default)] +pub struct GlyphAllocation { + /// Used for pair-kerning. + /// + /// Doesn't need to be unique. + /// Use `ab_glyph::GlyphId(0)` if you just want to have an id, and don't care. + pub(crate) id: ab_glyph::GlyphId, + /// Unit: points. pub advance_width: f32, - /// Texture coordinates. + /// UV rectangle for drawing. pub uv_rect: UvRect, } -impl Default for GlyphInfo { - /// Basically a zero-width space. - fn default() -> Self { - Self { - id: ab_glyph::GlyphId(0), - advance_width: 0.0, - uv_rect: Default::default(), - } - } -} - // ---------------------------------------------------------------------------- /// A specific font with a size. @@ -67,72 +83,51 @@ impl Default for GlyphInfo { pub struct FontImpl { name: String, ab_glyph_font: ab_glyph::FontArc, - - /// Maximum character height - scale_in_pixels: u32, - - height_in_points: f32, - - // move each character by this much (hack) - y_offset_in_points: f32, - - ascent: f32, - pixels_per_point: f32, + tweak: FontTweak, glyph_info_cache: RwLock>, // TODO(emilk): standard Mutex + glyph_alloc_cache: RwLock), GlyphAllocation>>, // TODO(emilk): standard Mutex atlas: Arc>, } +trait FontExt { + fn pt_scaled(&self, scale: f32) -> PxScaleFont<&'_ Self>; + + fn pt_scale_factor(&self, scale: f32) -> f32; +} + +impl 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 { + 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 + } +} + impl FontImpl { pub fn new( atlas: Arc>, - pixels_per_point: f32, name: String, ab_glyph_font: ab_glyph::FontArc, - scale_in_pixels: f32, tweak: FontTweak, ) -> Self { - assert!( - scale_in_pixels > 0.0, - "scale_in_pixels is smaller than 0, got: {scale_in_pixels:?}" - ); - assert!( - pixels_per_point > 0.0, - "pixels_per_point must be greater than 0, got: {pixels_per_point:?}" - ); - - use ab_glyph::{Font as _, ScaleFont as _}; - let scaled = ab_glyph_font.as_scaled(scale_in_pixels); - let ascent = (scaled.ascent() / pixels_per_point).round_ui(); - let descent = (scaled.descent() / pixels_per_point).round_ui(); - let line_gap = (scaled.line_gap() / pixels_per_point).round_ui(); - - // Tweak the scale as the user desired - let scale_in_pixels = scale_in_pixels * tweak.scale; - let scale_in_points = scale_in_pixels / pixels_per_point; - - let y_offset_points = - ((scale_in_points * tweak.y_offset_factor) + tweak.y_offset).round_ui(); - - // Center scaled glyphs properly: - let height = ascent + descent; - let y_offset_points = y_offset_points - (1.0 - tweak.scale) * 0.5 * height; - - // Round to an even number of physical pixels to get even kerning. - // See https://github.com/emilk/egui/issues/382 - let scale_in_pixels = scale_in_pixels.round() as u32; - - // Round to closest pixel: - let y_offset_in_points = (y_offset_points * pixels_per_point).round() / pixels_per_point; - Self { name, ab_glyph_font, - scale_in_pixels, - height_in_points: ascent - descent + line_gap, - y_offset_in_points, - ascent, - pixels_per_point, + tweak, glyph_info_cache: Default::default(), + glyph_alloc_cache: Default::default(), atlas, } } @@ -159,7 +154,6 @@ impl FontImpl { /// An un-ordered iterator over all supported characters. fn characters(&self) -> impl Iterator + '_ { - use ab_glyph::Font as _; self.ab_glyph_font .codepoint_ids() .map(|(_, chr)| chr) @@ -181,7 +175,9 @@ impl FontImpl { if c == '\t' { if let Some(space) = self.glyph_info(' ') { let glyph_info = GlyphInfo { - advance_width: crate::text::TAB_SIZE as f32 * space.advance_width, + advance_width_unscaled: (crate::text::TAB_SIZE as f32 + * space.advance_width_unscaled.0) + .into(), ..space }; self.glyph_info_cache.write().insert(c, glyph_info); @@ -195,10 +191,10 @@ impl FontImpl { // https://en.wikipedia.org/wiki/Thin_space if let Some(space) = self.glyph_info(' ') { - let em = self.height_in_points; // TODO(emilk): is this right? - let advance_width = f32::min(em / 6.0, space.advance_width * 0.5); + let em = self.ab_glyph_font.units_per_em().unwrap_or(1.0); + let advance_width = f32::min(em / 6.0, space.advance_width_unscaled.0 * 0.5); let glyph_info = GlyphInfo { - advance_width, + advance_width_unscaled: advance_width.into(), ..space }; self.glyph_info_cache.write().insert(c, glyph_info); @@ -213,13 +209,16 @@ impl FontImpl { } // Add new character: - use ab_glyph::Font as _; let glyph_id = self.ab_glyph_font.glyph_id(c); if glyph_id.0 == 0 { None // unsupported character } else { - let glyph_info = self.allocate_glyph(glyph_id); + let glyph_info = GlyphInfo { + id: glyph_id, + advance_width_unscaled: self.ab_glyph_font.h_advance_unscaled(glyph_id).into(), + visible: true, + }; self.glyph_info_cache.write().insert(c, glyph_info); Some(glyph_info) } @@ -230,43 +229,80 @@ impl FontImpl { &self, last_glyph_id: ab_glyph::GlyphId, glyph_id: ab_glyph::GlyphId, + font_size: f32, + pixels_per_point: f32, ) -> f32 { - use ab_glyph::{Font as _, ScaleFont as _}; + // Round to an even number of physical pixels to get even kerning. + // See https://github.com/emilk/egui/issues/382 self.ab_glyph_font - .as_scaled(self.scale_in_pixels as f32) + .pt_scaled((font_size * self.tweak.scale * pixels_per_point).round()) .kern(last_glyph_id, glyph_id) - / self.pixels_per_point + / 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) -> f32 { - self.height_in_points - } + pub fn row_height(&self, font_size: f32) -> f32 { + let font = self.ab_glyph_font.pt_scaled(font_size); - #[inline(always)] - pub fn pixels_per_point(&self) -> f32 { - self.pixels_per_point + 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) -> f32 { - self.ascent + pub fn ascent(&self, font_size: f32) -> f32 { + self.ab_glyph_font.pt_scaled(font_size).ascent().round_ui() } - fn allocate_glyph(&self, glyph_id: ab_glyph::GlyphId) -> GlyphInfo { - assert!(glyph_id.0 != 0, "Can't allocate glyph for id 0"); - use ab_glyph::{Font as _, ScaleFont as _}; + pub fn allocate_glyph( + &self, + glyph_info: GlyphInfo, + font_size: f32, + pixels_per_point: f32, + ) -> GlyphAllocation { + if !glyph_info.visible { + 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 mut cache = self.glyph_alloc_cache.write(); + let entry = match cache.entry((glyph_info, scale.into())) { + std::collections::hash_map::Entry::Occupied(glyph_alloc) => { + return *glyph_alloc.get(); + } + std::collections::hash_map::Entry::Vacant(entry) => entry, + }; - let glyph = glyph_id.with_scale_and_position( - self.scale_in_pixels as f32, - ab_glyph::Point { x: 0.0, y: 0.0 }, - ); + assert!(glyph_info.id.0 != 0, "Can't allocate glyph for id 0"); + + let glyph = glyph_info + .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(); @@ -290,11 +326,10 @@ impl FontImpl { }; let offset_in_pixels = vec2(bb.min.x, bb.min.y); - let offset = - offset_in_pixels / self.pixels_per_point + self.y_offset_in_points * Vec2::Y; + let offset = offset_in_pixels / pixels_per_point + y_offset_in_points * Vec2::Y; UvRect { offset, - size: vec2(glyph_width as f32, glyph_height as f32) / self.pixels_per_point, + size: vec2(glyph_width as f32, glyph_height as f32) / pixels_per_point, min: [glyph_pos.0 as u16, glyph_pos.1 as u16], max: [ (glyph_pos.0 + glyph_width) as u16, @@ -305,17 +340,15 @@ impl FontImpl { }); let uv_rect = uv_rect.unwrap_or_default(); - let advance_width_in_points = self - .ab_glyph_font - .as_scaled(self.scale_in_pixels as f32) - .h_advance(glyph_id) - / self.pixels_per_point; - - GlyphInfo { - id: glyph_id, - advance_width: advance_width_in_points, + let allocation = GlyphAllocation { + id: glyph_info.id, + advance_width: (glyph_info.advance_width_unscaled.0 * scale + / self.ab_glyph_font.height_unscaled()) + / pixels_per_point, uv_rect, - } + }; + entry.insert(allocation); + allocation } } @@ -330,8 +363,6 @@ pub struct Font { characters: Option>>, replacement_glyph: (FontIndex, GlyphInfo), - pixels_per_point: f32, - row_height: f32, glyph_info_cache: ahash::HashMap, } @@ -342,21 +373,14 @@ impl Font { fonts, characters: None, replacement_glyph: Default::default(), - pixels_per_point: 1.0, - row_height: 0.0, glyph_info_cache: Default::default(), }; } - let pixels_per_point = fonts[0].pixels_per_point(); - let row_height = fonts[0].row_height(); - let mut slf = Self { fonts, characters: None, replacement_glyph: Default::default(), - pixels_per_point, - row_height, glyph_info_cache: Default::default(), }; @@ -408,29 +432,20 @@ impl Font { }) } - #[inline(always)] - pub fn round_to_pixel(&self, point: f32) -> f32 { - (point * self.pixels_per_point).round() / self.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) -> f32 { - self.row_height - } - - pub fn uv_rect(&self, c: char) -> UvRect { - self.glyph_info_cache - .get(&c) - .map(|gi| gi.1.uv_rect) - .unwrap_or_default() + pub fn row_height(&self, font_size: f32) -> f32 { + self.fonts[0].row_height(font_size) } /// Width of this character in points. - pub fn glyph_width(&mut self, c: char) -> f32 { - self.glyph_info(c).1.advance_width + pub fn glyph_width(&mut self, c: char, font_size: f32) -> f32 { + let (font_index, glyph_info) = self.glyph_info(c); + let font = &self.fonts[font_index].ab_glyph_font; + glyph_info.advance_width_unscaled.0 * font.pt_scale_factor(font_size) + / font.height_unscaled() } /// Can we display this glyph? @@ -465,11 +480,27 @@ impl Font { (Some(font_impl), glyph_info) } - pub(crate) fn ascent(&self) -> f32 { + #[inline] + pub(crate) fn font_impl_and_glyph_alloc( + &mut self, + c: char, + font_size: f32, + pixels_per_point: f32, + ) -> (Option<&FontImpl>, GlyphAllocation) { + if self.fonts.is_empty() { + return (None, Default::default()); + } + let (font_index, glyph_info) = self.glyph_info(c); + let font_impl = &self.fonts[font_index]; + let allocated_glyph = font_impl.allocate_glyph(glyph_info, font_size, pixels_per_point); + (Some(font_impl), allocated_glyph) + } + + pub(crate) fn ascent(&self, font_size: f32) -> f32 { if let Some(first) = self.fonts.first() { - first.ascent() + first.ascent(font_size) } else { - self.row_height + self.row_height(font_size) } } diff --git a/crates/epaint/src/text/fonts.rs b/crates/epaint/src/text/fonts.rs index 623baa9bf..e4eb30367 100644 --- a/crates/epaint/src/text/fonts.rs +++ b/crates/epaint/src/text/fonts.rs @@ -8,7 +8,7 @@ use crate::{ font::{Font, FontImpl}, }, }; -use emath::{NumExt as _, OrderedFloat}; +use emath::NumExt as _; #[cfg(feature = "default_fonts")] use epaint_default_fonts::{EMOJI_ICON, HACK_REGULAR, NOTO_EMOJI_REGULAR, UBUNTU_LIGHT}; @@ -619,8 +619,9 @@ pub struct FontsImpl { max_texture_side: usize, definitions: FontDefinitions, atlas: Arc>, - font_impl_cache: FontImplCache, - sized_family: ahash::HashMap<(OrderedFloat, FontFamily), Font>, + //font_impl_cache: FontImplCache, + font_impls: ahash::HashMap>, + family_cache: ahash::HashMap, } impl FontsImpl { @@ -643,16 +644,24 @@ impl FontsImpl { let atlas = Arc::new(Mutex::new(atlas)); - let font_impl_cache = - FontImplCache::new(atlas.clone(), pixels_per_point, &definitions.font_data); + let font_impls = definitions + .font_data + .iter() + .map(|(name, font_data)| { + let tweak = font_data.tweak; + let ab_glyph = ab_glyph_font_from_font_data(name, font_data); + let font_impl = FontImpl::new(atlas.clone(), name.clone(), ab_glyph, tweak); + (name.clone(), Arc::new(font_impl)) + }) + .collect(); Self { pixels_per_point, max_texture_side, definitions, atlas, - font_impl_cache, - sized_family: Default::default(), + font_impls, + family_cache: Default::default(), } } @@ -666,48 +675,47 @@ impl FontsImpl { &self.definitions } - /// Get the right font implementation from size and [`FontFamily`]. - pub fn font(&mut self, font_id: &FontId) -> &mut Font { - let FontId { size, family } = font_id; - let mut size = *size; - size = size.at_least(0.1).at_most(2048.0); + /// Get the right font implementation from [`FontFamily`]. + pub fn font(&mut self, family: &FontFamily) -> &mut Font { + self.family_cache.entry(family.clone()).or_insert_with(|| { + let fonts = &self.definitions.families.get(family); + let fonts = + fonts.unwrap_or_else(|| panic!("FontFamily::{family:?} is not bound to any fonts")); - self.sized_family - .entry((OrderedFloat(size), family.clone())) - .or_insert_with(|| { - let fonts = &self.definitions.families.get(family); - let fonts = fonts - .unwrap_or_else(|| panic!("FontFamily::{family:?} is not bound to any fonts")); + let fonts: Vec> = fonts + .iter() + .map(|font_name| { + self.font_impls + .get(font_name) + .unwrap_or_else(|| panic!("No font data found for {font_name:?}")) + .clone() + }) + .collect(); - let fonts: Vec> = fonts - .iter() - .map(|font_name| self.font_impl_cache.font_impl(size, font_name)) - .collect(); - - Font::new(fonts) - }) + Font::new(fonts) + }) } /// Width of this character in points. fn glyph_width(&mut self, font_id: &FontId, c: char) -> f32 { - self.font(font_id).glyph_width(c) + self.font(&font_id.family).glyph_width(c, font_id.size) } /// Can we display this glyph? pub fn has_glyph(&mut self, font_id: &FontId, c: char) -> bool { - self.font(font_id).has_glyph(c) + self.font(&font_id.family).has_glyph(c) } /// Can we display all the glyphs in this text? pub fn has_glyphs(&mut self, font_id: &FontId, s: &str) -> bool { - self.font(font_id).has_glyphs(s) + self.font(&font_id.family).has_glyphs(s) } /// Height of one row of text in points. /// /// Returns a value rounded to [`emath::GUI_ROUNDING`]. fn row_height(&mut self, font_id: &FontId) -> f32 { - self.font(font_id).row_height() + self.font(&font_id.family).row_height(font_id.size) } } @@ -963,77 +971,6 @@ fn should_cache_each_paragraph_individually(job: &LayoutJob) -> bool { job.break_on_newline && job.wrap.max_rows == usize::MAX && job.text.contains('\n') } -// ---------------------------------------------------------------------------- - -struct FontImplCache { - atlas: Arc>, - pixels_per_point: f32, - ab_glyph_fonts: BTreeMap, - - /// Map font pixel sizes and names to the cached [`FontImpl`]. - cache: ahash::HashMap<(u32, String), Arc>, -} - -impl FontImplCache { - pub fn new( - atlas: Arc>, - pixels_per_point: f32, - font_data: &BTreeMap>, - ) -> Self { - let ab_glyph_fonts = font_data - .iter() - .map(|(name, font_data)| { - let tweak = font_data.tweak; - let ab_glyph = ab_glyph_font_from_font_data(name, font_data); - (name.clone(), (tweak, ab_glyph)) - }) - .collect(); - - Self { - atlas, - pixels_per_point, - ab_glyph_fonts, - cache: Default::default(), - } - } - - pub fn font_impl(&mut self, scale_in_points: f32, font_name: &str) -> Arc { - use ab_glyph::Font as _; - - let (tweak, ab_glyph_font) = self - .ab_glyph_fonts - .get(font_name) - .unwrap_or_else(|| panic!("No font data found for {font_name:?}")) - .clone(); - - let scale_in_pixels = self.pixels_per_point * scale_in_points; - - // Scale the font properly (see https://github.com/emilk/egui/issues/2068). - let units_per_em = ab_glyph_font.units_per_em().unwrap_or_else(|| { - panic!("The font unit size of {font_name:?} exceeds the expected range (16..=16384)") - }); - let font_scaling = ab_glyph_font.height_unscaled() / units_per_em; - let scale_in_pixels = scale_in_pixels * font_scaling; - - self.cache - .entry(( - (scale_in_pixels * tweak.scale).round() as u32, - font_name.to_owned(), - )) - .or_insert_with(|| { - Arc::new(FontImpl::new( - self.atlas.clone(), - self.pixels_per_point, - font_name.to_owned(), - ab_glyph_font, - scale_in_pixels, - tweak, - )) - }) - .clone() - } -} - #[cfg(feature = "default_fonts")] #[cfg(test)] mod tests { diff --git a/crates/epaint/src/text/text_layout.rs b/crates/epaint/src/text/text_layout.rs index fd71506ef..cba38c450 100644 --- a/crates/epaint/src/text/text_layout.rs +++ b/crates/epaint/src/text/text_layout.rs @@ -143,11 +143,13 @@ fn layout_section( byte_range, format, } = section; - let font = fonts.font(&format.font_id); + let pixels_per_point = fonts.pixels_per_point(); + let font = fonts.font(&format.font_id.family); + let font_size = format.font_id.size; let line_height = section .format .line_height - .unwrap_or_else(|| font.row_height()); + .unwrap_or_else(|| font.row_height(font_size)); let extra_letter_spacing = section.format.extra_letter_spacing; let mut paragraph = out_paragraphs.last_mut().unwrap(); @@ -165,30 +167,35 @@ fn layout_section( 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_info) = font.font_impl_and_glyph_info(chr); - if let Some(font_impl) = font_impl { - if let Some(last_glyph_id) = last_glyph_id { - paragraph.cursor_x += font_impl.pair_kerning(last_glyph_id, glyph_info.id); - paragraph.cursor_x += extra_letter_spacing; - } + let (font_impl, glyph_alloc) = + font.font_impl_and_glyph_alloc(chr, font_size, pixels_per_point); + + if let (Some(font_impl), Some(last_glyph_id)) = (font_impl, last_glyph_id) { + paragraph.cursor_x += font_impl.pair_kerning( + last_glyph_id, + glyph_alloc.id, + font_size, + pixels_per_point, + ); + paragraph.cursor_x += extra_letter_spacing; } paragraph.glyphs.push(Glyph { chr, pos: pos2(paragraph.cursor_x, f32::NAN), - advance_width: glyph_info.advance_width, + advance_width: glyph_alloc.advance_width, line_height, - font_impl_height: font_impl.map_or(0.0, |f| f.row_height()), - font_impl_ascent: font_impl.map_or(0.0, |f| f.ascent()), - font_height: font.row_height(), - font_ascent: font.ascent(), - uv_rect: glyph_info.uv_rect, + font_impl_height: font_impl.map_or(0.0, |f| f.row_height(font_size)), + font_impl_ascent: font_impl.map_or(0.0, |f| f.ascent(font_size)), + font_height: font.row_height(font_size), + font_ascent: font.ascent(font_size), + uv_rect: glyph_alloc.uv_rect, section_index, }); - paragraph.cursor_x += glyph_info.advance_width; - paragraph.cursor_x = font.round_to_pixel(paragraph.cursor_x); - last_glyph_id = Some(glyph_info.id); + paragraph.cursor_x += glyph_alloc.advance_width; + paragraph.cursor_x = paragraph.cursor_x.round_to_pixels(pixels_per_point); + last_glyph_id = Some(glyph_alloc.id); } } } @@ -409,70 +416,83 @@ fn replace_last_glyph_with_overflow_character( } } - fn row_height(section: &LayoutSection, font: &Font) -> f32 { + fn row_height(section: &LayoutSection, font: &Font, font_size: f32) -> f32 { section .format .line_height - .unwrap_or_else(|| font.row_height()) + .unwrap_or_else(|| font.row_height(font_size)) } let Some(overflow_character) = job.wrap.overflow_character else { return; }; + let pixels_per_point = fonts.pixels_per_point(); + // We always try to just append the character first: if let Some(last_glyph) = row.glyphs.last() { let section_index = last_glyph.section_index; let section = &job.sections[section_index as usize]; - let font = fonts.font(§ion.format.font_id); - let line_height = row_height(section, font); + let font = fonts.font(§ion.format.font_id.family); + let font_size = section.format.font_id.size; + let line_height = row_height(section, font, font_size); let (_, last_glyph_info) = font.font_impl_and_glyph_info(last_glyph.chr); let mut x = last_glyph.pos.x + last_glyph.advance_width; - let (font_impl, replacement_glyph_info) = font.font_impl_and_glyph_info(overflow_character); + let (font_impl, replacement_glyph_alloc) = + font.font_impl_and_glyph_alloc(overflow_character, font_size, pixels_per_point); - { - // Kerning: - x += section.format.extra_letter_spacing; - if let Some(font_impl) = font_impl { - x += font_impl.pair_kerning(last_glyph_info.id, replacement_glyph_info.id); - } + // Kerning: + x += section.format.extra_letter_spacing; + if let Some(font_impl) = font_impl { + x += font_impl.pair_kerning( + last_glyph_info.id, + replacement_glyph_alloc.id, + section.format.font_id.size, + pixels_per_point, + ); } row.glyphs.push(Glyph { chr: overflow_character, pos: pos2(x, f32::NAN), - advance_width: replacement_glyph_info.advance_width, + advance_width: replacement_glyph_alloc.advance_width, line_height, - font_impl_height: font_impl.map_or(0.0, |f| f.row_height()), - font_impl_ascent: font_impl.map_or(0.0, |f| f.ascent()), - font_height: font.row_height(), - font_ascent: font.ascent(), - uv_rect: replacement_glyph_info.uv_rect, + font_impl_height: font_impl.map_or(0.0, |f| f.row_height(font_size)), + font_impl_ascent: font_impl.map_or(0.0, |f| f.ascent(font_size)), + font_height: font.row_height(font_size), + font_ascent: font.ascent(font_size), + uv_rect: replacement_glyph_alloc.uv_rect, section_index, }); } else { let section_index = row.section_index_at_start; let section = &job.sections[section_index as usize]; - let font = fonts.font(§ion.format.font_id); - let line_height = row_height(section, font); + let font = fonts.font(§ion.format.font_id.family); + let font_size = section.format.font_id.size; + let line_height = row_height(section, font, font_size); let x = 0.0; // TODO(emilk): heed paragraph leading_space 😬 let (font_impl, replacement_glyph_info) = font.font_impl_and_glyph_info(overflow_character); + let glyph_alloc = if let Some(font_impl) = font_impl { + font_impl.allocate_glyph(replacement_glyph_info, font_size, pixels_per_point) + } else { + Default::default() + }; row.glyphs.push(Glyph { chr: overflow_character, pos: pos2(x, f32::NAN), - advance_width: replacement_glyph_info.advance_width, + advance_width: glyph_alloc.advance_width, line_height, - font_impl_height: font_impl.map_or(0.0, |f| f.row_height()), - font_impl_ascent: font_impl.map_or(0.0, |f| f.ascent()), - font_height: font.row_height(), - font_ascent: font.ascent(), - uv_rect: replacement_glyph_info.uv_rect, + font_impl_height: font_impl.map_or(0.0, |f| f.row_height(font_size)), + font_impl_ascent: font_impl.map_or(0.0, |f| f.ascent(font_size)), + font_height: font.row_height(font_size), + font_ascent: font.ascent(font_size), + uv_rect: glyph_alloc.uv_rect, section_index, }); } @@ -498,30 +518,47 @@ fn replace_last_glyph_with_overflow_character( let section = &job.sections[last_glyph.section_index as usize]; let extra_letter_spacing = section.format.extra_letter_spacing; - let font = fonts.font(§ion.format.font_id); + let pixels_per_point = fonts.pixels_per_point(); + let font = fonts.font(§ion.format.font_id.family); + let font_size = section.format.font_id.size; if let Some(prev_glyph) = prev_glyph { - let prev_glyph_id = font.font_impl_and_glyph_info(prev_glyph.chr).1.id; + let prev_glyph_id = font + .font_impl_and_glyph_alloc(prev_glyph.chr, font_size, pixels_per_point) + .1 + .id; // Undo kerning with previous glyph: - let (font_impl, glyph_info) = font.font_impl_and_glyph_info(last_glyph.chr); + let (font_impl, glyph_alloc) = + font.font_impl_and_glyph_alloc(last_glyph.chr, font_size, pixels_per_point); last_glyph.pos.x -= extra_letter_spacing; if let Some(font_impl) = font_impl { - last_glyph.pos.x -= font_impl.pair_kerning(prev_glyph_id, glyph_info.id); + last_glyph.pos.x -= font_impl.pair_kerning( + prev_glyph_id, + glyph_alloc.id, + font_size, + pixels_per_point, + ); } // Replace the glyph: last_glyph.chr = overflow_character; - let (font_impl, glyph_info) = font.font_impl_and_glyph_info(last_glyph.chr); - last_glyph.advance_width = glyph_info.advance_width; - last_glyph.font_impl_ascent = font_impl.map_or(0.0, |f| f.ascent()); - last_glyph.font_impl_height = font_impl.map_or(0.0, |f| f.row_height()); - last_glyph.uv_rect = glyph_info.uv_rect; + let (font_impl, glyph_alloc) = + font.font_impl_and_glyph_alloc(last_glyph.chr, font_size, pixels_per_point); + last_glyph.advance_width = glyph_alloc.advance_width; + last_glyph.font_impl_ascent = font_impl.map_or(0.0, |f| f.ascent(font_size)); + last_glyph.font_impl_height = font_impl.map_or(0.0, |f| f.row_height(font_size)); + last_glyph.uv_rect = glyph_alloc.uv_rect; // Reapply kerning: last_glyph.pos.x += extra_letter_spacing; if let Some(font_impl) = font_impl { - last_glyph.pos.x += font_impl.pair_kerning(prev_glyph_id, glyph_info.id); + last_glyph.pos.x += font_impl.pair_kerning( + prev_glyph_id, + glyph_alloc.id, + font_size, + pixels_per_point, + ); } // Check if we're within width budget: @@ -532,13 +569,20 @@ fn replace_last_glyph_with_overflow_character( // We didn't fit - pop the last glyph and try again. row.glyphs.pop(); } else { + let Some(section) = &job.sections.get(last_glyph.section_index as usize) else { + return; + }; + let pixels_per_point = fonts.pixels_per_point(); + let font = fonts.font(§ion.format.font_id.family); + let font_size = section.format.font_id.size; // Just replace and be done with it. last_glyph.chr = overflow_character; - let (font_impl, glyph_info) = font.font_impl_and_glyph_info(last_glyph.chr); - last_glyph.advance_width = glyph_info.advance_width; - last_glyph.font_impl_ascent = font_impl.map_or(0.0, |f| f.ascent()); - last_glyph.font_impl_height = font_impl.map_or(0.0, |f| f.row_height()); - last_glyph.uv_rect = glyph_info.uv_rect; + let (font_impl, glyph_alloc) = + font.font_impl_and_glyph_alloc(last_glyph.chr, font_size, pixels_per_point); + last_glyph.advance_width = glyph_alloc.advance_width; + last_glyph.font_impl_ascent = font_impl.map_or(0.0, |f| f.ascent(font_size)); + last_glyph.font_impl_height = font_impl.map_or(0.0, |f| f.row_height(font_size)); + last_glyph.uv_rect = glyph_alloc.uv_rect; return; } }