Skip to main content

epaint/text/
fonts.rs

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