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>, 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#[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 pub fn ascender(&self) -> f32 {
56 self.ascender
57 }
58
59 pub fn descender(&self) -> f32 {
61 self.descender
62 }
63
64 pub fn height(&self) -> f32 {
66 self.height.round()
67 }
68
69 pub fn regular(&self) -> bool {
71 self.regular
72 }
73
74 pub fn italic(&self) -> bool {
76 self.italic
77 }
78
79 pub fn bold(&self) -> bool {
81 self.bold
82 }
83
84 pub fn oblique(&self) -> bool {
86 self.oblique
87 }
88
89 pub fn variable(&self) -> bool {
91 self.variable
92 }
93
94 pub fn weight(&self) -> u16 {
96 self.weight
97 }
98
99 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}