Simplify how fonts are defined in FontDefinitions
This commit is contained in:
parent
5880c95158
commit
b7d1584f44
5 changed files with 104 additions and 80 deletions
|
@ -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.
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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),
|
||||
|
|
|
@ -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]>,
|
||||
pub font_data: BTreeMap<String, Vec<u8>>,
|
||||
|
||||
/// 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<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`
|
||||
/// 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(),
|
||||
);
|
||||
|
||||
// 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. <http://jslegers.github.io/emoji-icon-font/>:
|
||||
font_data.insert(
|
||||
"emoji-icon-font".to_owned(),
|
||||
include_bytes!("../../fonts/emoji-icon-font.ttf").to_vec(),
|
||||
);
|
||||
|
||||
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);
|
||||
// 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<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))
|
||||
})
|
||||
|
@ -210,54 +238,47 @@ 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();
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
Loading…
Reference in a new issue