epaint/text/
fonts.rs

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