Skip to content

Commit

Permalink
Simplify how fonts are defined in FontDefinitions
Browse files Browse the repository at this point in the history
  • Loading branch information
emilk committed Dec 13, 2020
1 parent 5880c95 commit b7d1584
Show file tree
Hide file tree
Showing 5 changed files with 107 additions and 83 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
4 changes: 2 additions & 2 deletions egui/src/introspection.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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);
}
}
}
4 changes: 2 additions & 2 deletions egui/src/paint/font.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<Arc<FontImpl>>,
replacement_glyph: (FontIndex, GlyphInfo),
Expand Down
177 changes: 99 additions & 78 deletions egui/src/paint/fonts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<TextStyle, (FontFamily, f32)>,

/// 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<FontFamily, &'static [u8]>,

/// ttf data for emoji font(s), if any, in order of preference
pub emoji_ttf_data: Vec<&'static [u8]>,
pub font_data: BTreeMap<String, Vec<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<FontFamily, Vec<String>>,

/// The `FontFaily` and size you want to use for a specific `TextStyle`.
pub family_and_size: BTreeMap<TextStyle, (FontFamily, f32)>,
}

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`

// 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");

let mut ttf_data: BTreeMap<FontFamily, &'static [u8]> = BTreeMap::new();
ttf_data.insert(FontFamily::Monospace, monospace_typeface_data);
ttf_data.insert(FontFamily::VariableWidth, variable_typeface_data);
/// Default values for the fonts
pub fn default_with_pixels_per_point(pixels_per_point: f32) -> Self {
let mut font_data: BTreeMap<String, Vec<u8>> = 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(),
);

// 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. <http://jslegers.github.io/emoji-icon-font/>:
font_data.insert(
"emoji-icon-font".to_owned(),
include_bytes!("../../fonts/emoji-icon-font.ttf").to_vec(),
);

// 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,
}
}
}
Expand Down Expand Up @@ -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.
Expand All @@ -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<Arc<FontImpl>> = 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))
})
Expand Down Expand Up @@ -210,66 +238,59 @@ pub enum FontSource {
pub struct FontImplCache {
atlas: Arc<Mutex<TextureAtlas>>,
pixels_per_point: f32,
font_families: std::collections::BTreeMap<FontFamily, Arc<rusttype::Font<'static>>>,
emoji_fonts: Vec<Arc<rusttype::Font<'static>>>,
rusttype_fonts: std::collections::BTreeMap<String, Arc<rusttype::Font<'static>>>,

/// can't have f32 in a HashMap or BTreeMap,
/// so let's do a linear search
cache: Vec<(FontSource, f32, Arc<FontImpl>)>,
/// 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<FontImpl>)>,
}

impl FontImplCache {
pub fn new(atlas: Arc<Mutex<TextureAtlas>>, 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<rusttype::Font<'static>> {
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<rusttype::Font<'static>> {
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<FontImpl> {
pub fn font_impl(&mut self, font_name: &str, scale_in_points: f32) -> Arc<FontImpl> {
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();
}
}

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
}
}
4 changes: 3 additions & 1 deletion egui/src/paint/galley.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down

0 comments on commit b7d1584

Please sign in to comment.