epaint/text/
fonts.rs

1use std::{collections::BTreeMap, sync::Arc};
2
3use crate::{
4    AlphaFromCoverage, TextureAtlas,
5    mutex::{Mutex, MutexGuard},
6    text::{
7        Galley, LayoutJob, LayoutSection,
8        font::{Font, FontImpl},
9    },
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
57impl std::hash::Hash for FontId {
58    #[inline(always)]
59    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
60        let Self { size, family } = self;
61        emath::OrderedFloat(*size).hash(state);
62        family.hash(state);
63    }
64}
65
66// ----------------------------------------------------------------------------
67
68/// Font of unknown size.
69///
70/// Which style of font: [`Monospace`][`FontFamily::Monospace`], [`Proportional`][`FontFamily::Proportional`],
71/// or by user-chosen name.
72#[derive(Clone, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
73#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
74pub enum FontFamily {
75    /// A font where some characters are wider than other (e.g. 'w' is wider than 'i').
76    ///
77    /// Proportional fonts are easier to read and should be the preferred choice in most situations.
78    #[default]
79    Proportional,
80
81    /// A font where each character is the same width (`w` is the same width as `i`).
82    ///
83    /// Useful for code snippets, or when you need to align numbers or text.
84    Monospace,
85
86    /// One of the names in [`FontDefinitions::families`].
87    ///
88    /// ```
89    /// # use epaint::FontFamily;
90    /// // User-chosen names:
91    /// FontFamily::Name("arial".into());
92    /// FontFamily::Name("serif".into());
93    /// ```
94    Name(Arc<str>),
95}
96
97impl std::fmt::Display for FontFamily {
98    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
99        match self {
100            Self::Monospace => "Monospace".fmt(f),
101            Self::Proportional => "Proportional".fmt(f),
102            Self::Name(name) => (*name).fmt(f),
103        }
104    }
105}
106
107// ----------------------------------------------------------------------------
108
109/// A `.ttf` or `.otf` file and a font face index.
110#[derive(Clone, Debug, PartialEq)]
111#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
112pub struct FontData {
113    /// The content of a `.ttf` or `.otf` file.
114    pub font: std::borrow::Cow<'static, [u8]>,
115
116    /// Which font face in the file to use.
117    /// When in doubt, use `0`.
118    pub index: u32,
119
120    /// Extra scale and vertical tweak to apply to all text of this font.
121    pub tweak: FontTweak,
122}
123
124impl FontData {
125    pub fn from_static(font: &'static [u8]) -> Self {
126        Self {
127            font: std::borrow::Cow::Borrowed(font),
128            index: 0,
129            tweak: Default::default(),
130        }
131    }
132
133    pub fn from_owned(font: Vec<u8>) -> Self {
134        Self {
135            font: std::borrow::Cow::Owned(font),
136            index: 0,
137            tweak: Default::default(),
138        }
139    }
140
141    pub fn tweak(self, tweak: FontTweak) -> Self {
142        Self { tweak, ..self }
143    }
144}
145
146impl AsRef<[u8]> for FontData {
147    fn as_ref(&self) -> &[u8] {
148        self.font.as_ref()
149    }
150}
151
152// ----------------------------------------------------------------------------
153
154/// Extra scale and vertical tweak to apply to all text of a certain font.
155#[derive(Copy, Clone, Debug, PartialEq)]
156#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
157pub struct FontTweak {
158    /// Scale the font's glyphs by this much.
159    /// this is only a visual effect and does not affect the text layout.
160    ///
161    /// Default: `1.0` (no scaling).
162    pub scale: f32,
163
164    /// Shift font's glyphs downwards by this fraction of the font size (in points).
165    /// this is only a visual effect and does not affect the text layout.
166    ///
167    /// Affects larger font sizes more.
168    ///
169    /// A positive value shifts the text downwards.
170    /// A negative value shifts it upwards.
171    ///
172    /// Example value: `-0.2`.
173    pub y_offset_factor: f32,
174
175    /// Shift font's glyphs downwards by this amount of logical points.
176    /// this is only a visual effect and does not affect the text layout.
177    ///
178    /// Affects all font sizes equally.
179    ///
180    /// Example value: `2.0`.
181    pub y_offset: f32,
182
183    /// When using this font's metrics to layout a row,
184    /// shift the entire row downwards by this fraction of the font size (in points).
185    ///
186    /// A positive value shifts the text downwards.
187    /// A negative value shifts it upwards.
188    pub baseline_offset_factor: f32,
189}
190
191impl Default for FontTweak {
192    fn default() -> Self {
193        Self {
194            scale: 1.0,
195            y_offset_factor: 0.0,
196            y_offset: 0.0,
197            baseline_offset_factor: 0.0,
198        }
199    }
200}
201
202// ----------------------------------------------------------------------------
203
204fn ab_glyph_font_from_font_data(name: &str, data: &FontData) -> ab_glyph::FontArc {
205    match &data.font {
206        std::borrow::Cow::Borrowed(bytes) => {
207            ab_glyph::FontRef::try_from_slice_and_index(bytes, data.index)
208                .map(ab_glyph::FontArc::from)
209        }
210        std::borrow::Cow::Owned(bytes) => {
211            ab_glyph::FontVec::try_from_vec_and_index(bytes.clone(), data.index)
212                .map(ab_glyph::FontArc::from)
213        }
214    }
215    .unwrap_or_else(|err| panic!("Error parsing {name:?} TTF/OTF font file: {err}"))
216}
217
218/// Describes the font data and the sizes to use.
219///
220/// Often you would start with [`FontDefinitions::default()`] and then add/change the contents.
221///
222/// This is how you install your own custom fonts:
223/// ```
224/// # use {epaint::text::{FontDefinitions, FontFamily, FontData}};
225/// # struct FakeEguiCtx {};
226/// # impl FakeEguiCtx { fn set_fonts(&self, _: FontDefinitions) {} }
227/// # let egui_ctx = FakeEguiCtx {};
228/// let mut fonts = FontDefinitions::default();
229///
230/// // Install my own font (maybe supporting non-latin characters):
231/// fonts.font_data.insert("my_font".to_owned(),
232///    std::sync::Arc::new(
233///        // .ttf and .otf supported
234///        FontData::from_static(include_bytes!("../../../epaint_default_fonts/fonts/Ubuntu-Light.ttf"))
235///    )
236/// );
237///
238/// // Put my font first (highest priority):
239/// fonts.families.get_mut(&FontFamily::Proportional).unwrap()
240///     .insert(0, "my_font".to_owned());
241///
242/// // Put my font as last fallback for monospace:
243/// fonts.families.get_mut(&FontFamily::Monospace).unwrap()
244///     .push("my_font".to_owned());
245///
246/// egui_ctx.set_fonts(fonts);
247/// ```
248#[derive(Clone, Debug, PartialEq)]
249#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
250#[cfg_attr(feature = "serde", serde(default))]
251pub struct FontDefinitions {
252    /// List of font names and their definitions.
253    ///
254    /// `epaint` has built-in-default for these, but you can override them if you like.
255    pub font_data: BTreeMap<String, Arc<FontData>>,
256
257    /// Which fonts (names) to use for each [`FontFamily`].
258    ///
259    /// The list should be a list of keys into [`Self::font_data`].
260    /// When looking for a character glyph `epaint` will start with
261    /// the first font and then move to the second, and so on.
262    /// So the first font is the primary, and then comes a list of fallbacks in order of priority.
263    pub families: BTreeMap<FontFamily, Vec<String>>,
264}
265
266#[derive(Debug, Clone)]
267pub struct FontInsert {
268    /// Font name
269    pub name: String,
270
271    /// A `.ttf` or `.otf` file and a font face index.
272    pub data: FontData,
273
274    /// Sets the font family and priority
275    pub families: Vec<InsertFontFamily>,
276}
277
278#[derive(Debug, Clone)]
279pub struct InsertFontFamily {
280    /// Font family
281    pub family: FontFamily,
282
283    /// Fallback or Primary font
284    pub priority: FontPriority,
285}
286
287#[derive(Debug, Clone)]
288pub enum FontPriority {
289    /// Prefer this font before all existing ones.
290    ///
291    /// If a desired glyph exists in this font, it will be used.
292    Highest,
293
294    /// Use this font as a fallback, after all existing ones.
295    ///
296    /// This font will only be used if the glyph is not found in any of the previously installed fonts.
297    Lowest,
298}
299
300impl FontInsert {
301    pub fn new(name: &str, data: FontData, families: Vec<InsertFontFamily>) -> Self {
302        Self {
303            name: name.to_owned(),
304            data,
305            families,
306        }
307    }
308}
309
310impl Default for FontDefinitions {
311    /// Specifies the default fonts if the feature `default_fonts` is enabled,
312    /// otherwise this is the same as [`Self::empty`].
313    #[cfg(not(feature = "default_fonts"))]
314    fn default() -> Self {
315        Self::empty()
316    }
317
318    /// Specifies the default fonts if the feature `default_fonts` is enabled,
319    /// otherwise this is the same as [`Self::empty`].
320    #[cfg(feature = "default_fonts")]
321    fn default() -> Self {
322        let mut font_data: BTreeMap<String, Arc<FontData>> = BTreeMap::new();
323
324        let mut families = BTreeMap::new();
325
326        font_data.insert(
327            "Hack".to_owned(),
328            Arc::new(FontData::from_static(HACK_REGULAR)),
329        );
330
331        // Some good looking emojis. Use as first priority:
332        font_data.insert(
333            "NotoEmoji-Regular".to_owned(),
334            Arc::new(FontData::from_static(NOTO_EMOJI_REGULAR).tweak(FontTweak {
335                scale: 0.81, // Make smaller
336                ..Default::default()
337            })),
338        );
339
340        font_data.insert(
341            "Ubuntu-Light".to_owned(),
342            Arc::new(FontData::from_static(UBUNTU_LIGHT)),
343        );
344
345        // Bigger emojis, and more. <http://jslegers.github.io/emoji-icon-font/>:
346        font_data.insert(
347            "emoji-icon-font".to_owned(),
348            Arc::new(FontData::from_static(EMOJI_ICON).tweak(FontTweak {
349                scale: 0.90, // Make smaller
350                ..Default::default()
351            })),
352        );
353
354        families.insert(
355            FontFamily::Monospace,
356            vec![
357                "Hack".to_owned(),
358                "Ubuntu-Light".to_owned(), // fallback for √ etc
359                "NotoEmoji-Regular".to_owned(),
360                "emoji-icon-font".to_owned(),
361            ],
362        );
363        families.insert(
364            FontFamily::Proportional,
365            vec![
366                "Ubuntu-Light".to_owned(),
367                "NotoEmoji-Regular".to_owned(),
368                "emoji-icon-font".to_owned(),
369            ],
370        );
371
372        Self {
373            font_data,
374            families,
375        }
376    }
377}
378
379impl FontDefinitions {
380    /// No fonts.
381    pub fn empty() -> Self {
382        let mut families = BTreeMap::new();
383        families.insert(FontFamily::Monospace, vec![]);
384        families.insert(FontFamily::Proportional, vec![]);
385
386        Self {
387            font_data: Default::default(),
388            families,
389        }
390    }
391
392    /// List of all the builtin font names used by `epaint`.
393    #[cfg(feature = "default_fonts")]
394    pub fn builtin_font_names() -> &'static [&'static str] {
395        &[
396            "Ubuntu-Light",
397            "NotoEmoji-Regular",
398            "emoji-icon-font",
399            "Hack",
400        ]
401    }
402
403    /// List of all the builtin font names used by `epaint`.
404    #[cfg(not(feature = "default_fonts"))]
405    pub fn builtin_font_names() -> &'static [&'static str] {
406        &[]
407    }
408}
409
410// ----------------------------------------------------------------------------
411
412/// The collection of fonts used by `epaint`.
413///
414/// Required in order to paint text. Create one and reuse. Cheap to clone.
415///
416/// Each [`Fonts`] comes with a font atlas textures that needs to be used when painting.
417///
418/// If you are using `egui`, use `egui::Context::set_fonts` and `egui::Context::fonts`.
419///
420/// You need to call [`Self::begin_pass`] and [`Self::font_image_delta`] once every frame.
421#[derive(Clone)]
422pub struct Fonts(Arc<Mutex<FontsAndCache>>);
423
424impl Fonts {
425    /// Create a new [`Fonts`] for text layout.
426    /// This call is expensive, so only create one [`Fonts`] and then reuse it.
427    ///
428    /// * `pixels_per_point`: how many physical pixels per logical "point".
429    /// * `max_texture_side`: largest supported texture size (one side).
430    pub fn new(
431        pixels_per_point: f32,
432        max_texture_side: usize,
433        text_alpha_from_coverage: AlphaFromCoverage,
434        definitions: FontDefinitions,
435    ) -> Self {
436        let fonts_and_cache = FontsAndCache {
437            fonts: FontsImpl::new(
438                pixels_per_point,
439                max_texture_side,
440                text_alpha_from_coverage,
441                definitions,
442            ),
443            galley_cache: Default::default(),
444        };
445        Self(Arc::new(Mutex::new(fonts_and_cache)))
446    }
447
448    /// Call at the start of each frame with the latest known
449    /// `pixels_per_point`, `max_texture_side`, and `text_alpha_from_coverage`.
450    ///
451    /// Call after painting the previous frame, but before using [`Fonts`] for the new frame.
452    ///
453    /// This function will react to changes in `pixels_per_point`, `max_texture_side`, and `text_alpha_from_coverage`,
454    /// as well as notice when the font atlas is getting full, and handle that.
455    pub fn begin_pass(
456        &self,
457        pixels_per_point: f32,
458        max_texture_side: usize,
459        text_alpha_from_coverage: AlphaFromCoverage,
460    ) {
461        let mut fonts_and_cache = self.0.lock();
462
463        let pixels_per_point_changed = fonts_and_cache.fonts.pixels_per_point != pixels_per_point;
464        let max_texture_side_changed = fonts_and_cache.fonts.max_texture_side != max_texture_side;
465        let text_alpha_from_coverage_changed =
466            fonts_and_cache.fonts.atlas.lock().text_alpha_from_coverage != text_alpha_from_coverage;
467        let font_atlas_almost_full = fonts_and_cache.fonts.atlas.lock().fill_ratio() > 0.8;
468        let needs_recreate = pixels_per_point_changed
469            || max_texture_side_changed
470            || text_alpha_from_coverage_changed
471            || font_atlas_almost_full;
472
473        if needs_recreate {
474            let definitions = fonts_and_cache.fonts.definitions.clone();
475
476            *fonts_and_cache = FontsAndCache {
477                fonts: FontsImpl::new(
478                    pixels_per_point,
479                    max_texture_side,
480                    text_alpha_from_coverage,
481                    definitions,
482                ),
483                galley_cache: Default::default(),
484            };
485        }
486
487        fonts_and_cache.galley_cache.flush_cache();
488    }
489
490    /// Call at the end of each frame (before painting) to get the change to the font texture since last call.
491    pub fn font_image_delta(&self) -> Option<crate::ImageDelta> {
492        self.lock().fonts.atlas.lock().take_delta()
493    }
494
495    /// Access the underlying [`FontsAndCache`].
496    #[doc(hidden)]
497    #[inline]
498    pub fn lock(&self) -> MutexGuard<'_, FontsAndCache> {
499        self.0.lock()
500    }
501
502    #[inline]
503    pub fn pixels_per_point(&self) -> f32 {
504        self.lock().fonts.pixels_per_point
505    }
506
507    #[inline]
508    pub fn max_texture_side(&self) -> usize {
509        self.lock().fonts.max_texture_side
510    }
511
512    /// The font atlas.
513    /// Pass this to [`crate::Tessellator`].
514    pub fn texture_atlas(&self) -> Arc<Mutex<TextureAtlas>> {
515        self.lock().fonts.atlas.clone()
516    }
517
518    /// The full font atlas image.
519    #[inline]
520    pub fn image(&self) -> crate::ColorImage {
521        self.lock().fonts.atlas.lock().image().clone()
522    }
523
524    /// Current size of the font image.
525    /// Pass this to [`crate::Tessellator`].
526    pub fn font_image_size(&self) -> [usize; 2] {
527        self.lock().fonts.atlas.lock().size()
528    }
529
530    /// Width of this character in points.
531    #[inline]
532    pub fn glyph_width(&self, font_id: &FontId, c: char) -> f32 {
533        self.lock().fonts.glyph_width(font_id, c)
534    }
535
536    /// Can we display this glyph?
537    #[inline]
538    pub fn has_glyph(&self, font_id: &FontId, c: char) -> bool {
539        self.lock().fonts.has_glyph(font_id, c)
540    }
541
542    /// Can we display all the glyphs in this text?
543    pub fn has_glyphs(&self, font_id: &FontId, s: &str) -> bool {
544        self.lock().fonts.has_glyphs(font_id, s)
545    }
546
547    /// Height of one row of text in points.
548    ///
549    /// Returns a value rounded to [`emath::GUI_ROUNDING`].
550    #[inline]
551    pub fn row_height(&self, font_id: &FontId) -> f32 {
552        self.lock().fonts.row_height(font_id)
553    }
554
555    /// List of all known font families.
556    pub fn families(&self) -> Vec<FontFamily> {
557        self.lock()
558            .fonts
559            .definitions
560            .families
561            .keys()
562            .cloned()
563            .collect()
564    }
565
566    /// Layout some text.
567    ///
568    /// This is the most advanced layout function.
569    /// See also [`Self::layout`], [`Self::layout_no_wrap`] and
570    /// [`Self::layout_delayed_color`].
571    ///
572    /// The implementation uses memoization so repeated calls are cheap.
573    #[inline]
574    pub fn layout_job(&self, job: LayoutJob) -> Arc<Galley> {
575        self.lock().layout_job(job)
576    }
577
578    pub fn num_galleys_in_cache(&self) -> usize {
579        self.lock().galley_cache.num_galleys_in_cache()
580    }
581
582    /// How full is the font atlas?
583    ///
584    /// This increases as new fonts and/or glyphs are used,
585    /// but can also decrease in a call to [`Self::begin_pass`].
586    pub fn font_atlas_fill_ratio(&self) -> f32 {
587        self.lock().fonts.atlas.lock().fill_ratio()
588    }
589
590    /// Will wrap text at the given width and line break at `\n`.
591    ///
592    /// The implementation uses memoization so repeated calls are cheap.
593    pub fn layout(
594        &self,
595        text: String,
596        font_id: FontId,
597        color: crate::Color32,
598        wrap_width: f32,
599    ) -> Arc<Galley> {
600        let job = LayoutJob::simple(text, font_id, color, wrap_width);
601        self.layout_job(job)
602    }
603
604    /// Will line break at `\n`.
605    ///
606    /// The implementation uses memoization so repeated calls are cheap.
607    pub fn layout_no_wrap(
608        &self,
609        text: String,
610        font_id: FontId,
611        color: crate::Color32,
612    ) -> Arc<Galley> {
613        let job = LayoutJob::simple(text, font_id, color, f32::INFINITY);
614        self.layout_job(job)
615    }
616
617    /// Like [`Self::layout`], made for when you want to pick a color for the text later.
618    ///
619    /// The implementation uses memoization so repeated calls are cheap.
620    pub fn layout_delayed_color(
621        &self,
622        text: String,
623        font_id: FontId,
624        wrap_width: f32,
625    ) -> Arc<Galley> {
626        self.layout(text, font_id, crate::Color32::PLACEHOLDER, wrap_width)
627    }
628}
629
630// ----------------------------------------------------------------------------
631
632pub struct FontsAndCache {
633    pub fonts: FontsImpl,
634    galley_cache: GalleyCache,
635}
636
637impl FontsAndCache {
638    fn layout_job(&mut self, job: LayoutJob) -> Arc<Galley> {
639        let allow_split_paragraphs = true; // Optimization for editing text with many paragraphs.
640        self.galley_cache
641            .layout(&mut self.fonts, job, allow_split_paragraphs)
642    }
643}
644
645// ----------------------------------------------------------------------------
646
647/// The collection of fonts used by `epaint`.
648///
649/// Required in order to paint text.
650pub struct FontsImpl {
651    pixels_per_point: f32,
652    max_texture_side: usize,
653    definitions: FontDefinitions,
654    atlas: Arc<Mutex<TextureAtlas>>,
655    font_impl_cache: FontImplCache,
656    sized_family: ahash::HashMap<(OrderedFloat<f32>, FontFamily), Font>,
657}
658
659impl FontsImpl {
660    /// Create a new [`FontsImpl`] for text layout.
661    /// This call is expensive, so only create one [`FontsImpl`] and then reuse it.
662    pub fn new(
663        pixels_per_point: f32,
664        max_texture_side: usize,
665        text_alpha_from_coverage: AlphaFromCoverage,
666        definitions: FontDefinitions,
667    ) -> Self {
668        assert!(
669            0.0 < pixels_per_point && pixels_per_point < 100.0,
670            "pixels_per_point out of range: {pixels_per_point}"
671        );
672
673        let texture_width = max_texture_side.at_most(16 * 1024);
674        let initial_height = 32; // Keep initial font atlas small, so it is fast to upload to GPU. This will expand as needed anyways.
675        let atlas = TextureAtlas::new([texture_width, initial_height], text_alpha_from_coverage);
676
677        let atlas = Arc::new(Mutex::new(atlas));
678
679        let font_impl_cache =
680            FontImplCache::new(atlas.clone(), pixels_per_point, &definitions.font_data);
681
682        Self {
683            pixels_per_point,
684            max_texture_side,
685            definitions,
686            atlas,
687            font_impl_cache,
688            sized_family: Default::default(),
689        }
690    }
691
692    #[inline(always)]
693    pub fn pixels_per_point(&self) -> f32 {
694        self.pixels_per_point
695    }
696
697    #[inline]
698    pub fn definitions(&self) -> &FontDefinitions {
699        &self.definitions
700    }
701
702    /// Get the right font implementation from size and [`FontFamily`].
703    pub fn font(&mut self, font_id: &FontId) -> &mut Font {
704        let FontId { size, family } = font_id;
705        let mut size = *size;
706        size = size.at_least(0.1).at_most(2048.0);
707
708        self.sized_family
709            .entry((OrderedFloat(size), family.clone()))
710            .or_insert_with(|| {
711                let fonts = &self.definitions.families.get(family);
712                let fonts = fonts
713                    .unwrap_or_else(|| panic!("FontFamily::{family:?} is not bound to any fonts"));
714
715                let fonts: Vec<Arc<FontImpl>> = fonts
716                    .iter()
717                    .map(|font_name| self.font_impl_cache.font_impl(size, font_name))
718                    .collect();
719
720                Font::new(fonts)
721            })
722    }
723
724    /// Width of this character in points.
725    fn glyph_width(&mut self, font_id: &FontId, c: char) -> f32 {
726        self.font(font_id).glyph_width(c)
727    }
728
729    /// Can we display this glyph?
730    pub fn has_glyph(&mut self, font_id: &FontId, c: char) -> bool {
731        self.font(font_id).has_glyph(c)
732    }
733
734    /// Can we display all the glyphs in this text?
735    pub fn has_glyphs(&mut self, font_id: &FontId, s: &str) -> bool {
736        self.font(font_id).has_glyphs(s)
737    }
738
739    /// Height of one row of text in points.
740    ///
741    /// Returns a value rounded to [`emath::GUI_ROUNDING`].
742    fn row_height(&mut self, font_id: &FontId) -> f32 {
743        self.font(font_id).row_height()
744    }
745}
746
747// ----------------------------------------------------------------------------
748
749struct CachedGalley {
750    /// When it was last used
751    last_used: u32,
752
753    /// Hashes of all other entries this one depends on for quick re-layout.
754    /// Their `last_used`s should be updated alongside this one to make sure they're
755    /// not evicted.
756    children: Option<Arc<[u64]>>,
757
758    galley: Arc<Galley>,
759}
760
761#[derive(Default)]
762struct GalleyCache {
763    /// Frame counter used to do garbage collection on the cache
764    generation: u32,
765    cache: nohash_hasher::IntMap<u64, CachedGalley>,
766}
767
768impl GalleyCache {
769    fn layout_internal(
770        &mut self,
771        fonts: &mut FontsImpl,
772        mut job: LayoutJob,
773        allow_split_paragraphs: bool,
774    ) -> (u64, Arc<Galley>) {
775        if job.wrap.max_width.is_finite() {
776            // Protect against rounding errors in egui layout code.
777
778            // Say the user asks to wrap at width 200.0.
779            // The text layout wraps, and reports that the final width was 196.0 points.
780            // This then trickles up the `Ui` chain and gets stored as the width for a tooltip (say).
781            // On the next frame, this is then set as the max width for the tooltip,
782            // and we end up calling the text layout code again, this time with a wrap width of 196.0.
783            // Except, somewhere in the `Ui` chain with added margins etc, a rounding error was introduced,
784            // so that we actually set a wrap-width of 195.9997 instead.
785            // Now the text that fit perfrectly at 196.0 needs to wrap one word earlier,
786            // and so the text re-wraps and reports a new width of 185.0 points.
787            // And then the cycle continues.
788
789            // So we limit max_width to integers.
790
791            // Related issues:
792            // * https://github.com/emilk/egui/issues/4927
793            // * https://github.com/emilk/egui/issues/4928
794            // * https://github.com/emilk/egui/issues/5084
795            // * https://github.com/emilk/egui/issues/5163
796
797            job.wrap.max_width = job.wrap.max_width.round();
798        }
799
800        let hash = crate::util::hash(&job); // TODO(emilk): even faster hasher?
801
802        let galley = match self.cache.entry(hash) {
803            std::collections::hash_map::Entry::Occupied(entry) => {
804                // The job was found in cache - no need to re-layout.
805                let cached = entry.into_mut();
806                cached.last_used = self.generation;
807
808                let galley = cached.galley.clone();
809                if let Some(children) = &cached.children {
810                    // The point of `allow_split_paragraphs` is to split large jobs into paragraph,
811                    // and then cache each paragraph individually.
812                    // That way, if we edit a single paragraph, only that paragraph will be re-layouted.
813                    // For that to work we need to keep all the child/paragraph
814                    // galleys alive while the parent galley is alive:
815                    for child_hash in children.clone().iter() {
816                        if let Some(cached_child) = self.cache.get_mut(child_hash) {
817                            cached_child.last_used = self.generation;
818                        }
819                    }
820                }
821
822                galley
823            }
824            std::collections::hash_map::Entry::Vacant(entry) => {
825                let job = Arc::new(job);
826                if allow_split_paragraphs && should_cache_each_paragraph_individually(&job) {
827                    let (child_galleys, child_hashes) =
828                        self.layout_each_paragraph_individually(fonts, &job);
829                    debug_assert_eq!(
830                        child_hashes.len(),
831                        child_galleys.len(),
832                        "Bug in `layout_each_paragraph_individuallly`"
833                    );
834                    let galley =
835                        Arc::new(Galley::concat(job, &child_galleys, fonts.pixels_per_point));
836
837                    self.cache.insert(
838                        hash,
839                        CachedGalley {
840                            last_used: self.generation,
841                            children: Some(child_hashes.into()),
842                            galley: galley.clone(),
843                        },
844                    );
845                    galley
846                } else {
847                    let galley = super::layout(fonts, job);
848                    let galley = Arc::new(galley);
849                    entry.insert(CachedGalley {
850                        last_used: self.generation,
851                        children: None,
852                        galley: galley.clone(),
853                    });
854                    galley
855                }
856            }
857        };
858
859        (hash, galley)
860    }
861
862    fn layout(
863        &mut self,
864        fonts: &mut FontsImpl,
865        job: LayoutJob,
866        allow_split_paragraphs: bool,
867    ) -> Arc<Galley> {
868        self.layout_internal(fonts, job, allow_split_paragraphs).1
869    }
870
871    /// Split on `\n` and lay out (and cache) each paragraph individually.
872    fn layout_each_paragraph_individually(
873        &mut self,
874        fonts: &mut FontsImpl,
875        job: &LayoutJob,
876    ) -> (Vec<Arc<Galley>>, Vec<u64>) {
877        profiling::function_scope!();
878
879        let mut current_section = 0;
880        let mut start = 0;
881        let mut max_rows_remaining = job.wrap.max_rows;
882        let mut child_galleys = Vec::new();
883        let mut child_hashes = Vec::new();
884
885        while start < job.text.len() {
886            let is_first_paragraph = start == 0;
887            // `end` will not include the `\n` since we don't want to create an empty row in our
888            // split galley
889            let mut end = job.text[start..]
890                .find('\n')
891                .map_or(job.text.len(), |i| start + i);
892            if end == job.text.len() - 1 && job.text.ends_with('\n') {
893                end += 1; // If the text ends with a newline, we include it in the last paragraph.
894            }
895
896            let mut paragraph_job = LayoutJob {
897                text: job.text[start..end].to_owned(),
898                wrap: crate::text::TextWrapping {
899                    max_rows: max_rows_remaining,
900                    ..job.wrap
901                },
902                sections: Vec::new(),
903                break_on_newline: job.break_on_newline,
904                halign: job.halign,
905                justify: job.justify,
906                first_row_min_height: if is_first_paragraph {
907                    job.first_row_min_height
908                } else {
909                    0.0
910                },
911                round_output_to_gui: job.round_output_to_gui,
912            };
913
914            // Add overlapping sections:
915            for section in &job.sections[current_section..job.sections.len()] {
916                let LayoutSection {
917                    leading_space,
918                    byte_range: section_range,
919                    format,
920                } = section;
921
922                // `start` and `end` are the byte range of the current paragraph.
923                // How does the current section overlap with the paragraph range?
924
925                if section_range.end <= start {
926                    // The section is behind us
927                    current_section += 1;
928                } else if end < section_range.start {
929                    break; // Haven't reached this one yet.
930                } else {
931                    // Section range overlaps with paragraph range
932                    debug_assert!(
933                        section_range.start < section_range.end,
934                        "Bad byte_range: {section_range:?}"
935                    );
936                    let new_range = section_range.start.saturating_sub(start)
937                        ..(section_range.end.at_most(end)).saturating_sub(start);
938                    debug_assert!(
939                        new_range.start <= new_range.end,
940                        "Bad new section range: {new_range:?}"
941                    );
942                    paragraph_job.sections.push(LayoutSection {
943                        leading_space: if start <= section_range.start {
944                            *leading_space
945                        } else {
946                            0.0
947                        },
948                        byte_range: new_range,
949                        format: format.clone(),
950                    });
951                }
952            }
953
954            // TODO(emilk): we could lay out each paragraph in parallel to get a nice speedup on multicore machines.
955            let (hash, galley) = self.layout_internal(fonts, paragraph_job, false);
956            child_hashes.push(hash);
957
958            // This will prevent us from invalidating cache entries unnecessarily:
959            if max_rows_remaining != usize::MAX {
960                max_rows_remaining -= galley.rows.len();
961            }
962
963            let elided = galley.elided;
964            child_galleys.push(galley);
965            if elided {
966                break;
967            }
968
969            start = end + 1;
970        }
971
972        (child_galleys, child_hashes)
973    }
974
975    pub fn num_galleys_in_cache(&self) -> usize {
976        self.cache.len()
977    }
978
979    /// Must be called once per frame to clear the [`Galley`] cache.
980    pub fn flush_cache(&mut self) {
981        let current_generation = self.generation;
982        self.cache.retain(|_key, cached| {
983            cached.last_used == current_generation // only keep those that were used this frame
984        });
985        self.generation = self.generation.wrapping_add(1);
986    }
987}
988
989/// If true, lay out and cache each paragraph (sections separated by newlines) individually.
990///
991/// This makes it much faster to re-layout the full text when only a portion of it has changed since last frame, i.e. when editing somewhere in a file with thousands of lines/paragraphs.
992fn should_cache_each_paragraph_individually(job: &LayoutJob) -> bool {
993    // We currently don't support this elided text, i.e. when `max_rows` is set.
994    // Most often, elided text is elided to one row,
995    // and so will always be fast to lay out.
996    job.break_on_newline && job.wrap.max_rows == usize::MAX && job.text.contains('\n')
997}
998
999// ----------------------------------------------------------------------------
1000
1001struct FontImplCache {
1002    atlas: Arc<Mutex<TextureAtlas>>,
1003    pixels_per_point: f32,
1004    ab_glyph_fonts: BTreeMap<String, (FontTweak, ab_glyph::FontArc)>,
1005
1006    /// Map font pixel sizes and names to the cached [`FontImpl`].
1007    cache: ahash::HashMap<(u32, String), Arc<FontImpl>>,
1008}
1009
1010impl FontImplCache {
1011    pub fn new(
1012        atlas: Arc<Mutex<TextureAtlas>>,
1013        pixels_per_point: f32,
1014        font_data: &BTreeMap<String, Arc<FontData>>,
1015    ) -> Self {
1016        let ab_glyph_fonts = font_data
1017            .iter()
1018            .map(|(name, font_data)| {
1019                let tweak = font_data.tweak;
1020                let ab_glyph = ab_glyph_font_from_font_data(name, font_data);
1021                (name.clone(), (tweak, ab_glyph))
1022            })
1023            .collect();
1024
1025        Self {
1026            atlas,
1027            pixels_per_point,
1028            ab_glyph_fonts,
1029            cache: Default::default(),
1030        }
1031    }
1032
1033    pub fn font_impl(&mut self, scale_in_points: f32, font_name: &str) -> Arc<FontImpl> {
1034        use ab_glyph::Font as _;
1035
1036        let (tweak, ab_glyph_font) = self
1037            .ab_glyph_fonts
1038            .get(font_name)
1039            .unwrap_or_else(|| panic!("No font data found for {font_name:?}"))
1040            .clone();
1041
1042        let scale_in_pixels = self.pixels_per_point * scale_in_points;
1043
1044        // Scale the font properly (see https://github.com/emilk/egui/issues/2068).
1045        let units_per_em = ab_glyph_font.units_per_em().unwrap_or_else(|| {
1046            panic!("The font unit size of {font_name:?} exceeds the expected range (16..=16384)")
1047        });
1048        let font_scaling = ab_glyph_font.height_unscaled() / units_per_em;
1049        let scale_in_pixels = scale_in_pixels * font_scaling;
1050
1051        self.cache
1052            .entry((
1053                (scale_in_pixels * tweak.scale).round() as u32,
1054                font_name.to_owned(),
1055            ))
1056            .or_insert_with(|| {
1057                Arc::new(FontImpl::new(
1058                    self.atlas.clone(),
1059                    self.pixels_per_point,
1060                    font_name.to_owned(),
1061                    ab_glyph_font,
1062                    scale_in_pixels,
1063                    tweak,
1064                ))
1065            })
1066            .clone()
1067    }
1068}
1069
1070#[cfg(feature = "default_fonts")]
1071#[cfg(test)]
1072mod tests {
1073    use core::f32;
1074
1075    use super::*;
1076    use crate::text::{TextWrapping, layout};
1077    use crate::{Stroke, text::TextFormat};
1078    use ecolor::Color32;
1079    use emath::Align;
1080
1081    fn jobs() -> Vec<LayoutJob> {
1082        vec![
1083            LayoutJob::simple(
1084                String::default(),
1085                FontId::new(14.0, FontFamily::Monospace),
1086                Color32::WHITE,
1087                f32::INFINITY,
1088            ),
1089            LayoutJob::simple(
1090                "ends with newlines\n\n".to_owned(),
1091                FontId::new(14.0, FontFamily::Monospace),
1092                Color32::WHITE,
1093                f32::INFINITY,
1094            ),
1095            LayoutJob::simple(
1096                "Simple test.".to_owned(),
1097                FontId::new(14.0, FontFamily::Monospace),
1098                Color32::WHITE,
1099                f32::INFINITY,
1100            ),
1101            {
1102                let mut job = LayoutJob::simple(
1103                    "hi".to_owned(),
1104                    FontId::default(),
1105                    Color32::WHITE,
1106                    f32::INFINITY,
1107                );
1108                job.append("\n", 0.0, TextFormat::default());
1109                job.append("\n", 0.0, TextFormat::default());
1110                job.append("world", 0.0, TextFormat::default());
1111                job.wrap.max_rows = 2;
1112                job
1113            },
1114            {
1115                let mut job = LayoutJob::simple(
1116                    "Test text with a lot of words\n and a newline.".to_owned(),
1117                    FontId::new(14.0, FontFamily::Monospace),
1118                    Color32::WHITE,
1119                    40.0,
1120                );
1121                job.first_row_min_height = 30.0;
1122                job
1123            },
1124            LayoutJob::simple(
1125                "This some text that may be long.\nDet kanske också finns lite ÅÄÖ här.".to_owned(),
1126                FontId::new(14.0, FontFamily::Proportional),
1127                Color32::WHITE,
1128                50.0,
1129            ),
1130            {
1131                let mut job = LayoutJob {
1132                    first_row_min_height: 20.0,
1133                    ..Default::default()
1134                };
1135                job.append(
1136                    "1st paragraph has underline and strikethrough, and has some non-ASCII characters:\n ÅÄÖ.",
1137                    0.0,
1138                    TextFormat {
1139                        font_id: FontId::new(15.0, FontFamily::Monospace),
1140                        underline: Stroke::new(1.0, Color32::RED),
1141                        strikethrough: Stroke::new(1.0, Color32::GREEN),
1142                        ..Default::default()
1143                    },
1144                );
1145                job.append(
1146                    "2nd paragraph has some leading space.\n",
1147                    16.0,
1148                    TextFormat {
1149                        font_id: FontId::new(14.0, FontFamily::Proportional),
1150                        ..Default::default()
1151                    },
1152                );
1153                job.append(
1154                    "3rd paragraph is kind of boring, but has italics.\nAnd a newline",
1155                    0.0,
1156                    TextFormat {
1157                        font_id: FontId::new(10.0, FontFamily::Proportional),
1158                        italics: true,
1159                        ..Default::default()
1160                    },
1161                );
1162
1163                job
1164            },
1165        ]
1166    }
1167
1168    #[test]
1169    fn test_split_paragraphs() {
1170        for pixels_per_point in [1.0, 2.0_f32.sqrt(), 2.0] {
1171            let max_texture_side = 4096;
1172            let mut fonts = FontsImpl::new(
1173                pixels_per_point,
1174                max_texture_side,
1175                AlphaFromCoverage::default(),
1176                FontDefinitions::default(),
1177            );
1178
1179            for halign in [Align::Min, Align::Center, Align::Max] {
1180                for justify in [false, true] {
1181                    for mut job in jobs() {
1182                        job.halign = halign;
1183                        job.justify = justify;
1184
1185                        let whole = GalleyCache::default().layout(&mut fonts, job.clone(), false);
1186
1187                        let split = GalleyCache::default().layout(&mut fonts, job.clone(), true);
1188
1189                        for (i, row) in whole.rows.iter().enumerate() {
1190                            println!(
1191                                "Whole row {i}: section_index_at_start={}, first glyph section_index: {:?}",
1192                                row.row.section_index_at_start,
1193                                row.row.glyphs.first().map(|g| g.section_index)
1194                            );
1195                        }
1196                        for (i, row) in split.rows.iter().enumerate() {
1197                            println!(
1198                                "Split row {i}: section_index_at_start={}, first glyph section_index: {:?}",
1199                                row.row.section_index_at_start,
1200                                row.row.glyphs.first().map(|g| g.section_index)
1201                            );
1202                        }
1203
1204                        // Don't compare for equaliity; but format with a specific precision and make sure we hit that.
1205                        // NOTE: we use a rather low precision, because as long as we're within a pixel I think it's good enough.
1206                        similar_asserts::assert_eq!(
1207                            format!("{:#.1?}", split),
1208                            format!("{:#.1?}", whole),
1209                            "pixels_per_point: {pixels_per_point:.2}, input text: '{}'",
1210                            job.text
1211                        );
1212                    }
1213                }
1214            }
1215        }
1216    }
1217
1218    #[test]
1219    fn test_intrinsic_size() {
1220        let pixels_per_point = [1.0, 1.3, 2.0, 0.867];
1221        let max_widths = [40.0, 80.0, 133.0, 200.0];
1222        let rounded_output_to_gui = [false, true];
1223
1224        for pixels_per_point in pixels_per_point {
1225            let mut fonts = FontsImpl::new(
1226                pixels_per_point,
1227                1024,
1228                AlphaFromCoverage::default(),
1229                FontDefinitions::default(),
1230            );
1231
1232            for &max_width in &max_widths {
1233                for round_output_to_gui in rounded_output_to_gui {
1234                    for mut job in jobs() {
1235                        job.wrap = TextWrapping::wrap_at_width(max_width);
1236
1237                        job.round_output_to_gui = round_output_to_gui;
1238
1239                        let galley_wrapped = layout(&mut fonts, job.clone().into());
1240
1241                        job.wrap = TextWrapping::no_max_width();
1242
1243                        let text = job.text.clone();
1244                        let galley_unwrapped = layout(&mut fonts, job.into());
1245
1246                        let intrinsic_size = galley_wrapped.intrinsic_size();
1247                        let unwrapped_size = galley_unwrapped.size();
1248
1249                        let difference = (intrinsic_size - unwrapped_size).length().abs();
1250                        similar_asserts::assert_eq!(
1251                            format!("{intrinsic_size:.4?}"),
1252                            format!("{unwrapped_size:.4?}"),
1253                            "Wrapped intrinsic size should almost match unwrapped size. Intrinsic: {intrinsic_size:.8?} vs unwrapped: {unwrapped_size:.8?}
1254                                Difference: {difference:.8?}
1255                                wrapped rows: {}, unwrapped rows: {}
1256                                pixels_per_point: {pixels_per_point}, text: {text:?}, max_width: {max_width}, round_output_to_gui: {round_output_to_gui}",
1257                            galley_wrapped.rows.len(),
1258                            galley_unwrapped.rows.len()
1259                            );
1260                        similar_asserts::assert_eq!(
1261                            format!("{intrinsic_size:.4?}"),
1262                            format!("{unwrapped_size:.4?}"),
1263                            "Unwrapped galley intrinsic size should exactly match its size. \
1264                                {:.8?} vs {:8?}",
1265                            galley_unwrapped.intrinsic_size(),
1266                            galley_unwrapped.size(),
1267                        );
1268                    }
1269                }
1270            }
1271        }
1272    }
1273}