1use ttf_parser::gdef::GlyphClass;
2use ttf_parser::opentype_layout::LayoutTable;
3use ttf_parser::GlyphId;
4
5use crate::buffer::GlyphPropsFlags;
6use crate::ot::{PositioningTable, SubstitutionTable, TableIndex};
7use crate::Variation;
8
9// https://docs.microsoft.com/en-us/typography/opentype/spec/cmap#windows-platform-platform-id--3
10const WINDOWS_SYMBOL_ENCODING: u16 = 0;
11const WINDOWS_UNICODE_BMP_ENCODING: u16 = 1;
12const WINDOWS_UNICODE_FULL_ENCODING: u16 = 10;
13
14// https://docs.microsoft.com/en-us/typography/opentype/spec/name#platform-specific-encoding-and-language-ids-unicode-platform-platform-id--0
15const UNICODE_1_0_ENCODING: u16 = 0;
16const UNICODE_1_1_ENCODING: u16 = 1;
17const UNICODE_ISO_ENCODING: u16 = 2;
18const UNICODE_2_0_BMP_ENCODING: u16 = 3;
19const UNICODE_2_0_FULL_ENCODING: u16 = 4;
20//const UNICODE_VARIATION_ENCODING: u16 = 5;
21const UNICODE_FULL_ENCODING: u16 = 6;
22
23/// A font face handle.
24#[derive(Clone)]
25pub struct Face<'a> {
26 pub(crate) ttfp_face: ttf_parser::Face<'a>,
27 pub(crate) units_per_em: u16,
28 pixels_per_em: Option<(u16, u16)>,
29 pub(crate) points_per_em: Option<f32>,
30 prefered_cmap_encoding_subtable: Option<u16>,
31 pub(crate) gsub: Option<SubstitutionTable<'a>>,
32 pub(crate) gpos: Option<PositioningTable<'a>>,
33}
34
35impl<'a> AsRef<ttf_parser::Face<'a>> for Face<'a> {
36 #[inline]
37 fn as_ref(&self) -> &ttf_parser::Face<'a> {
38 &self.ttfp_face
39 }
40}
41
42impl<'a> AsMut<ttf_parser::Face<'a>> for Face<'a> {
43 #[inline]
44 fn as_mut(&mut self) -> &mut ttf_parser::Face<'a> {
45 &mut self.ttfp_face
46 }
47}
48
49impl<'a> core::ops::Deref for Face<'a> {
50 type Target = ttf_parser::Face<'a>;
51
52 #[inline]
53 fn deref(&self) -> &Self::Target {
54 &self.ttfp_face
55 }
56}
57
58impl<'a> core::ops::DerefMut for Face<'a> {
59 #[inline]
60 fn deref_mut(&mut self) -> &mut Self::Target {
61 &mut self.ttfp_face
62 }
63}
64
65impl<'a> Face<'a> {
66 /// Creates a new `Face` from data.
67 ///
68 /// Data will be referenced, not owned.
69 pub fn from_slice(data: &'a [u8], face_index: u32) -> Option<Self> {
70 let face = ttf_parser::Face::parse(data, face_index).ok()?;
71 Some(Self::from_face(face))
72 }
73
74 /// Creates a new [`Face`] from [`ttf_parser::Face`].
75 ///
76 /// Data will be referenced, not owned.
77 pub fn from_face(face: ttf_parser::Face<'a>) -> Self {
78 Face {
79 units_per_em: face.units_per_em(),
80 pixels_per_em: None,
81 points_per_em: None,
82 prefered_cmap_encoding_subtable: find_best_cmap_subtable(&face),
83 gsub: face.tables().gsub.map(SubstitutionTable::new),
84 gpos: face.tables().gpos.map(PositioningTable::new),
85 ttfp_face: face,
86 }
87 }
88
89 // TODO: remove
90 /// Returns face’s units per EM.
91 #[inline]
92 pub fn units_per_em(&self) -> i32 {
93 self.units_per_em as i32
94 }
95
96 #[inline]
97 pub(crate) fn pixels_per_em(&self) -> Option<(u16, u16)> {
98 self.pixels_per_em
99 }
100
101 /// Sets pixels per EM.
102 ///
103 /// Used during raster glyphs processing and hinting.
104 ///
105 /// `None` by default.
106 #[inline]
107 pub fn set_pixels_per_em(&mut self, ppem: Option<(u16, u16)>) {
108 self.pixels_per_em = ppem;
109 }
110
111 /// Sets point size per EM.
112 ///
113 /// Used for optical-sizing in Apple fonts.
114 ///
115 /// `None` by default.
116 #[inline]
117 pub fn set_points_per_em(&mut self, ptem: Option<f32>) {
118 self.points_per_em = ptem;
119 }
120
121 /// Sets font variations.
122 pub fn set_variations(&mut self, variations: &[Variation]) {
123 for variation in variations {
124 self.set_variation(variation.tag, variation.value);
125 }
126 }
127
128 pub(crate) fn has_glyph(&self, c: u32) -> bool {
129 self.glyph_index(c).is_some()
130 }
131
132 pub(crate) fn glyph_index(&self, c: u32) -> Option<GlyphId> {
133 let subtable_idx = self.prefered_cmap_encoding_subtable?;
134 let subtable = self.tables().cmap?.subtables.get(subtable_idx)?;
135 match subtable.glyph_index(c) {
136 Some(gid) => Some(gid),
137 None => {
138 // Special case for Windows Symbol fonts.
139 // TODO: add tests
140 if subtable.platform_id == ttf_parser::PlatformId::Windows
141 && subtable.encoding_id == WINDOWS_SYMBOL_ENCODING
142 {
143 if c <= 0x00FF {
144 // For symbol-encoded OpenType fonts, we duplicate the
145 // U+F000..F0FF range at U+0000..U+00FF. That's what
146 // Windows seems to do, and that's hinted about at:
147 // https://docs.microsoft.com/en-us/typography/opentype/spec/recom
148 // under "Non-Standard (Symbol) Fonts".
149 return self.glyph_index(0xF000 + c);
150 }
151 }
152
153 None
154 }
155 }
156 }
157
158 pub(crate) fn glyph_h_advance(&self, glyph: GlyphId) -> i32 {
159 self.glyph_advance(glyph, false) as i32
160 }
161
162 pub(crate) fn glyph_v_advance(&self, glyph: GlyphId) -> i32 {
163 -(self.glyph_advance(glyph, true) as i32)
164 }
165
166 fn glyph_advance(&self, glyph: GlyphId, is_vertical: bool) -> u32 {
167 let face = &self.ttfp_face;
168 if face.is_variable()
169 && face.has_non_default_variation_coordinates()
170 && face.tables().hvar.is_none()
171 && face.tables().vvar.is_none()
172 {
173 return match face.glyph_bounding_box(glyph) {
174 Some(bbox) => {
175 (if is_vertical {
176 bbox.y_max + bbox.y_min
177 } else {
178 bbox.x_max + bbox.x_min
179 }) as u32
180 }
181 None => 0,
182 };
183 }
184
185 if is_vertical {
186 if face.tables().vmtx.is_some() {
187 return face.glyph_ver_advance(glyph).unwrap_or(0) as u32;
188 } else {
189 // TODO: Original code calls `h_extents_with_fallback`
190 return (face.ascender() - face.descender()) as u32;
191 }
192 } else if !is_vertical && face.tables().hmtx.is_some() {
193 face.glyph_hor_advance(glyph).unwrap_or(0) as u32
194 } else {
195 face.units_per_em() as u32
196 }
197 }
198
199 pub(crate) fn glyph_h_origin(&self, glyph: GlyphId) -> i32 {
200 self.glyph_h_advance(glyph) / 2
201 }
202
203 pub(crate) fn glyph_v_origin(&self, glyph: GlyphId) -> i32 {
204 match self.ttfp_face.glyph_y_origin(glyph) {
205 Some(y) => i32::from(y),
206 None => {
207 let mut extents = GlyphExtents::default();
208 if self.glyph_extents(glyph, &mut extents) {
209 if self.ttfp_face.tables().vmtx.is_some() {
210 extents.y_bearing + self.glyph_side_bearing(glyph, true)
211 } else {
212 let advance = self.ttfp_face.ascender() - self.ttfp_face.descender();
213 let diff = advance as i32 - -extents.height;
214 return extents.y_bearing + (diff >> 1);
215 }
216 } else {
217 // TODO: Original code calls `h_extents_with_fallback`
218 self.ttfp_face.ascender() as i32
219 }
220 }
221 }
222 }
223
224 pub(crate) fn glyph_side_bearing(&self, glyph: GlyphId, is_vertical: bool) -> i32 {
225 let face = &self.ttfp_face;
226 if face.is_variable() && face.tables().hvar.is_none() && face.tables().vvar.is_none() {
227 return match face.glyph_bounding_box(glyph) {
228 Some(bbox) => (if is_vertical { bbox.x_min } else { bbox.y_min }) as i32,
229 None => 0,
230 };
231 }
232
233 if is_vertical {
234 face.glyph_ver_side_bearing(glyph).unwrap_or(0) as i32
235 } else {
236 face.glyph_hor_side_bearing(glyph).unwrap_or(0) as i32
237 }
238 }
239
240 pub(crate) fn glyph_extents(&self, glyph: GlyphId, glyph_extents: &mut GlyphExtents) -> bool {
241 let pixels_per_em = match self.pixels_per_em {
242 Some(ppem) => ppem.0,
243 None => core::u16::MAX,
244 };
245
246 if let Some(img) = self.ttfp_face.glyph_raster_image(glyph, pixels_per_em) {
247 // HarfBuzz also supports only PNG.
248 if img.format == ttf_parser::RasterImageFormat::PNG {
249 let scale = self.units_per_em as f32 / img.pixels_per_em as f32;
250 glyph_extents.x_bearing = crate::round(f32::from(img.x) * scale) as i32;
251 glyph_extents.y_bearing =
252 crate::round((f32::from(img.y) + f32::from(img.height)) * scale) as i32;
253 glyph_extents.width = crate::round(f32::from(img.width) * scale) as i32;
254 glyph_extents.height = crate::round(-f32::from(img.height) * scale) as i32;
255 return true;
256 }
257 }
258
259 let bbox = self.ttfp_face.glyph_bounding_box(glyph);
260
261 // See https://github.com/RazrFalcon/rustybuzz/pull/98#issuecomment-1948430785
262 if self.ttfp_face.tables().glyf.is_some() && bbox.is_none() {
263 // Empty glyph; zero extents.
264 return true;
265 }
266
267 let Some(bbox) = bbox else {
268 return false;
269 };
270
271 glyph_extents.x_bearing = i32::from(bbox.x_min);
272 glyph_extents.y_bearing = i32::from(bbox.y_max);
273 glyph_extents.width = i32::from(bbox.width());
274 glyph_extents.height = i32::from(bbox.y_min - bbox.y_max);
275
276 return true;
277 }
278
279 pub(crate) fn glyph_name(&self, glyph: GlyphId) -> Option<&str> {
280 self.ttfp_face.glyph_name(glyph)
281 }
282
283 pub(crate) fn glyph_props(&self, glyph: GlyphId) -> u16 {
284 let table = match self.tables().gdef {
285 Some(v) => v,
286 None => return 0,
287 };
288
289 match table.glyph_class(glyph) {
290 Some(GlyphClass::Base) => GlyphPropsFlags::BASE_GLYPH.bits(),
291 Some(GlyphClass::Ligature) => GlyphPropsFlags::LIGATURE.bits(),
292 Some(GlyphClass::Mark) => {
293 let class = table.glyph_mark_attachment_class(glyph);
294 (class << 8) | GlyphPropsFlags::MARK.bits()
295 }
296 _ => 0,
297 }
298 }
299
300 pub(crate) fn layout_table(&self, table_index: TableIndex) -> Option<&LayoutTable<'a>> {
301 match table_index {
302 TableIndex::GSUB => self.gsub.as_ref().map(|table| &table.inner),
303 TableIndex::GPOS => self.gpos.as_ref().map(|table| &table.inner),
304 }
305 }
306
307 pub(crate) fn layout_tables(
308 &self,
309 ) -> impl Iterator<Item = (TableIndex, &LayoutTable<'a>)> + '_ {
310 TableIndex::iter().filter_map(move |idx| self.layout_table(idx).map(|table| (idx, table)))
311 }
312}
313
314#[derive(Clone, Copy, Default)]
315pub struct GlyphExtents {
316 pub x_bearing: i32,
317 pub y_bearing: i32,
318 pub width: i32,
319 pub height: i32,
320}
321
322fn find_best_cmap_subtable(face: &ttf_parser::Face) -> Option<u16> {
323 use ttf_parser::PlatformId;
324
325 // Symbol subtable.
326 // Prefer symbol if available.
327 // https://github.com/harfbuzz/harfbuzz/issues/1918
328 find_cmap_subtableOption(face, PlatformId::Windows, WINDOWS_SYMBOL_ENCODING)
329 // 32-bit subtables:
330 .or_else(|| find_cmap_subtable(face, PlatformId::Windows, WINDOWS_UNICODE_FULL_ENCODING))
331 .or_else(|| find_cmap_subtable(face, PlatformId::Unicode, UNICODE_FULL_ENCODING))
332 .or_else(|| find_cmap_subtable(face, PlatformId::Unicode, UNICODE_2_0_FULL_ENCODING))
333 // 16-bit subtables:
334 .or_else(|| find_cmap_subtable(face, PlatformId::Windows, WINDOWS_UNICODE_BMP_ENCODING))
335 .or_else(|| find_cmap_subtable(face, PlatformId::Unicode, UNICODE_2_0_BMP_ENCODING))
336 .or_else(|| find_cmap_subtable(face, PlatformId::Unicode, UNICODE_ISO_ENCODING))
337 .or_else(|| find_cmap_subtable(face, PlatformId::Unicode, UNICODE_1_1_ENCODING))
338 .or_else(|| find_cmap_subtable(face, PlatformId::Unicode, UNICODE_1_0_ENCODING))
339}
340
341fn find_cmap_subtable(
342 face: &ttf_parser::Face,
343 platform_id: ttf_parser::PlatformId,
344 encoding_id: u16,
345) -> Option<u16> {
346 for (i: usize, subtable: Subtable<'_>) in face.tables().cmap?.subtables.into_iter().enumerate() {
347 if subtable.platform_id == platform_id && subtable.encoding_id == encoding_id {
348 return Some(i as u16);
349 }
350 }
351
352 None
353}
354