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