Skip to main content

vg/text/
font.rs

1use fnv::FnvHashMap;
2use ouroboros::self_referencing;
3use ttf_parser::{
4    Face as TtfFont,
5    GlyphId,
6};
7
8use crate::{
9    ErrorKind,
10    Path,
11};
12
13pub struct GlyphMetrics {
14    pub width: f32,
15    pub height: f32,
16    pub bearing_x: f32,
17    pub bearing_y: f32,
18}
19
20pub struct Glyph {
21    pub path: Option<Path>, // None means render as image
22    pub metrics: GlyphMetrics,
23}
24
25pub(crate) enum GlyphRendering<'a> {
26    RenderAsPath(&'a mut Path),
27    #[cfg(feature = "image-loading")]
28    RenderAsImage(image::DynamicImage),
29}
30
31/// Information about a font.
32// TODO: underline, strikeout, subscript, superscript metrics
33#[derive(Copy, Clone, Default, Debug)]
34pub struct FontMetrics {
35    ascender: f32,
36    descender: f32,
37    height: f32,
38    regular: bool,
39    italic: bool,
40    bold: bool,
41    oblique: bool,
42    variable: bool,
43    weight: u16,
44    width: u16,
45}
46
47impl FontMetrics {
48    fn scale(&mut self, scale: f32) {
49        self.ascender *= scale;
50        self.descender *= scale;
51        self.height *= scale;
52    }
53
54    /// The distance from the baseline to the top of the highest glyph
55    pub fn ascender(&self) -> f32 {
56        self.ascender
57    }
58
59    /// The distance from the baseline to the bottom of the lowest descenders on the glyphs
60    pub fn descender(&self) -> f32 {
61        self.descender
62    }
63
64    /// Font height
65    pub fn height(&self) -> f32 {
66        self.height.round()
67    }
68
69    /// Font is regular
70    pub fn regular(&self) -> bool {
71        self.regular
72    }
73
74    /// Font is italic
75    pub fn italic(&self) -> bool {
76        self.italic
77    }
78
79    /// Font is bold
80    pub fn bold(&self) -> bool {
81        self.bold
82    }
83
84    /// Font is oblique
85    pub fn oblique(&self) -> bool {
86        self.oblique
87    }
88
89    /// Font is variable
90    pub fn variable(&self) -> bool {
91        self.variable
92    }
93
94    /// Font weight
95    pub fn weight(&self) -> u16 {
96        self.weight
97    }
98
99    /// Font width
100    pub fn width(&self) -> u16 {
101        self.width
102    }
103}
104
105#[self_referencing]
106pub(crate) struct Font {
107    data: Box<dyn AsRef<[u8]>>,
108    #[borrows(data)]
109    #[covariant]
110    face_data: rustybuzz::Face<'this>,
111    units_per_em: u16,
112    metrics: FontMetrics,
113    glyphs: FnvHashMap<u16, Glyph>,
114}
115
116impl Font {
117    pub fn new_with_data<T: AsRef<[u8]> + 'static>(data: T, face_index: u32) -> Result<Self, ErrorKind> {
118        let ttf_font = TtfFont::from_slice(data.as_ref(), face_index).map_err(|_| ErrorKind::FontParseError)?;
119
120        let units_per_em = ttf_font.units_per_em().ok_or(ErrorKind::FontInfoExtracionError)?;
121
122        let metrics = FontMetrics {
123            ascender: ttf_font.ascender() as f32,
124            descender: ttf_font.descender() as f32,
125            height: ttf_font.height() as f32,
126            regular: ttf_font.is_regular(),
127            italic: ttf_font.is_italic(),
128            bold: ttf_font.is_bold(),
129            oblique: ttf_font.is_oblique(),
130            variable: ttf_font.is_variable(),
131            weight: ttf_font.width().to_number(),
132            width: ttf_font.weight().to_number(),
133        };
134
135        Ok(Self::new(
136            Box::new(data),
137            |data| rustybuzz::Face::from_slice(data.as_ref().as_ref(), face_index).unwrap(),
138            units_per_em,
139            metrics,
140            Default::default(),
141        ))
142    }
143
144    pub fn face_ref(&self) -> &rustybuzz::Face<'_> {
145        self.borrow_face_data()
146    }
147
148    pub fn metrics(&self, size: f32) -> FontMetrics {
149        let mut metrics = *self.borrow_metrics();
150
151        metrics.scale(self.scale(size));
152
153        metrics
154    }
155
156    pub fn scale(&self, size: f32) -> f32 {
157        size / *self.borrow_units_per_em() as f32
158    }
159
160    pub fn glyph(&mut self, codepoint: u16) -> Option<&mut Glyph> {
161        self.with_mut(|fields| {
162            if !fields.glyphs.contains_key(&codepoint) {
163                let mut path = Path::new();
164
165                let id = GlyphId(codepoint);
166
167                let maybe_glyph = if let Some(image) = fields
168                    .face_data
169                    .glyph_raster_image(id, std::u16::MAX)
170                    .filter(|img| img.format == ttf_parser::RasterImageFormat::PNG)
171                {
172                    let scale = if image.pixels_per_em != 0 {
173                        *fields.units_per_em as f32 / image.pixels_per_em as f32
174                    } else {
175                        1.0
176                    };
177                    Some(Glyph {
178                        path: None,
179                        metrics: GlyphMetrics {
180                            width: image.width as f32 * scale,
181                            height: image.height as f32 * scale,
182                            bearing_x: image.x as f32 * scale,
183                            bearing_y: (image.y as f32 + image.height as f32) * scale,
184                        },
185                    })
186                } else if let Some(bbox) = fields.face_data.outline_glyph(id, &mut path) {
187                    Some(Glyph {
188                        path: Some(path),
189                        metrics: GlyphMetrics {
190                            width: bbox.width() as f32,
191                            height: bbox.height() as f32,
192                            bearing_x: bbox.x_min as f32,
193                            bearing_y: bbox.y_max as f32,
194                        },
195                    })
196                } else {
197                    None
198                };
199
200                if let Some(glyph) = maybe_glyph {
201                    fields.glyphs.insert(codepoint, glyph);
202                }
203            }
204
205            fields.glyphs.get_mut(&codepoint)
206        })
207    }
208
209    pub fn glyph_rendering_representation(&mut self, codepoint: u16, _pixels_per_em: u16) -> Option<GlyphRendering> {
210        #[cfg(feature = "image-loading")]
211        if let Some(image) = self
212            .face_ref()
213            .glyph_raster_image(GlyphId(codepoint), _pixels_per_em)
214            .and_then(|raster_glyph_image| {
215                image::load_from_memory_with_format(raster_glyph_image.data, image::ImageFormat::Png).ok()
216            })
217        {
218            return Some(GlyphRendering::RenderAsImage(image));
219        };
220
221        self.glyph(codepoint)
222            .and_then(|glyph| glyph.path.as_mut().map(GlyphRendering::RenderAsPath))
223    }
224}