epaint/text/
fonts.rs

1use std::{collections::BTreeMap, sync::Arc};
2
3use crate::{
4    mutex::{Mutex, MutexGuard},
5    text::{
6        font::{Font, FontImpl},
7        Galley, LayoutJob,
8    },
9    TextureAtlas,
10};
11use emath::{NumExt as _, OrderedFloat};
12
13#[cfg(feature = "default_fonts")]
14use epaint_default_fonts::{EMOJI_ICON, HACK_REGULAR, NOTO_EMOJI_REGULAR, UBUNTU_LIGHT};
15
16// ----------------------------------------------------------------------------
17
18/// How to select a sized font.
19#[derive(Clone, Debug, PartialEq)]
20#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
21pub struct FontId {
22    /// Height in points.
23    pub size: f32,
24
25    /// What font family to use.
26    pub family: FontFamily,
27    // TODO(emilk): weight (bold), italics, …
28}
29
30impl Default for FontId {
31    #[inline]
32    fn default() -> Self {
33        Self {
34            size: 14.0,
35            family: FontFamily::Proportional,
36        }
37    }
38}
39
40impl FontId {
41    #[inline]
42    pub const fn new(size: f32, family: FontFamily) -> Self {
43        Self { size, family }
44    }
45
46    #[inline]
47    pub const fn proportional(size: f32) -> Self {
48        Self::new(size, FontFamily::Proportional)
49    }
50
51    #[inline]
52    pub const fn monospace(size: f32) -> Self {
53        Self::new(size, FontFamily::Monospace)
54    }
55}
56
57#[allow(clippy::derived_hash_with_manual_eq)]
58impl std::hash::Hash for FontId {
59    #[inline(always)]
60    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
61        let Self { size, family } = self;
62        emath::OrderedFloat(*size).hash(state);
63        family.hash(state);
64    }
65}
66
67// ----------------------------------------------------------------------------
68
69/// Font of unknown size.
70///
71/// Which style of font: [`Monospace`][`FontFamily::Monospace`], [`Proportional`][`FontFamily::Proportional`],
72/// or by user-chosen name.
73#[derive(Clone, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
74#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
75pub enum FontFamily {
76    /// A font where some characters are wider than other (e.g. 'w' is wider than 'i').
77    ///
78    /// Proportional fonts are easier to read and should be the preferred choice in most situations.
79    #[default]
80    Proportional,
81
82    /// A font where each character is the same width (`w` is the same width as `i`).
83    ///
84    /// Useful for code snippets, or when you need to align numbers or text.
85    Monospace,
86
87    /// One of the names in [`FontDefinitions::families`].
88    ///
89    /// ```
90    /// # use epaint::FontFamily;
91    /// // User-chosen names:
92    /// FontFamily::Name("arial".into());
93    /// FontFamily::Name("serif".into());
94    /// ```
95    Name(Arc<str>),
96}
97
98impl std::fmt::Display for FontFamily {
99    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
100        match self {
101            Self::Monospace => "Monospace".fmt(f),
102            Self::Proportional => "Proportional".fmt(f),
103            Self::Name(name) => (*name).fmt(f),
104        }
105    }
106}
107
108// ----------------------------------------------------------------------------
109
110/// A `.ttf` or `.otf` file and a font face index.
111#[derive(Clone, Debug, PartialEq)]
112#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
113pub struct FontData {
114    /// The content of a `.ttf` or `.otf` file.
115    pub font: std::borrow::Cow<'static, [u8]>,
116
117    /// Which font face in the file to use.
118    /// When in doubt, use `0`.
119    pub index: u32,
120
121    /// Extra scale and vertical tweak to apply to all text of this font.
122    pub tweak: FontTweak,
123}
124
125impl FontData {
126    pub fn from_static(font: &'static [u8]) -> Self {
127        Self {
128            font: std::borrow::Cow::Borrowed(font),
129            index: 0,
130            tweak: Default::default(),
131        }
132    }
133
134    pub fn from_owned(font: Vec<u8>) -> Self {
135        Self {
136            font: std::borrow::Cow::Owned(font),
137            index: 0,
138            tweak: Default::default(),
139        }
140    }
141
142    pub fn tweak(self, tweak: FontTweak) -> Self {
143        Self { tweak, ..self }
144    }
145}
146
147// ----------------------------------------------------------------------------
148
149/// Extra scale and vertical tweak to apply to all text of a certain font.
150#[derive(Copy, Clone, Debug, PartialEq)]
151#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
152pub struct FontTweak {
153    /// Scale the font's glyphs by this much.
154    /// this is only a visual effect and does not affect the text layout.
155    ///
156    /// Default: `1.0` (no scaling).
157    pub scale: f32,
158
159    /// Shift font's glyphs downwards by this fraction of the font size (in points).
160    /// this is only a visual effect and does not affect the text layout.
161    ///
162    /// Affects larger font sizes more.
163    ///
164    /// A positive value shifts the text downwards.
165    /// A negative value shifts it upwards.
166    ///
167    /// Example value: `-0.2`.
168    pub y_offset_factor: f32,
169
170    /// Shift font's glyphs downwards by this amount of logical points.
171    /// this is only a visual effect and does not affect the text layout.
172    ///
173    /// Affects all font sizes equally.
174    ///
175    /// Example value: `2.0`.
176    pub y_offset: f32,
177
178    /// When using this font's metrics to layout a row,
179    /// shift the entire row downwards by this fraction of the font size (in points).
180    ///
181    /// A positive value shifts the text downwards.
182    /// A negative value shifts it upwards.
183    pub baseline_offset_factor: f32,
184}
185
186impl Default for FontTweak {
187    fn default() -> Self {
188        Self {
189            scale: 1.0,
190            y_offset_factor: 0.0,
191            y_offset: 0.0,
192            baseline_offset_factor: 0.0,
193        }
194    }
195}
196
197// ----------------------------------------------------------------------------
198
199fn ab_glyph_font_from_font_data(name: &str, data: &FontData) -> ab_glyph::FontArc {
200    match &data.font {
201        std::borrow::Cow::Borrowed(bytes) => {
202            ab_glyph::FontRef::try_from_slice_and_index(bytes, data.index)
203                .map(ab_glyph::FontArc::from)
204        }
205        std::borrow::Cow::Owned(bytes) => {
206            ab_glyph::FontVec::try_from_vec_and_index(bytes.clone(), data.index)
207                .map(ab_glyph::FontArc::from)
208        }
209    }
210    .unwrap_or_else(|err| panic!("Error parsing {name:?} TTF/OTF font file: {err}"))
211}
212
213/// Describes the font data and the sizes to use.
214///
215/// Often you would start with [`FontDefinitions::default()`] and then add/change the contents.
216///
217/// This is how you install your own custom fonts:
218/// ```
219/// # use {epaint::text::{FontDefinitions, FontFamily, FontData}};
220/// # struct FakeEguiCtx {};
221/// # impl FakeEguiCtx { fn set_fonts(&self, _: FontDefinitions) {} }
222/// # let egui_ctx = FakeEguiCtx {};
223/// let mut fonts = FontDefinitions::default();
224///
225/// // Install my own font (maybe supporting non-latin characters):
226/// fonts.font_data.insert("my_font".to_owned(),
227///    std::sync::Arc::new(
228///        // .ttf and .otf supported
229///        FontData::from_static(include_bytes!("../../../epaint_default_fonts/fonts/Ubuntu-Light.ttf"))
230///    )
231/// );
232///
233/// // Put my font first (highest priority):
234/// fonts.families.get_mut(&FontFamily::Proportional).unwrap()
235///     .insert(0, "my_font".to_owned());
236///
237/// // Put my font as last fallback for monospace:
238/// fonts.families.get_mut(&FontFamily::Monospace).unwrap()
239///     .push("my_font".to_owned());
240///
241/// egui_ctx.set_fonts(fonts);
242/// ```
243#[derive(Clone, Debug, PartialEq)]
244#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
245#[cfg_attr(feature = "serde", serde(default))]
246pub struct FontDefinitions {
247    /// List of font names and their definitions.
248    ///
249    /// `epaint` has built-in-default for these, but you can override them if you like.
250    pub font_data: BTreeMap<String, Arc<FontData>>,
251
252    /// Which fonts (names) to use for each [`FontFamily`].
253    ///
254    /// The list should be a list of keys into [`Self::font_data`].
255    /// When looking for a character glyph `epaint` will start with
256    /// the first font and then move to the second, and so on.
257    /// So the first font is the primary, and then comes a list of fallbacks in order of priority.
258    pub families: BTreeMap<FontFamily, Vec<String>>,
259}
260
261#[derive(Debug, Clone)]
262pub struct FontInsert {
263    /// Font name
264    pub name: String,
265
266    /// A `.ttf` or `.otf` file and a font face index.
267    pub data: FontData,
268
269    /// Sets the font family and priority
270    pub families: Vec<InsertFontFamily>,
271}
272
273#[derive(Debug, Clone)]
274pub struct InsertFontFamily {
275    /// Font family
276    pub family: FontFamily,
277
278    /// Fallback or Primary font
279    pub priority: FontPriority,
280}
281
282#[derive(Debug, Clone)]
283pub enum FontPriority {
284    /// Prefer this font before all existing ones.
285    ///
286    /// If a desired glyph exists in this font, it will be used.
287    Highest,
288
289    /// Use this font as a fallback, after all existing ones.
290    ///
291    /// This font will only be used if the glyph is not found in any of the previously installed fonts.
292    Lowest,
293}
294
295impl FontInsert {
296    pub fn new(name: &str, data: FontData, families: Vec<InsertFontFamily>) -> Self {
297        Self {
298            name: name.to_owned(),
299            data,
300            families,
301        }
302    }
303}
304
305impl Default for FontDefinitions {
306    /// Specifies the default fonts if the feature `default_fonts` is enabled,
307    /// otherwise this is the same as [`Self::empty`].
308    #[cfg(not(feature = "default_fonts"))]
309    fn default() -> Self {
310        Self::empty()
311    }
312
313    /// Specifies the default fonts if the feature `default_fonts` is enabled,
314    /// otherwise this is the same as [`Self::empty`].
315    #[cfg(feature = "default_fonts")]
316    fn default() -> Self {
317        let mut font_data: BTreeMap<String, Arc<FontData>> = BTreeMap::new();
318
319        let mut families = BTreeMap::new();
320
321        font_data.insert(
322            "Hack".to_owned(),
323            Arc::new(FontData::from_static(HACK_REGULAR)),
324        );
325
326        // Some good looking emojis. Use as first priority:
327        font_data.insert(
328            "NotoEmoji-Regular".to_owned(),
329            Arc::new(FontData::from_static(NOTO_EMOJI_REGULAR).tweak(FontTweak {
330                scale: 0.81, // Make smaller
331                ..Default::default()
332            })),
333        );
334
335        font_data.insert(
336            "Ubuntu-Light".to_owned(),
337            Arc::new(FontData::from_static(UBUNTU_LIGHT)),
338        );
339
340        // Bigger emojis, and more. <http://jslegers.github.io/emoji-icon-font/>:
341        font_data.insert(
342            "emoji-icon-font".to_owned(),
343            Arc::new(FontData::from_static(EMOJI_ICON).tweak(FontTweak {
344                scale: 0.90, // Make smaller
345                ..Default::default()
346            })),
347        );
348
349        families.insert(
350            FontFamily::Monospace,
351            vec![
352                "Hack".to_owned(),
353                "Ubuntu-Light".to_owned(), // fallback for √ etc
354                "NotoEmoji-Regular".to_owned(),
355                "emoji-icon-font".to_owned(),
356            ],
357        );
358        families.insert(
359            FontFamily::Proportional,
360            vec![
361                "Ubuntu-Light".to_owned(),
362                "NotoEmoji-Regular".to_owned(),
363                "emoji-icon-font".to_owned(),
364            ],
365        );
366
367        Self {
368            font_data,
369            families,
370        }
371    }
372}
373
374impl FontDefinitions {
375    /// No fonts.
376    pub fn empty() -> Self {
377        let mut families = BTreeMap::new();
378        families.insert(FontFamily::Monospace, vec![]);
379        families.insert(FontFamily::Proportional, vec![]);
380
381        Self {
382            font_data: Default::default(),
383            families,
384        }
385    }
386
387    /// List of all the builtin font names used by `epaint`.
388    #[cfg(feature = "default_fonts")]
389    pub fn builtin_font_names() -> &'static [&'static str] {
390        &[
391            "Ubuntu-Light",
392            "NotoEmoji-Regular",
393            "emoji-icon-font",
394            "Hack",
395        ]
396    }
397
398    /// List of all the builtin font names used by `epaint`.
399    #[cfg(not(feature = "default_fonts"))]
400    pub fn builtin_font_names() -> &'static [&'static str] {
401        &[]
402    }
403}
404
405// ----------------------------------------------------------------------------
406
407/// The collection of fonts used by `epaint`.
408///
409/// Required in order to paint text. Create one and reuse. Cheap to clone.
410///
411/// Each [`Fonts`] comes with a font atlas textures that needs to be used when painting.
412///
413/// If you are using `egui`, use `egui::Context::set_fonts` and `egui::Context::fonts`.
414///
415/// You need to call [`Self::begin_pass`] and [`Self::font_image_delta`] once every frame.
416#[derive(Clone)]
417pub struct Fonts(Arc<Mutex<FontsAndCache>>);
418
419impl Fonts {
420    /// Create a new [`Fonts`] for text layout.
421    /// This call is expensive, so only create one [`Fonts`] and then reuse it.
422    ///
423    /// * `pixels_per_point`: how many physical pixels per logical "point".
424    /// * `max_texture_side`: largest supported texture size (one side).
425    pub fn new(
426        pixels_per_point: f32,
427        max_texture_side: usize,
428        definitions: FontDefinitions,
429    ) -> Self {
430        let fonts_and_cache = FontsAndCache {
431            fonts: FontsImpl::new(pixels_per_point, max_texture_side, definitions),
432            galley_cache: Default::default(),
433        };
434        Self(Arc::new(Mutex::new(fonts_and_cache)))
435    }
436
437    /// Call at the start of each frame with the latest known
438    /// `pixels_per_point` and `max_texture_side`.
439    ///
440    /// Call after painting the previous frame, but before using [`Fonts`] for the new frame.
441    ///
442    /// This function will react to changes in `pixels_per_point` and `max_texture_side`,
443    /// as well as notice when the font atlas is getting full, and handle that.
444    pub fn begin_pass(&self, pixels_per_point: f32, max_texture_side: usize) {
445        let mut fonts_and_cache = self.0.lock();
446
447        let pixels_per_point_changed = fonts_and_cache.fonts.pixels_per_point != pixels_per_point;
448        let max_texture_side_changed = fonts_and_cache.fonts.max_texture_side != max_texture_side;
449        let font_atlas_almost_full = fonts_and_cache.fonts.atlas.lock().fill_ratio() > 0.8;
450        let needs_recreate =
451            pixels_per_point_changed || max_texture_side_changed || font_atlas_almost_full;
452
453        if needs_recreate {
454            let definitions = fonts_and_cache.fonts.definitions.clone();
455
456            *fonts_and_cache = FontsAndCache {
457                fonts: FontsImpl::new(pixels_per_point, max_texture_side, definitions),
458                galley_cache: Default::default(),
459            };
460        }
461
462        fonts_and_cache.galley_cache.flush_cache();
463    }
464
465    /// Call at the end of each frame (before painting) to get the change to the font texture since last call.
466    pub fn font_image_delta(&self) -> Option<crate::ImageDelta> {
467        self.lock().fonts.atlas.lock().take_delta()
468    }
469
470    /// Access the underlying [`FontsAndCache`].
471    #[doc(hidden)]
472    #[inline]
473    pub fn lock(&self) -> MutexGuard<'_, FontsAndCache> {
474        self.0.lock()
475    }
476
477    #[inline]
478    pub fn pixels_per_point(&self) -> f32 {
479        self.lock().fonts.pixels_per_point
480    }
481
482    #[inline]
483    pub fn max_texture_side(&self) -> usize {
484        self.lock().fonts.max_texture_side
485    }
486
487    /// The font atlas.
488    /// Pass this to [`crate::Tessellator`].
489    pub fn texture_atlas(&self) -> Arc<Mutex<TextureAtlas>> {
490        self.lock().fonts.atlas.clone()
491    }
492
493    /// The full font atlas image.
494    #[inline]
495    pub fn image(&self) -> crate::FontImage {
496        self.lock().fonts.atlas.lock().image().clone()
497    }
498
499    /// Current size of the font image.
500    /// Pass this to [`crate::Tessellator`].
501    pub fn font_image_size(&self) -> [usize; 2] {
502        self.lock().fonts.atlas.lock().size()
503    }
504
505    /// Width of this character in points.
506    #[inline]
507    pub fn glyph_width(&self, font_id: &FontId, c: char) -> f32 {
508        self.lock().fonts.glyph_width(font_id, c)
509    }
510
511    /// Can we display this glyph?
512    #[inline]
513    pub fn has_glyph(&self, font_id: &FontId, c: char) -> bool {
514        self.lock().fonts.has_glyph(font_id, c)
515    }
516
517    /// Can we display all the glyphs in this text?
518    pub fn has_glyphs(&self, font_id: &FontId, s: &str) -> bool {
519        self.lock().fonts.has_glyphs(font_id, s)
520    }
521
522    /// Height of one row of text in points.
523    ///
524    /// Returns a value rounded to [`emath::GUI_ROUNDING`].
525    #[inline]
526    pub fn row_height(&self, font_id: &FontId) -> f32 {
527        self.lock().fonts.row_height(font_id)
528    }
529
530    /// List of all known font families.
531    pub fn families(&self) -> Vec<FontFamily> {
532        self.lock()
533            .fonts
534            .definitions
535            .families
536            .keys()
537            .cloned()
538            .collect()
539    }
540
541    /// Layout some text.
542    ///
543    /// This is the most advanced layout function.
544    /// See also [`Self::layout`], [`Self::layout_no_wrap`] and
545    /// [`Self::layout_delayed_color`].
546    ///
547    /// The implementation uses memoization so repeated calls are cheap.
548    #[inline]
549    pub fn layout_job(&self, job: LayoutJob) -> Arc<Galley> {
550        self.lock().layout_job(job)
551    }
552
553    pub fn num_galleys_in_cache(&self) -> usize {
554        self.lock().galley_cache.num_galleys_in_cache()
555    }
556
557    /// How full is the font atlas?
558    ///
559    /// This increases as new fonts and/or glyphs are used,
560    /// but can also decrease in a call to [`Self::begin_pass`].
561    pub fn font_atlas_fill_ratio(&self) -> f32 {
562        self.lock().fonts.atlas.lock().fill_ratio()
563    }
564
565    /// Will wrap text at the given width and line break at `\n`.
566    ///
567    /// The implementation uses memoization so repeated calls are cheap.
568    pub fn layout(
569        &self,
570        text: String,
571        font_id: FontId,
572        color: crate::Color32,
573        wrap_width: f32,
574    ) -> Arc<Galley> {
575        let job = LayoutJob::simple(text, font_id, color, wrap_width);
576        self.layout_job(job)
577    }
578
579    /// Will line break at `\n`.
580    ///
581    /// The implementation uses memoization so repeated calls are cheap.
582    pub fn layout_no_wrap(
583        &self,
584        text: String,
585        font_id: FontId,
586        color: crate::Color32,
587    ) -> Arc<Galley> {
588        let job = LayoutJob::simple(text, font_id, color, f32::INFINITY);
589        self.layout_job(job)
590    }
591
592    /// Like [`Self::layout`], made for when you want to pick a color for the text later.
593    ///
594    /// The implementation uses memoization so repeated calls are cheap.
595    pub fn layout_delayed_color(
596        &self,
597        text: String,
598        font_id: FontId,
599        wrap_width: f32,
600    ) -> Arc<Galley> {
601        self.layout(text, font_id, crate::Color32::PLACEHOLDER, wrap_width)
602    }
603}
604
605// ----------------------------------------------------------------------------
606
607pub struct FontsAndCache {
608    pub fonts: FontsImpl,
609    galley_cache: GalleyCache,
610}
611
612impl FontsAndCache {
613    fn layout_job(&mut self, job: LayoutJob) -> Arc<Galley> {
614        self.galley_cache.layout(&mut self.fonts, job)
615    }
616}
617
618// ----------------------------------------------------------------------------
619
620/// The collection of fonts used by `epaint`.
621///
622/// Required in order to paint text.
623pub struct FontsImpl {
624    pixels_per_point: f32,
625    max_texture_side: usize,
626    definitions: FontDefinitions,
627    atlas: Arc<Mutex<TextureAtlas>>,
628    font_impl_cache: FontImplCache,
629    sized_family: ahash::HashMap<(OrderedFloat<f32>, FontFamily), Font>,
630}
631
632impl FontsImpl {
633    /// Create a new [`FontsImpl`] for text layout.
634    /// This call is expensive, so only create one [`FontsImpl`] and then reuse it.
635    pub fn new(
636        pixels_per_point: f32,
637        max_texture_side: usize,
638        definitions: FontDefinitions,
639    ) -> Self {
640        assert!(
641            0.0 < pixels_per_point && pixels_per_point < 100.0,
642            "pixels_per_point out of range: {pixels_per_point}"
643        );
644
645        let texture_width = max_texture_side.at_most(16 * 1024);
646        let initial_height = 32; // Keep initial font atlas small, so it is fast to upload to GPU. This will expand as needed anyways.
647        let atlas = TextureAtlas::new([texture_width, initial_height]);
648
649        let atlas = Arc::new(Mutex::new(atlas));
650
651        let font_impl_cache =
652            FontImplCache::new(atlas.clone(), pixels_per_point, &definitions.font_data);
653
654        Self {
655            pixels_per_point,
656            max_texture_side,
657            definitions,
658            atlas,
659            font_impl_cache,
660            sized_family: Default::default(),
661        }
662    }
663
664    #[inline(always)]
665    pub fn pixels_per_point(&self) -> f32 {
666        self.pixels_per_point
667    }
668
669    #[inline]
670    pub fn definitions(&self) -> &FontDefinitions {
671        &self.definitions
672    }
673
674    /// Get the right font implementation from size and [`FontFamily`].
675    pub fn font(&mut self, font_id: &FontId) -> &mut Font {
676        let FontId { mut size, family } = font_id;
677        size = size.at_least(0.1).at_most(2048.0);
678
679        self.sized_family
680            .entry((OrderedFloat(size), family.clone()))
681            .or_insert_with(|| {
682                let fonts = &self.definitions.families.get(family);
683                let fonts = fonts
684                    .unwrap_or_else(|| panic!("FontFamily::{family:?} is not bound to any fonts"));
685
686                let fonts: Vec<Arc<FontImpl>> = fonts
687                    .iter()
688                    .map(|font_name| self.font_impl_cache.font_impl(size, font_name))
689                    .collect();
690
691                Font::new(fonts)
692            })
693    }
694
695    /// Width of this character in points.
696    fn glyph_width(&mut self, font_id: &FontId, c: char) -> f32 {
697        self.font(font_id).glyph_width(c)
698    }
699
700    /// Can we display this glyph?
701    pub fn has_glyph(&mut self, font_id: &FontId, c: char) -> bool {
702        self.font(font_id).has_glyph(c)
703    }
704
705    /// Can we display all the glyphs in this text?
706    pub fn has_glyphs(&mut self, font_id: &FontId, s: &str) -> bool {
707        self.font(font_id).has_glyphs(s)
708    }
709
710    /// Height of one row of text in points.
711    ///
712    /// Returns a value rounded to [`emath::GUI_ROUNDING`].
713    fn row_height(&mut self, font_id: &FontId) -> f32 {
714        self.font(font_id).row_height()
715    }
716}
717
718// ----------------------------------------------------------------------------
719
720struct CachedGalley {
721    /// When it was last used
722    last_used: u32,
723    galley: Arc<Galley>,
724}
725
726#[derive(Default)]
727struct GalleyCache {
728    /// Frame counter used to do garbage collection on the cache
729    generation: u32,
730    cache: nohash_hasher::IntMap<u64, CachedGalley>,
731}
732
733impl GalleyCache {
734    fn layout(&mut self, fonts: &mut FontsImpl, mut job: LayoutJob) -> Arc<Galley> {
735        if job.wrap.max_width.is_finite() {
736            // Protect against rounding errors in egui layout code.
737
738            // Say the user asks to wrap at width 200.0.
739            // The text layout wraps, and reports that the final width was 196.0 points.
740            // This than trickles up the `Ui` chain and gets stored as the width for a tooltip (say).
741            // On the next frame, this is then set as the max width for the tooltip,
742            // and we end up calling the text layout code again, this time with a wrap width of 196.0.
743            // Except, somewhere in the `Ui` chain with added margins etc, a rounding error was introduced,
744            // so that we actually set a wrap-width of 195.9997 instead.
745            // Now the text that fit perfrectly at 196.0 needs to wrap one word earlier,
746            // and so the text re-wraps and reports a new width of 185.0 points.
747            // And then the cycle continues.
748
749            // So we limit max_width to integers.
750
751            // Related issues:
752            // * https://github.com/emilk/egui/issues/4927
753            // * https://github.com/emilk/egui/issues/4928
754            // * https://github.com/emilk/egui/issues/5084
755            // * https://github.com/emilk/egui/issues/5163
756
757            job.wrap.max_width = job.wrap.max_width.round();
758        }
759
760        let hash = crate::util::hash(&job); // TODO(emilk): even faster hasher?
761
762        match self.cache.entry(hash) {
763            std::collections::hash_map::Entry::Occupied(entry) => {
764                let cached = entry.into_mut();
765                cached.last_used = self.generation;
766                cached.galley.clone()
767            }
768            std::collections::hash_map::Entry::Vacant(entry) => {
769                let galley = super::layout(fonts, job.into());
770                let galley = Arc::new(galley);
771                entry.insert(CachedGalley {
772                    last_used: self.generation,
773                    galley: galley.clone(),
774                });
775                galley
776            }
777        }
778    }
779
780    pub fn num_galleys_in_cache(&self) -> usize {
781        self.cache.len()
782    }
783
784    /// Must be called once per frame to clear the [`Galley`] cache.
785    pub fn flush_cache(&mut self) {
786        let current_generation = self.generation;
787        self.cache.retain(|_key, cached| {
788            cached.last_used == current_generation // only keep those that were used this frame
789        });
790        self.generation = self.generation.wrapping_add(1);
791    }
792}
793
794// ----------------------------------------------------------------------------
795
796struct FontImplCache {
797    atlas: Arc<Mutex<TextureAtlas>>,
798    pixels_per_point: f32,
799    ab_glyph_fonts: BTreeMap<String, (FontTweak, ab_glyph::FontArc)>,
800
801    /// Map font pixel sizes and names to the cached [`FontImpl`].
802    cache: ahash::HashMap<(u32, String), Arc<FontImpl>>,
803}
804
805impl FontImplCache {
806    pub fn new(
807        atlas: Arc<Mutex<TextureAtlas>>,
808        pixels_per_point: f32,
809        font_data: &BTreeMap<String, Arc<FontData>>,
810    ) -> Self {
811        let ab_glyph_fonts = font_data
812            .iter()
813            .map(|(name, font_data)| {
814                let tweak = font_data.tweak;
815                let ab_glyph = ab_glyph_font_from_font_data(name, font_data);
816                (name.clone(), (tweak, ab_glyph))
817            })
818            .collect();
819
820        Self {
821            atlas,
822            pixels_per_point,
823            ab_glyph_fonts,
824            cache: Default::default(),
825        }
826    }
827
828    pub fn font_impl(&mut self, scale_in_points: f32, font_name: &str) -> Arc<FontImpl> {
829        use ab_glyph::Font as _;
830
831        let (tweak, ab_glyph_font) = self
832            .ab_glyph_fonts
833            .get(font_name)
834            .unwrap_or_else(|| panic!("No font data found for {font_name:?}"))
835            .clone();
836
837        let scale_in_pixels = self.pixels_per_point * scale_in_points;
838
839        // Scale the font properly (see https://github.com/emilk/egui/issues/2068).
840        let units_per_em = ab_glyph_font.units_per_em().unwrap_or_else(|| {
841            panic!("The font unit size of {font_name:?} exceeds the expected range (16..=16384)")
842        });
843        let font_scaling = ab_glyph_font.height_unscaled() / units_per_em;
844        let scale_in_pixels = scale_in_pixels * font_scaling;
845
846        self.cache
847            .entry((
848                (scale_in_pixels * tweak.scale).round() as u32,
849                font_name.to_owned(),
850            ))
851            .or_insert_with(|| {
852                Arc::new(FontImpl::new(
853                    self.atlas.clone(),
854                    self.pixels_per_point,
855                    font_name.to_owned(),
856                    ab_glyph_font,
857                    scale_in_pixels,
858                    tweak,
859                ))
860            })
861            .clone()
862    }
863}