1 | //! ttf-parser crate specific code. ttf-parser types should not be leaked publicly. |
2 | mod outliner; |
3 | #[cfg (feature = "variable-fonts" )] |
4 | mod variable; |
5 | |
6 | use crate::{point, v2, Font, GlyphId, GlyphImageFormat, GlyphSvg, InvalidFont, Outline, Rect}; |
7 | use alloc::boxed::Box; |
8 | #[cfg (not(feature = "std" ))] |
9 | use alloc::vec::Vec; |
10 | use core::fmt; |
11 | use owned_ttf_parser::{self as ttfp, AsFaceRef}; |
12 | |
13 | impl From<GlyphId> for ttfp::GlyphId { |
14 | #[inline ] |
15 | fn from(id: GlyphId) -> Self { |
16 | Self(id.0) |
17 | } |
18 | } |
19 | |
20 | /// Font data handle stored as a `&[u8]` + parsed data. |
21 | /// See [`Font`] for more methods. |
22 | /// |
23 | /// Also see the owned version [`FontVec`]. |
24 | /// |
25 | /// # Example |
26 | /// ``` |
27 | /// use ab_glyph::{Font, FontRef}; |
28 | /// |
29 | /// # fn main() -> Result<(), ab_glyph::InvalidFont> { |
30 | /// let font = FontRef::try_from_slice(include_bytes!("../../dev/fonts/Exo2-Light.otf" ))?; |
31 | /// |
32 | /// assert_eq!(font.glyph_id('s' ), ab_glyph::GlyphId(56)); |
33 | /// # Ok(()) } |
34 | /// ``` |
35 | #[derive (Clone)] |
36 | pub struct FontRef<'font>(ttfp::PreParsedSubtables<'font, ttfp::Face<'font>>); |
37 | |
38 | impl fmt::Debug for FontRef<'_> { |
39 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
40 | write!(f, "FontRef" ) |
41 | } |
42 | } |
43 | |
44 | impl<'font> FontRef<'font> { |
45 | /// Creates an `FontRef` from a byte-slice. |
46 | /// |
47 | /// For font collections see |
48 | /// [`FontRef::try_from_slice_and_index`]. |
49 | /// |
50 | /// # Example |
51 | /// ``` |
52 | /// # use ab_glyph::*; |
53 | /// # fn main() -> Result<(), InvalidFont> { |
54 | /// let font = FontRef::try_from_slice(include_bytes!("../../dev/fonts/Exo2-Light.otf" ))?; |
55 | /// # Ok(()) } |
56 | /// ``` |
57 | #[inline ] |
58 | pub fn try_from_slice(data: &'font [u8]) -> Result<Self, InvalidFont> { |
59 | Self::try_from_slice_and_index(data, 0) |
60 | } |
61 | |
62 | /// Creates an `FontRef` from byte-slice. |
63 | /// |
64 | /// You can set index for font collections. For simple fonts use `0` or |
65 | /// [`FontRef::try_from_slice`]. |
66 | /// |
67 | /// # Example |
68 | /// ``` |
69 | /// # use ab_glyph::*; |
70 | /// # fn main() -> Result<(), InvalidFont> { |
71 | /// let font = |
72 | /// FontRef::try_from_slice_and_index(include_bytes!("../../dev/fonts/Exo2-Light.otf" ), 0)?; |
73 | /// # Ok(()) } |
74 | /// ``` |
75 | #[inline ] |
76 | pub fn try_from_slice_and_index(data: &'font [u8], index: u32) -> Result<Self, InvalidFont> { |
77 | Ok(Self(ttfp::PreParsedSubtables::from( |
78 | ttfp::Face::parse(data, index).map_err(|_| InvalidFont)?, |
79 | ))) |
80 | } |
81 | } |
82 | |
83 | /// Font data handle stored in a `Vec<u8>` + parsed data. |
84 | /// See [`Font`] for more methods. |
85 | /// |
86 | /// Also see [`FontRef`]. |
87 | /// |
88 | /// # Example |
89 | /// ``` |
90 | /// use ab_glyph::{Font, FontVec}; |
91 | /// |
92 | /// # fn main() -> Result<(), ab_glyph::InvalidFont> { |
93 | /// # let owned_font_data = include_bytes!("../../dev/fonts/Exo2-Light.otf" ).to_vec(); |
94 | /// let font = FontVec::try_from_vec_and_index(owned_font_data, 0)?; |
95 | /// |
96 | /// assert_eq!(font.glyph_id('s' ), ab_glyph::GlyphId(56)); |
97 | /// # Ok(()) } |
98 | /// ``` |
99 | pub struct FontVec(ttfp::PreParsedSubtables<'static, ttfp::OwnedFace>); |
100 | |
101 | impl fmt::Debug for FontVec { |
102 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
103 | write!(f, "FontVec" ) |
104 | } |
105 | } |
106 | |
107 | impl FontVec { |
108 | /// Creates an `FontVec` from owned data. |
109 | /// |
110 | /// For font collections see |
111 | /// [`FontVec::try_from_vec_and_index`]. |
112 | /// |
113 | /// # Example |
114 | /// ``` |
115 | /// # use ab_glyph::*; |
116 | /// # fn main() -> Result<(), InvalidFont> { |
117 | /// # let owned_font_data = include_bytes!("../../dev/fonts/Exo2-Light.otf" ).to_vec(); |
118 | /// let font = FontVec::try_from_vec(owned_font_data)?; |
119 | /// # Ok(()) } |
120 | /// ``` |
121 | #[inline ] |
122 | pub fn try_from_vec(data: Vec<u8>) -> Result<Self, InvalidFont> { |
123 | Self::try_from_vec_and_index(data, 0) |
124 | } |
125 | |
126 | /// Creates an `FontVec` from owned data. |
127 | /// |
128 | /// You can set index for font collections. For simple fonts use `0` or |
129 | /// [`FontVec::try_from_vec`]. |
130 | /// |
131 | /// # Example |
132 | /// ``` |
133 | /// # use ab_glyph::*; |
134 | /// # fn main() -> Result<(), InvalidFont> { |
135 | /// # let owned_font_data = include_bytes!("../../dev/fonts/Exo2-Light.otf" ).to_vec(); |
136 | /// let font = FontVec::try_from_vec_and_index(owned_font_data, 0)?; |
137 | /// # Ok(()) } |
138 | /// ``` |
139 | #[inline ] |
140 | pub fn try_from_vec_and_index(data: Vec<u8>, index: u32) -> Result<Self, InvalidFont> { |
141 | Ok(Self(ttfp::PreParsedSubtables::from( |
142 | ttfp::OwnedFace::from_vec(data, index).map_err(|_| InvalidFont)?, |
143 | ))) |
144 | } |
145 | |
146 | /// Extracts a slice containing the data passed into e.g. [`FontVec::try_from_vec`]. |
147 | /// |
148 | /// # Example |
149 | /// ``` |
150 | /// # use ab_glyph::*; |
151 | /// # fn main() -> Result<(), InvalidFont> { |
152 | /// # let owned_font_data = include_bytes!("../../dev/fonts/Exo2-Light.otf" ).to_vec(); |
153 | /// let font_data_clone = owned_font_data.clone(); |
154 | /// let font = FontVec::try_from_vec(owned_font_data)?; |
155 | /// assert_eq!(font.as_slice(), font_data_clone); |
156 | /// # Ok(()) } |
157 | /// ``` |
158 | #[inline ] |
159 | pub fn as_slice(&self) -> &[u8] { |
160 | self.0.face.as_slice() |
161 | } |
162 | |
163 | /// Unwraps the data passed into e.g. [`FontVec::try_from_vec`]. |
164 | /// |
165 | /// # Example |
166 | /// ``` |
167 | /// # use ab_glyph::*; |
168 | /// # fn main() -> Result<(), InvalidFont> { |
169 | /// # let owned_font_data = include_bytes!("../../dev/fonts/Exo2-Light.otf" ).to_vec(); |
170 | /// let font_data_clone = owned_font_data.clone(); |
171 | /// let font = FontVec::try_from_vec(owned_font_data)?; |
172 | /// assert_eq!(font.into_vec(), font_data_clone); |
173 | /// # Ok(()) } |
174 | /// ``` |
175 | #[inline ] |
176 | pub fn into_vec(self) -> Vec<u8> { |
177 | self.0.face.into_vec() |
178 | } |
179 | } |
180 | |
181 | /// Implement `Font` for `Self(AsFontRef)` types. |
182 | macro_rules! impl_font { |
183 | ($font:ty) => { |
184 | impl Font for $font { |
185 | #[inline] |
186 | fn units_per_em(&self) -> Option<f32> { |
187 | // TODO unwrap signature when making next breaking change |
188 | Some(self.0.as_face_ref().units_per_em().into()) |
189 | } |
190 | |
191 | #[inline] |
192 | fn ascent_unscaled(&self) -> f32 { |
193 | self.0.as_face_ref().ascender().into() |
194 | } |
195 | |
196 | #[inline] |
197 | fn descent_unscaled(&self) -> f32 { |
198 | self.0.as_face_ref().descender().into() |
199 | } |
200 | |
201 | #[inline] |
202 | fn line_gap_unscaled(&self) -> f32 { |
203 | self.0.as_face_ref().line_gap().into() |
204 | } |
205 | |
206 | #[inline] |
207 | fn glyph_id(&self, c: char) -> GlyphId { |
208 | // Note: Using `PreParsedSubtables` method for better performance. |
209 | let index = self.0.glyph_index(c).map(|id| id.0).unwrap_or(0); |
210 | GlyphId(index) |
211 | } |
212 | |
213 | #[inline] |
214 | fn h_advance_unscaled(&self, id: GlyphId) -> f32 { |
215 | self.0 |
216 | .as_face_ref() |
217 | .glyph_hor_advance(id.into()) |
218 | .unwrap_or_default() |
219 | .into() |
220 | } |
221 | |
222 | #[inline] |
223 | fn h_side_bearing_unscaled(&self, id: GlyphId) -> f32 { |
224 | self.0 |
225 | .as_face_ref() |
226 | .glyph_hor_side_bearing(id.into()) |
227 | .unwrap_or_default() |
228 | .into() |
229 | } |
230 | |
231 | #[inline] |
232 | fn v_advance_unscaled(&self, id: GlyphId) -> f32 { |
233 | self.0 |
234 | .as_face_ref() |
235 | .glyph_ver_advance(id.into()) |
236 | .unwrap_or_default() |
237 | .into() |
238 | } |
239 | |
240 | #[inline] |
241 | fn v_side_bearing_unscaled(&self, id: GlyphId) -> f32 { |
242 | self.0 |
243 | .as_face_ref() |
244 | .glyph_ver_side_bearing(id.into()) |
245 | .unwrap_or_default() |
246 | .into() |
247 | } |
248 | |
249 | #[inline] |
250 | fn kern_unscaled(&self, first: GlyphId, second: GlyphId) -> f32 { |
251 | // Note: Using `PreParsedSubtables` method for better performance. |
252 | self.0 |
253 | .glyphs_hor_kerning(first.into(), second.into()) |
254 | .map(f32::from) |
255 | .unwrap_or_default() |
256 | } |
257 | |
258 | fn outline(&self, id: GlyphId) -> Option<Outline> { |
259 | let mut outliner = outliner::OutlineCurveBuilder::default(); |
260 | |
261 | let ttfp::Rect { |
262 | x_min, |
263 | x_max, |
264 | y_min, |
265 | y_max, |
266 | } = self |
267 | .0 |
268 | .as_face_ref() |
269 | .outline_glyph(id.into(), &mut outliner) |
270 | // invalid bounds are treated as having no outline |
271 | .filter(|b| b.x_min < b.x_max && b.y_min < b.y_max)?; |
272 | |
273 | let curves = outliner.take_outline(); |
274 | |
275 | let bounds = Rect { |
276 | min: point(x_min.into(), y_max.into()), |
277 | max: point(x_max.into(), y_min.into()), |
278 | }; |
279 | |
280 | Some(Outline { bounds, curves }) |
281 | } |
282 | |
283 | #[inline] |
284 | fn glyph_count(&self) -> usize { |
285 | self.0.as_face_ref().number_of_glyphs() as _ |
286 | } |
287 | |
288 | fn codepoint_ids(&self) -> crate::CodepointIdIter<'_> { |
289 | let face_ref = self.0.as_face_ref(); |
290 | |
291 | #[cfg(feature = "std" )] |
292 | let mut used_indices = |
293 | std::collections::HashSet::with_capacity(face_ref.number_of_glyphs() as _); |
294 | #[cfg(not(feature = "std" ))] |
295 | let mut used_indices = alloc::collections::BTreeSet::new(); |
296 | |
297 | let inner = Box::new( |
298 | face_ref |
299 | .tables() |
300 | .cmap |
301 | .iter() |
302 | .flat_map(|c| c.subtables) |
303 | .filter(|s| s.is_unicode()) |
304 | .flat_map(move |subtable| { |
305 | let mut pairs = Vec::new(); |
306 | subtable.codepoints(|c| { |
307 | if let Ok(ch) = char::try_from(c) { |
308 | if let Some(idx) = subtable.glyph_index(c).filter(|i| i.0 > 0) { |
309 | if used_indices.insert(idx.0) { |
310 | pairs.push((GlyphId(idx.0), ch)); |
311 | } |
312 | } |
313 | } |
314 | }); |
315 | pairs |
316 | }), |
317 | ); |
318 | |
319 | crate::CodepointIdIter { inner } |
320 | } |
321 | |
322 | fn glyph_raster_image2(&self, id: GlyphId, size: u16) -> Option<v2::GlyphImage> { |
323 | use GlyphImageFormat::*; |
324 | |
325 | let img = self.0.as_face_ref().glyph_raster_image(id.into(), size)?; |
326 | Some(v2::GlyphImage { |
327 | origin: point(img.x.into(), img.y.into()), |
328 | width: img.width, |
329 | height: img.height, |
330 | pixels_per_em: img.pixels_per_em, |
331 | data: img.data, |
332 | format: match img.format { |
333 | ttfp::RasterImageFormat::PNG => Png, |
334 | ttfp::RasterImageFormat::BitmapMono => BitmapMono, |
335 | ttfp::RasterImageFormat::BitmapMonoPacked => BitmapMonoPacked, |
336 | ttfp::RasterImageFormat::BitmapGray2 => BitmapGray2, |
337 | ttfp::RasterImageFormat::BitmapGray2Packed => BitmapGray2Packed, |
338 | ttfp::RasterImageFormat::BitmapGray4 => BitmapGray4, |
339 | ttfp::RasterImageFormat::BitmapGray4Packed => BitmapGray4Packed, |
340 | ttfp::RasterImageFormat::BitmapGray8 => BitmapGray8, |
341 | ttfp::RasterImageFormat::BitmapPremulBgra32 => BitmapPremulBgra32, |
342 | }, |
343 | }) |
344 | } |
345 | |
346 | fn glyph_svg_image(&self, id: GlyphId) -> Option<GlyphSvg> { |
347 | let img = self.0.as_face_ref().glyph_svg_image(id.into())?; |
348 | |
349 | Some(GlyphSvg { |
350 | data: img.data, |
351 | start_glyph_id: GlyphId(img.start_glyph_id.0), |
352 | end_glyph_id: GlyphId(img.end_glyph_id.0), |
353 | }) |
354 | } |
355 | |
356 | #[inline] |
357 | fn font_data(&self) -> &[u8] { |
358 | self.0.as_face_ref().raw_face().data |
359 | } |
360 | } |
361 | }; |
362 | } |
363 | |
364 | impl_font!(FontRef<'_>); |
365 | impl_font!(FontVec); |
366 | |