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, 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`](trait.Font.html) for more methods. |
22 | /// |
23 | /// Also see the owned version [`FontVec`](struct.FontVec.html). |
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`](#method.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`](#method.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`](trait.Font.html) for more methods. |
85 | /// |
86 | /// Also see [`FontRef`](struct.FontRef.html). |
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`](#method.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`](#method.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 | pub fn into_vec(self) -> Vec<u8> { |
176 | self.0.face.into_vec() |
177 | } |
178 | } |
179 | |
180 | /// Implement `Font` for `Self(AsFontRef)` types. |
181 | macro_rules! impl_font { |
182 | ($font:ty) => { |
183 | impl Font for $font { |
184 | #[inline] |
185 | fn units_per_em(&self) -> Option<f32> { |
186 | // TODO unwrap signature when making next breaking change |
187 | Some(self.0.as_face_ref().units_per_em().into()) |
188 | } |
189 | |
190 | #[inline] |
191 | fn ascent_unscaled(&self) -> f32 { |
192 | self.0.as_face_ref().ascender().into() |
193 | } |
194 | |
195 | #[inline] |
196 | fn descent_unscaled(&self) -> f32 { |
197 | self.0.as_face_ref().descender().into() |
198 | } |
199 | |
200 | #[inline] |
201 | fn line_gap_unscaled(&self) -> f32 { |
202 | self.0.as_face_ref().line_gap().into() |
203 | } |
204 | |
205 | #[inline] |
206 | fn glyph_id(&self, c: char) -> GlyphId { |
207 | // Note: Using `PreParsedSubtables` method for better performance. |
208 | let index = self.0.glyph_index(c).map(|id| id.0).unwrap_or(0); |
209 | GlyphId(index) |
210 | } |
211 | |
212 | #[inline] |
213 | fn h_advance_unscaled(&self, id: GlyphId) -> f32 { |
214 | self.0 |
215 | .as_face_ref() |
216 | .glyph_hor_advance(id.into()) |
217 | .unwrap_or_default() |
218 | .into() |
219 | } |
220 | |
221 | #[inline] |
222 | fn h_side_bearing_unscaled(&self, id: GlyphId) -> f32 { |
223 | self.0 |
224 | .as_face_ref() |
225 | .glyph_hor_side_bearing(id.into()) |
226 | .unwrap_or_default() |
227 | .into() |
228 | } |
229 | |
230 | #[inline] |
231 | fn v_advance_unscaled(&self, id: GlyphId) -> f32 { |
232 | self.0 |
233 | .as_face_ref() |
234 | .glyph_ver_advance(id.into()) |
235 | .unwrap_or_default() |
236 | .into() |
237 | } |
238 | |
239 | #[inline] |
240 | fn v_side_bearing_unscaled(&self, id: GlyphId) -> f32 { |
241 | self.0 |
242 | .as_face_ref() |
243 | .glyph_ver_side_bearing(id.into()) |
244 | .unwrap_or_default() |
245 | .into() |
246 | } |
247 | |
248 | #[inline] |
249 | fn kern_unscaled(&self, first: GlyphId, second: GlyphId) -> f32 { |
250 | // Note: Using `PreParsedSubtables` method for better performance. |
251 | self.0 |
252 | .glyphs_hor_kerning(first.into(), second.into()) |
253 | .map(f32::from) |
254 | .unwrap_or_default() |
255 | } |
256 | |
257 | fn outline(&self, id: GlyphId) -> Option<Outline> { |
258 | let mut outliner = outliner::OutlineCurveBuilder::default(); |
259 | |
260 | let ttfp::Rect { |
261 | x_min, |
262 | x_max, |
263 | y_min, |
264 | y_max, |
265 | } = self |
266 | .0 |
267 | .as_face_ref() |
268 | .outline_glyph(id.into(), &mut outliner) |
269 | // invalid bounds are treated as having no outline |
270 | .filter(|b| b.x_min < b.x_max && b.y_min < b.y_max)?; |
271 | |
272 | let curves = outliner.take_outline(); |
273 | |
274 | let bounds = Rect { |
275 | min: point(x_min.into(), y_max.into()), |
276 | max: point(x_max.into(), y_min.into()), |
277 | }; |
278 | |
279 | Some(Outline { bounds, curves }) |
280 | } |
281 | |
282 | #[inline] |
283 | fn glyph_count(&self) -> usize { |
284 | self.0.as_face_ref().number_of_glyphs() as _ |
285 | } |
286 | |
287 | fn codepoint_ids(&self) -> crate::CodepointIdIter<'_> { |
288 | let face_ref = self.0.as_face_ref(); |
289 | |
290 | #[cfg(feature = "std" )] |
291 | let mut used_indices = |
292 | std::collections::HashSet::with_capacity(face_ref.number_of_glyphs() as _); |
293 | #[cfg(not(feature = "std" ))] |
294 | let mut used_indices = alloc::collections::BTreeSet::new(); |
295 | |
296 | let inner = Box::new( |
297 | face_ref |
298 | .tables() |
299 | .cmap |
300 | .iter() |
301 | .flat_map(|c| c.subtables) |
302 | .filter(|s| s.is_unicode()) |
303 | .flat_map(move |subtable| { |
304 | let mut pairs = Vec::new(); |
305 | subtable.codepoints(|c| { |
306 | if let Ok(ch) = char::try_from(c) { |
307 | if let Some(idx) = subtable.glyph_index(c).filter(|i| i.0 > 0) { |
308 | if used_indices.insert(idx.0) { |
309 | pairs.push((GlyphId(idx.0), ch)); |
310 | } |
311 | } |
312 | } |
313 | }); |
314 | pairs |
315 | }), |
316 | ); |
317 | |
318 | crate::CodepointIdIter { inner } |
319 | } |
320 | |
321 | fn glyph_raster_image2(&self, id: GlyphId, size: u16) -> Option<v2::GlyphImage> { |
322 | use GlyphImageFormat::*; |
323 | |
324 | let img = self.0.as_face_ref().glyph_raster_image(id.into(), size)?; |
325 | Some(v2::GlyphImage { |
326 | origin: point(img.x.into(), img.y.into()), |
327 | width: img.width, |
328 | height: img.height, |
329 | pixels_per_em: img.pixels_per_em, |
330 | data: img.data, |
331 | format: match img.format { |
332 | ttfp::RasterImageFormat::PNG => Png, |
333 | ttfp::RasterImageFormat::BitmapMono => BitmapMono, |
334 | ttfp::RasterImageFormat::BitmapMonoPacked => BitmapMonoPacked, |
335 | ttfp::RasterImageFormat::BitmapGray2 => BitmapGray2, |
336 | ttfp::RasterImageFormat::BitmapGray2Packed => BitmapGray2Packed, |
337 | ttfp::RasterImageFormat::BitmapGray4 => BitmapGray4, |
338 | ttfp::RasterImageFormat::BitmapGray4Packed => BitmapGray4Packed, |
339 | ttfp::RasterImageFormat::BitmapGray8 => BitmapGray8, |
340 | ttfp::RasterImageFormat::BitmapPremulBgra32 => BitmapPremulBgra32, |
341 | }, |
342 | }) |
343 | } |
344 | } |
345 | }; |
346 | } |
347 | |
348 | impl_font!(FontRef<'_>); |
349 | impl_font!(FontVec); |
350 | |