Skip to main content

iced_markdown_editor/
fonts.rs

1//! Bundled fonts.
2//!
3//! The same font files are registered with Iced (for drawing) and parsed
4//! with `ab_glyph` (for measuring). Because text is drawn with basic
5//! shaping, the measured advances match the drawn glyph positions exactly,
6//! which keeps the caret, selection and mouse hit-testing precise.
7
8use ab_glyph::{Font as _, FontRef};
9use iced::Font;
10use iced::font::{Style as FontStyle, Weight};
11
12pub const SANS_REGULAR_BYTES: &[u8] = include_bytes!("../assets/fonts/DejaVuSans.ttf");
13pub const SANS_BOLD_BYTES: &[u8] = include_bytes!("../assets/fonts/DejaVuSans-Bold.ttf");
14pub const SANS_ITALIC_BYTES: &[u8] = include_bytes!("../assets/fonts/DejaVuSans-Oblique.ttf");
15pub const SANS_BOLD_ITALIC_BYTES: &[u8] =
16    include_bytes!("../assets/fonts/DejaVuSans-BoldOblique.ttf");
17pub const MONO_REGULAR_BYTES: &[u8] = include_bytes!("../assets/fonts/DejaVuSansMono.ttf");
18pub const MONO_BOLD_BYTES: &[u8] = include_bytes!("../assets/fonts/DejaVuSansMono-Bold.ttf");
19
20/// All bundled font files. Register them with your application:
21///
22/// ```ignore
23/// let mut app = iced::application(App::default, App::update, App::view);
24/// for font in iced_markdown_editor::FONTS {
25///     app = app.font(font);
26/// }
27/// ```
28pub const FONTS: [&[u8]; 6] = [
29    SANS_REGULAR_BYTES,
30    SANS_BOLD_BYTES,
31    SANS_ITALIC_BYTES,
32    SANS_BOLD_ITALIC_BYTES,
33    MONO_REGULAR_BYTES,
34    MONO_BOLD_BYTES,
35];
36
37pub const SANS: Font = Font::with_name("DejaVu Sans");
38pub const SANS_BOLD: Font = Font {
39    weight: Weight::Bold,
40    ..SANS
41};
42pub const SANS_ITALIC: Font = Font {
43    style: FontStyle::Italic,
44    ..SANS
45};
46pub const SANS_BOLD_ITALIC: Font = Font {
47    weight: Weight::Bold,
48    style: FontStyle::Italic,
49    ..SANS
50};
51pub const MONO: Font = Font::with_name("DejaVu Sans Mono");
52pub const MONO_BOLD: Font = Font {
53    weight: Weight::Bold,
54    ..MONO
55};
56
57/// A concrete font face used by the document. The discriminant is the
58/// index into [`FONTS`].
59#[derive(Debug, Clone, Copy, PartialEq, Eq)]
60pub enum Face {
61    Sans = 0,
62    SansBold = 1,
63    SansItalic = 2,
64    SansBoldItalic = 3,
65    Mono = 4,
66    MonoBold = 5,
67}
68
69impl Face {
70    pub fn select(bold: bool, italic: bool, mono: bool) -> Face {
71        match (mono, bold, italic) {
72            (true, true, _) => Face::MonoBold,
73            (true, false, _) => Face::Mono,
74            (false, true, true) => Face::SansBoldItalic,
75            (false, true, false) => Face::SansBold,
76            (false, false, true) => Face::SansItalic,
77            (false, false, false) => Face::Sans,
78        }
79    }
80
81    pub fn font(self) -> Font {
82        match self {
83            Face::Sans => SANS,
84            Face::SansBold => SANS_BOLD,
85            Face::SansItalic => SANS_ITALIC,
86            Face::SansBoldItalic => SANS_BOLD_ITALIC,
87            Face::Mono => MONO,
88            Face::MonoBold => MONO_BOLD,
89        }
90    }
91}
92
93/// Glyph metrics for all bundled faces.
94pub struct Fonts {
95    faces: Vec<FontRef<'static>>,
96    units_per_em: Vec<f32>,
97}
98
99impl Fonts {
100    pub fn load() -> Self {
101        let mut faces = Vec::with_capacity(FONTS.len());
102        let mut units_per_em = Vec::with_capacity(FONTS.len());
103
104        for bytes in FONTS {
105            let face = FontRef::try_from_slice(bytes).expect("bundled font must be valid");
106            units_per_em.push(face.units_per_em().unwrap_or(2048.0));
107            faces.push(face);
108        }
109
110        Self {
111            faces,
112            units_per_em,
113        }
114    }
115
116    /// Horizontal advance of `c` in logical pixels.
117    pub fn advance(&self, face: Face, c: char, size: f32) -> f32 {
118        let index = face as usize;
119        let font = &self.faces[index];
120        let glyph = font.glyph_id(c);
121        font.h_advance_unscaled(glyph) * size / self.units_per_em[index]
122    }
123
124    /// Width of a whole string in logical pixels.
125    pub fn width(&self, face: Face, text: &str, size: f32) -> f32 {
126        text.chars().map(|c| self.advance(face, c, size)).sum()
127    }
128}