diff --git a/CHANGELOG.md b/CHANGELOG.md index 7534ea06..c1d8acbb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -27,6 +27,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). ### Changed 🔧 * Changed default font to [Ubuntu-Light](https://fonts.google.com/specimen/Ubuntu). +* Changed how to override fonts in `FontDefinitions`. * Remove minimum button width * Refactored `egui::Layout` substantially, changing its interface. * Calling `on_hover_text`/`on_hover_ui` multiple times will stack tooltips underneath the previous ones. diff --git a/egui/src/introspection.rs b/egui/src/introspection.rs index 7a7a8735..9d967248 100644 --- a/egui/src/introspection.rs +++ b/egui/src/introspection.rs @@ -56,7 +56,7 @@ impl Texture { impl paint::FontDefinitions { pub fn ui(&mut self, ui: &mut Ui) { - for (text_style, (_family, size)) in self.fonts.iter_mut() { + for (text_style, (_family, size)) in self.family_and_size.iter_mut() { // TODO: radio button for family ui.add( Slider::f32(size, 4.0..=40.0) @@ -65,7 +65,7 @@ impl paint::FontDefinitions { ); } if ui.button("Reset fonts").clicked { - *self = paint::FontDefinitions::with_pixels_per_point(self.pixels_per_point); + *self = paint::FontDefinitions::default_with_pixels_per_point(self.pixels_per_point); } } } diff --git a/egui/src/paint/font.rs b/egui/src/paint/font.rs index 7d53acbb..4424d22e 100644 --- a/egui/src/paint/font.rs +++ b/egui/src/paint/font.rs @@ -145,8 +145,8 @@ impl FontImpl { type FontIndex = usize; -// TODO: rename Layouter ? -/// Wrapper over multiple `FontImpl` (commonly two: primary + emoji fallback) +// TODO: rename? +/// Wrapper over multiple `FontImpl` (e.g. a primary + fallbacks for emojis) pub struct Font { fonts: Vec>, replacement_glyph: (FontIndex, GlyphInfo), diff --git a/egui/src/paint/fonts.rs b/egui/src/paint/fonts.rs index d795fa83..80e3aa3f 100644 --- a/egui/src/paint/fonts.rs +++ b/egui/src/paint/fonts.rs @@ -49,48 +49,89 @@ pub struct FontDefinitions { /// The dpi scale factor. Needed to get pixel perfect fonts. pub pixels_per_point: f32, - pub fonts: BTreeMap, - - /// The TTF data for each font family. + /// List of font names and their definitions. + /// The definition must be the contents of either a `.ttf` or `.otf` font file. + /// /// Egui has built-in-default for these, /// but you can override them if you like. - pub ttf_data: BTreeMap, + pub font_data: BTreeMap>, - /// ttf data for emoji font(s), if any, in order of preference - pub emoji_ttf_data: Vec<&'static [u8]>, + /// Which fonts (names) to use for each `FontFamily`. + /// + /// The list should be a list of keys into `font_data`. + /// When looking for a character glyph, + /// Egui will start will the first font and then move to the second, and so on. + /// So the first font is the primary, and then comes a list of fallbacks in order of priority. + pub fonts_for_family: BTreeMap>, + + /// The `FontFaily` and size you want to use for a specific `TextStyle`. + pub family_and_size: BTreeMap, } impl Default for FontDefinitions { fn default() -> Self { - Self::with_pixels_per_point(f32::NAN) // must be set later + Self::default_with_pixels_per_point(f32::NAN) // must be set later } } impl FontDefinitions { - pub fn with_pixels_per_point(pixels_per_point: f32) -> Self { - let mut fonts = BTreeMap::new(); - fonts.insert(TextStyle::Small, (FontFamily::VariableWidth, 10.0)); - fonts.insert(TextStyle::Body, (FontFamily::VariableWidth, 14.0)); - fonts.insert(TextStyle::Button, (FontFamily::VariableWidth, 16.0)); - fonts.insert(TextStyle::Heading, (FontFamily::VariableWidth, 24.0)); - fonts.insert(TextStyle::Monospace, (FontFamily::Monospace, 13.0)); // 13 for `ProggyClean` + /// Default values for the fonts + pub fn default_with_pixels_per_point(pixels_per_point: f32) -> Self { + let mut font_data: BTreeMap> = BTreeMap::new(); + // Use size 13 for this. NOTHING ELSE: + font_data.insert( + "ProggyClean".to_owned(), + include_bytes!("../../fonts/ProggyClean.ttf").to_vec(), + ); + font_data.insert( + "Ubuntu-Light".to_owned(), + include_bytes!("../../fonts/Ubuntu-Light.ttf").to_vec(), + ); - // TODO: figure out a way to make the WASM smaller despite including a font. Zip it? - let monospace_typeface_data = include_bytes!("../../fonts/ProggyClean.ttf"); // Use 13 for this. NOTHING ELSE. - let variable_typeface_data = include_bytes!("../../fonts/Ubuntu-Light.ttf"); + // Few, but good looking. Use as first priority: + font_data.insert( + "NotoEmoji-Regular".to_owned(), + include_bytes!("../../fonts/NotoEmoji-Regular.ttf").to_vec(), + ); + // Bigger emojis, and more. : + font_data.insert( + "emoji-icon-font".to_owned(), + include_bytes!("../../fonts/emoji-icon-font.ttf").to_vec(), + ); - let mut ttf_data: BTreeMap = BTreeMap::new(); - ttf_data.insert(FontFamily::Monospace, monospace_typeface_data); - ttf_data.insert(FontFamily::VariableWidth, variable_typeface_data); + // TODO: figure out a way to make the WASM smaller despite including fonts. Zip them? + + let mut fonts_for_family = BTreeMap::new(); + fonts_for_family.insert( + FontFamily::Monospace, + vec![ + "ProggyClean".to_owned(), + "Ubuntu-Light".to_owned(), // fallback for √ etc + "NotoEmoji-Regular".to_owned(), + "emoji-icon-font".to_owned(), + ], + ); + fonts_for_family.insert( + FontFamily::VariableWidth, + vec![ + "Ubuntu-Light".to_owned(), + "NotoEmoji-Regular".to_owned(), + "emoji-icon-font".to_owned(), + ], + ); + + let mut family_and_size = BTreeMap::new(); + family_and_size.insert(TextStyle::Small, (FontFamily::VariableWidth, 10.0)); + family_and_size.insert(TextStyle::Body, (FontFamily::VariableWidth, 14.0)); + family_and_size.insert(TextStyle::Button, (FontFamily::VariableWidth, 16.0)); + family_and_size.insert(TextStyle::Heading, (FontFamily::VariableWidth, 24.0)); + family_and_size.insert(TextStyle::Monospace, (FontFamily::Monospace, 13.0)); // 13 for `ProggyClean` Self { pixels_per_point, - fonts, - ttf_data, - emoji_ttf_data: vec![ - include_bytes!("../../fonts/NotoEmoji-Regular.ttf"), // few, but good looking. Use as first priority - include_bytes!("../../fonts/emoji-icon-font.ttf"), // bigger and more: http://jslegers.github.io/emoji-icon-font/ - ], + font_data, + fonts_for_family, + family_and_size, } } } @@ -121,6 +162,7 @@ impl Fonts { if self.definitions == definitions { return; } + self.definitions = definitions; // We want an atlas big enough to be able to include all the Emojis in the `TextStyle::Heading`, // so we can show the Emoji picker demo window. @@ -135,31 +177,17 @@ impl Fonts { let atlas = Arc::new(Mutex::new(atlas)); - self.definitions = definitions; - let mut font_impl_cache = FontImplCache::new(atlas.clone(), &self.definitions); self.fonts = self .definitions - .fonts + .family_and_size .iter() - .map(|(&text_style, &(family, size))| { - let mut fonts = vec![]; - - fonts.push(font_impl_cache.font_impl(FontSource::Family(family), size)); - - if family == FontFamily::Monospace { - // monospace should have ubuntu as fallback (for √ etc): - fonts.push( - font_impl_cache - .font_impl(FontSource::Family(FontFamily::VariableWidth), size), - ); - } - - for index in 0..self.definitions.emoji_ttf_data.len() { - let emoji_font_impl = font_impl_cache.font_impl(FontSource::Emoji(index), size); - fonts.push(emoji_font_impl); - } + .map(|(&text_style, &(family, scale_in_points))| { + let fonts: Vec> = self.definitions.fonts_for_family[&family] + .iter() + .map(|font_name| font_impl_cache.font_impl(font_name, scale_in_points)) + .collect(); (text_style, Font::new(fonts)) }) @@ -210,54 +238,47 @@ pub enum FontSource { pub struct FontImplCache { atlas: Arc>, pixels_per_point: f32, - font_families: std::collections::BTreeMap>>, - emoji_fonts: Vec>>, + rusttype_fonts: std::collections::BTreeMap>>, - /// can't have f32 in a HashMap or BTreeMap, - /// so let's do a linear search - cache: Vec<(FontSource, f32, Arc)>, + /// Map font names and size to the cached `FontImpl`. + /// Can't have f32 in a HashMap or BTreeMap, so let's do a linear search + cache: Vec<(String, f32, Arc)>, } impl FontImplCache { pub fn new(atlas: Arc>, definitions: &super::FontDefinitions) -> Self { - let font_families = definitions - .ttf_data + let rusttype_fonts = definitions + .font_data .iter() - .map(|(family, ttf_data)| { + .map(|(name, font_data)| { ( - *family, - Arc::new(rusttype::Font::try_from_bytes(ttf_data).expect("Error parsing TTF")), + name.clone(), + Arc::new( + rusttype::Font::try_from_vec(font_data.clone()) + .expect("Error parsing TTF/OTF font file"), + ), ) }) .collect(); - let emoji_fonts = definitions - .emoji_ttf_data - .iter() - .map(|ttf_data| { - Arc::new(rusttype::Font::try_from_bytes(ttf_data).expect("Error parsing TTF")) - }) - .collect(); - Self { atlas, pixels_per_point: definitions.pixels_per_point, - font_families, - emoji_fonts, + rusttype_fonts, cache: Default::default(), } } - pub fn rusttype_font(&self, source: FontSource) -> Arc> { - match source { - FontSource::Family(family) => self.font_families.get(&family).unwrap().clone(), - FontSource::Emoji(index) => self.emoji_fonts[index].clone(), - } + pub fn rusttype_font(&self, font_name: &str) -> Arc> { + self.rusttype_fonts + .get(font_name) + .unwrap_or_else(|| panic!("No font data found for {:?}", font_name)) + .clone() } - pub fn font_impl(&mut self, source: FontSource, scale_in_points: f32) -> Arc { + pub fn font_impl(&mut self, font_name: &str, scale_in_points: f32) -> Arc { for entry in &self.cache { - if (entry.0, entry.1) == (source, scale_in_points) { + if (entry.0.as_str(), entry.1) == (font_name, scale_in_points) { return entry.2.clone(); } } @@ -265,11 +286,11 @@ impl FontImplCache { let font_impl = Arc::new(FontImpl::new( self.atlas.clone(), self.pixels_per_point, - self.rusttype_font(source), + self.rusttype_font(font_name), scale_in_points, )); self.cache - .push((source, scale_in_points, font_impl.clone())); + .push((font_name.to_owned(), scale_in_points, font_impl.clone())); font_impl } } diff --git a/egui/src/paint/galley.rs b/egui/src/paint/galley.rs index 00f64c42..f66afa70 100644 --- a/egui/src/paint/galley.rs +++ b/egui/src/paint/galley.rs @@ -657,7 +657,9 @@ fn test_text_layout() { use crate::paint::*; let pixels_per_point = 1.0; - let fonts = Fonts::from_definitions(FontDefinitions::with_pixels_per_point(pixels_per_point)); + let fonts = Fonts::from_definitions(FontDefinitions::default_with_pixels_per_point( + pixels_per_point, + )); let font = &fonts[TextStyle::Monospace]; let galley = font.layout_multiline("".to_owned(), 1024.0);