Implement user-named TextStyle:s

This commit is contained in:
Emil Ernerfeldt 2022-01-22 18:29:53 +01:00
parent 4f98f26fda
commit 1aceaefba3
7 changed files with 109 additions and 74 deletions

View file

@ -58,7 +58,7 @@ pub(crate) fn font_texture_ui(ui: &mut Ui, [width, height]: [usize; 2]) -> Respo
impl Widget for &mut epaint::text::FontDefinitions { impl Widget for &mut epaint::text::FontDefinitions {
fn ui(self, ui: &mut Ui) -> Response { fn ui(self, ui: &mut Ui) -> Response {
ui.vertical(|ui| { ui.vertical(|ui| {
for (text_style, (_family, size)) in self.family_and_size.iter_mut() { for (text_style, (size, _family)) in self.styles.iter_mut() {
// TODO: radio button for family // TODO: radio button for family
ui.add( ui.add(
Slider::new(size, 4.0..=40.0) Slider::new(size, 4.0..=40.0)

View file

@ -597,7 +597,7 @@ impl Style {
}) })
.show_ui(ui, |ui| { .show_ui(ui, |ui| {
ui.selectable_value(override_text_style, None, "None"); ui.selectable_value(override_text_style, None, "None");
for style in TextStyle::all() { for style in TextStyle::built_in() {
let text = let text =
crate::RichText::new(format!("{:?}", style)).text_style(style.clone()); crate::RichText::new(format!("{:?}", style)).text_style(style.clone());
ui.selectable_value(override_text_style, Some(style), text); ui.selectable_value(override_text_style, Some(style), text);

View file

@ -54,7 +54,7 @@ impl super::View for FontBook {
egui::ComboBox::from_label("Text style") egui::ComboBox::from_label("Text style")
.selected_text(format!("{:?}", self.text_style)) .selected_text(format!("{:?}", self.text_style))
.show_ui(ui, |ui| { .show_ui(ui, |ui| {
for style in egui::TextStyle::all() { for style in egui::TextStyle::built_in() {
ui.selectable_value( ui.selectable_value(
&mut self.text_style, &mut self.text_style,
style.clone(), style.clone(),
@ -81,7 +81,7 @@ impl super::View for FontBook {
ui.fonts() ui.fonts()
.lock() .lock()
.fonts .fonts
.font_mut(&text_style) .font_for_style(&text_style)
.characters() .characters()
.iter() .iter()
.filter(|chr| !chr.is_whitespace() && !chr.is_ascii_control()) .filter(|chr| !chr.is_whitespace() && !chr.is_ascii_control())

View file

@ -261,7 +261,7 @@ impl Widget for &mut LegendDemo {
egui::Grid::new("settings").show(ui, |ui| { egui::Grid::new("settings").show(ui, |ui| {
ui.label("Text style:"); ui.label("Text style:");
ui.horizontal(|ui| { ui.horizontal(|ui| {
TextStyle::all().for_each(|style| { TextStyle::built_in().for_each(|style| {
ui.selectable_value( ui.selectable_value(
&mut config.text_style, &mut config.text_style,
style.clone(), style.clone(),

View file

@ -1,6 +1,5 @@
use crate::{ use crate::{
mutex::{Arc, Mutex, RwLock}, mutex::{Arc, Mutex, RwLock},
text::TextStyle,
TextureAtlas, TextureAtlas,
}; };
use ahash::AHashMap; use ahash::AHashMap;
@ -195,7 +194,6 @@ type FontIndex = usize;
// TODO: rename? // TODO: rename?
/// Wrapper over multiple `FontImpl` (e.g. a primary + fallbacks for emojis) /// Wrapper over multiple `FontImpl` (e.g. a primary + fallbacks for emojis)
pub struct Font { pub struct Font {
text_style: TextStyle,
fonts: Vec<Arc<FontImpl>>, fonts: Vec<Arc<FontImpl>>,
/// Lazily calculated. /// Lazily calculated.
characters: Option<std::collections::BTreeSet<char>>, characters: Option<std::collections::BTreeSet<char>>,
@ -206,10 +204,9 @@ pub struct Font {
} }
impl Font { impl Font {
pub fn new(text_style: TextStyle, fonts: Vec<Arc<FontImpl>>) -> Self { pub fn new(fonts: Vec<Arc<FontImpl>>) -> Self {
if fonts.is_empty() { if fonts.is_empty() {
return Self { return Self {
text_style,
fonts, fonts,
characters: None, characters: None,
replacement_glyph: Default::default(), replacement_glyph: Default::default(),
@ -223,7 +220,6 @@ impl Font {
let row_height = fonts[0].row_height(); let row_height = fonts[0].row_height();
let mut slf = Self { let mut slf = Self {
text_style,
fonts, fonts,
characters: None, characters: None,
replacement_glyph: Default::default(), replacement_glyph: Default::default(),
@ -269,11 +265,6 @@ impl Font {
}) })
} }
#[inline(always)]
pub fn text_style(&self) -> &TextStyle {
&self.text_style
}
#[inline(always)] #[inline(always)]
pub fn round_to_pixel(&self, point: f32) -> f32 { pub fn round_to_pixel(&self, point: f32) -> f32 {
(point * self.pixels_per_point).round() / self.pixels_per_point (point * self.pixels_per_point).round() / self.pixels_per_point

View file

@ -9,55 +9,81 @@ use crate::{
TextureAtlas, TextureAtlas,
}; };
// TODO: rename /// Font of unknown size.
///
/// Which style of font: [`Monospace`][`FontFamily::Monospace`], [`Proportional`][`FontFamily::Proportional`],
/// or by user-chosen name.
#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))]
pub enum FontFamily {
/// A font where each character is the same width (`w` is the same width as `i`).
Monospace,
/// A font where some characters are wider than other (e.g. 'w' is wider than 'i').
Proportional,
/// ```
/// // User-chosen names:
/// FontFamily::Name("arial".into());
/// FontFamily::Name("serif".into());
/// ```
Name(Arc<str>),
}
/// Alias for a font of known size.
///
/// One of a few categories of styles of text, e.g. body, button or heading. /// One of a few categories of styles of text, e.g. body, button or heading.
/// Useful in GUI:s.
#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] #[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))] #[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))]
pub enum TextStyle { pub enum TextStyle {
/// Used when small text is needed. /// Used when small text is needed.
Small, Small,
/// Normal labels. Easily readable, doesn't take up too much space. /// Normal labels. Easily readable, doesn't take up too much space.
Body, Body,
/// Buttons. Maybe slightly bigger than `Body`.
Button, /// Same size as [`Self::Body]`, but used when monospace is important (for aligning number, code snippets, etc).
/// Heading. Probably larger than `Body`.
Heading,
/// Same size as `Body`, but used when monospace is important (for aligning number, code snippets, etc).
Monospace, Monospace,
/// Buttons. Maybe slightly bigger than [`Self::Body]`.
/// Signifies that he item is interactive.
Button,
/// Heading. Probably larger than [`Self::Body]`.
Heading,
/// ```
/// // A user-chosen name of a style:
/// TextStyle::Name("footing".into());
/// ````
Name(Arc<str>),
} }
impl TextStyle { impl TextStyle {
pub fn all() -> impl ExactSizeIterator<Item = TextStyle> { /// All default (un-named) `TextStyle`:s.
pub fn built_in() -> impl ExactSizeIterator<Item = TextStyle> {
[ [
TextStyle::Small, TextStyle::Small,
TextStyle::Body, TextStyle::Body,
TextStyle::Monospace,
TextStyle::Button, TextStyle::Button,
TextStyle::Heading, TextStyle::Heading,
TextStyle::Monospace,
] ]
.iter() .iter()
.cloned() .cloned()
} }
} }
/// Which style of font: [`Monospace`][`FontFamily::Monospace`] or [`Proportional`][`FontFamily::Proportional`].
#[derive(Copy, Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))]
pub enum FontFamily {
/// A font where each character is the same width (`w` is the same width as `i`).
Monospace,
/// A font where some characters are wider than other (e.g. 'w' is wider than 'i').
Proportional,
}
/// A `.ttf` or `.otf` file and a font face index. /// A `.ttf` or `.otf` file and a font face index.
#[derive(Clone, Debug, PartialEq)] #[derive(Clone, Debug, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
pub struct FontData { pub struct FontData {
/// The content of a `.ttf` or `.otf` file. /// The content of a `.ttf` or `.otf` file.
pub font: std::borrow::Cow<'static, [u8]>, pub font: std::borrow::Cow<'static, [u8]>,
/// Which font face in the file to use. /// Which font face in the file to use.
/// When in doubt, use `0`. /// When in doubt, use `0`.
pub index: u32, pub index: u32,
@ -105,7 +131,7 @@ fn ab_glyph_font_from_font_data(name: &str, data: &FontData) -> ab_glyph::FontAr
/// let mut fonts = FontDefinitions::default(); /// let mut fonts = FontDefinitions::default();
/// ///
/// // Large button text: /// // Large button text:
/// fonts.family_and_size.insert( /// fonts.styles.insert(
/// TextStyle::Button, /// TextStyle::Button,
/// (FontFamily::Proportional, 32.0) /// (FontFamily::Proportional, 32.0)
/// ); /// );
@ -150,10 +176,11 @@ pub struct FontDefinitions {
/// When looking for a character glyph `epaint` will start with /// When looking for a character glyph `epaint` will start with
/// the first font and then move to the second, and so on. /// 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. /// So the first font is the primary, and then comes a list of fallbacks in order of priority.
// TODO: per font size-modifier.
pub fonts_for_family: BTreeMap<FontFamily, Vec<String>>, pub fonts_for_family: BTreeMap<FontFamily, Vec<String>>,
/// The [`FontFamily`] and size you want to use for a specific [`TextStyle`]. /// The [`FontFamily`] and size you want to use for a specific [`TextStyle`].
pub family_and_size: BTreeMap<TextStyle, (FontFamily, f32)>, pub styles: BTreeMap<TextStyle, (f32, FontFamily)>,
} }
impl Default for FontDefinitions { impl Default for FontDefinitions {
@ -210,17 +237,17 @@ impl Default for FontDefinitions {
fonts_for_family.insert(FontFamily::Proportional, vec![]); fonts_for_family.insert(FontFamily::Proportional, vec![]);
} }
let mut family_and_size = BTreeMap::new(); let mut styles = BTreeMap::new();
family_and_size.insert(TextStyle::Small, (FontFamily::Proportional, 10.0)); styles.insert(TextStyle::Small, (10.0, FontFamily::Proportional));
family_and_size.insert(TextStyle::Body, (FontFamily::Proportional, 14.0)); styles.insert(TextStyle::Body, (14.0, FontFamily::Proportional));
family_and_size.insert(TextStyle::Button, (FontFamily::Proportional, 14.0)); styles.insert(TextStyle::Button, (14.0, FontFamily::Proportional));
family_and_size.insert(TextStyle::Heading, (FontFamily::Proportional, 20.0)); styles.insert(TextStyle::Heading, (20.0, FontFamily::Proportional));
family_and_size.insert(TextStyle::Monospace, (FontFamily::Monospace, 14.0)); styles.insert(TextStyle::Monospace, (14.0, FontFamily::Monospace));
Self { Self {
font_data, font_data,
fonts_for_family, fonts_for_family,
family_and_size, styles,
} }
} }
} }
@ -366,8 +393,9 @@ impl FontsAndCache {
pub struct FontsImpl { pub struct FontsImpl {
pixels_per_point: f32, pixels_per_point: f32,
definitions: FontDefinitions, definitions: FontDefinitions,
fonts: BTreeMap<TextStyle, Font>,
atlas: Arc<Mutex<TextureAtlas>>, atlas: Arc<Mutex<TextureAtlas>>,
font_impl_cache: FontImplCache,
styles: BTreeMap<TextStyle, Font>,
} }
impl FontsImpl { impl FontsImpl {
@ -393,31 +421,24 @@ impl FontsImpl {
let atlas = Arc::new(Mutex::new(atlas)); let atlas = Arc::new(Mutex::new(atlas));
let mut font_impl_cache = FontImplCache::new(atlas.clone(), pixels_per_point, &definitions); let font_impl_cache =
FontImplCache::new(atlas.clone(), pixels_per_point, &definitions.font_data);
let fonts = definitions let mut slf = Self {
.family_and_size
.iter()
.map(|(text_style, (family, scale_in_points))| {
let fonts = &definitions.fonts_for_family.get(family);
let fonts = fonts.unwrap_or_else(|| {
panic!("FontFamily::{:?} is not bound to any fonts", family)
});
let fonts: Vec<Arc<FontImpl>> = fonts
.iter()
.map(|font_name| font_impl_cache.font_impl(font_name, *scale_in_points))
.collect();
(text_style.clone(), Font::new(text_style.clone(), fonts))
})
.collect();
Self {
pixels_per_point, pixels_per_point,
definitions, definitions,
fonts,
atlas, atlas,
font_impl_cache,
styles: Default::default(),
};
// pre-cache all styles:
let styles: Vec<TextStyle> = slf.definitions.styles.keys().cloned().collect();
for style in styles {
slf.font_for_style(&style);
} }
slf
} }
#[inline(always)] #[inline(always)]
@ -425,23 +446,47 @@ impl FontsImpl {
self.pixels_per_point self.pixels_per_point
} }
#[inline]
pub fn definitions(&self) -> &FontDefinitions { pub fn definitions(&self) -> &FontDefinitions {
&self.definitions &self.definitions
} }
#[inline] pub fn font_for_style(&mut self, text_style: &TextStyle) -> &mut Font {
pub fn font_mut(&mut self, text_style: &TextStyle) -> &mut Font { self.styles.entry(text_style.clone()).or_insert_with(|| {
self.fonts.get_mut(text_style).unwrap() let (scale_in_points, family) = self
.definitions
.styles
.get(text_style)
.or_else(|| {
eprintln!(
"Missing TextStyle {:?} in font definitions; falling back to Body",
text_style
);
self.definitions.styles.get(&TextStyle::Body)
})
.unwrap_or_else(|| panic!("Missing {:?} in font definitions", TextStyle::Body));
let fonts = &self.definitions.fonts_for_family.get(family);
let fonts = fonts
.unwrap_or_else(|| panic!("FontFamily::{:?} is not bound to any fonts", family));
let fonts: Vec<Arc<FontImpl>> = fonts
.iter()
.map(|font_name| self.font_impl_cache.font_impl(*scale_in_points, font_name))
.collect();
Font::new(fonts)
})
} }
/// Width of this character in points. /// Width of this character in points.
fn glyph_width(&mut self, text_style: &TextStyle, c: char) -> f32 { fn glyph_width(&mut self, text_style: &TextStyle, c: char) -> f32 {
self.font_mut(text_style).glyph_width(c) self.font_for_style(text_style).glyph_width(c)
} }
/// Height of one row of text. In points /// Height of one row of text. In points
fn row_height(&mut self, text_style: &TextStyle) -> f32 { fn row_height(&mut self, text_style: &TextStyle) -> f32 {
self.font_mut(text_style).row_height() self.font_for_style(text_style).row_height()
} }
} }
@ -511,10 +556,9 @@ impl FontImplCache {
pub fn new( pub fn new(
atlas: Arc<Mutex<TextureAtlas>>, atlas: Arc<Mutex<TextureAtlas>>,
pixels_per_point: f32, pixels_per_point: f32,
definitions: &super::FontDefinitions, font_data: &BTreeMap<String, FontData>,
) -> Self { ) -> Self {
let ab_glyph_fonts = definitions let ab_glyph_fonts = font_data
.font_data
.iter() .iter()
.map(|(name, font_data)| (name.clone(), ab_glyph_font_from_font_data(name, font_data))) .map(|(name, font_data)| (name.clone(), ab_glyph_font_from_font_data(name, font_data)))
.collect(); .collect();
@ -527,7 +571,7 @@ impl FontImplCache {
} }
} }
pub fn font_impl(&mut self, font_name: &str, scale_in_points: f32) -> Arc<FontImpl> { pub fn font_impl(&mut self, scale_in_points: f32, font_name: &str) -> Arc<FontImpl> {
let y_offset = if font_name == "emoji-icon-font" { let y_offset = if font_name == "emoji-icon-font" {
scale_in_points * 0.235 // TODO: remove font alignment hack scale_in_points * 0.235 // TODO: remove font alignment hack
} else { } else {

View file

@ -86,7 +86,7 @@ fn layout_section(
byte_range, byte_range,
format, format,
} = section; } = section;
let font = fonts.font_mut(&format.style); let font = fonts.font_for_style(&format.style);
let font_height = font.row_height(); let font_height = font.row_height();
let mut paragraph = out_paragraphs.last_mut().unwrap(); let mut paragraph = out_paragraphs.last_mut().unwrap();