rustybuzz/hb/
face.rs

1#[cfg(not(feature = "std"))]
2use core_maths::CoreFloat;
3
4use crate::hb::paint_extents::hb_paint_extents_context_t;
5use ttf_parser::gdef::GlyphClass;
6use ttf_parser::opentype_layout::LayoutTable;
7use ttf_parser::{GlyphId, RgbaColor};
8
9use super::buffer::GlyphPropsFlags;
10use super::ot_layout::TableIndex;
11use super::ot_layout_common::{PositioningTable, SubstitutionTable};
12use crate::Variation;
13
14// https://docs.microsoft.com/en-us/typography/opentype/spec/cmap#windows-platform-platform-id--3
15const WINDOWS_SYMBOL_ENCODING: u16 = 0;
16const WINDOWS_UNICODE_BMP_ENCODING: u16 = 1;
17const WINDOWS_UNICODE_FULL_ENCODING: u16 = 10;
18
19// https://docs.microsoft.com/en-us/typography/opentype/spec/name#platform-specific-encoding-and-language-ids-unicode-platform-platform-id--0
20const UNICODE_1_0_ENCODING: u16 = 0;
21const UNICODE_1_1_ENCODING: u16 = 1;
22const UNICODE_ISO_ENCODING: u16 = 2;
23const UNICODE_2_0_BMP_ENCODING: u16 = 3;
24const UNICODE_2_0_FULL_ENCODING: u16 = 4;
25//const UNICODE_VARIATION_ENCODING: u16 = 5;
26const UNICODE_FULL_ENCODING: u16 = 6;
27
28/// A font face handle.
29#[derive(Clone)]
30pub struct hb_font_t<'a> {
31    pub(crate) ttfp_face: ttf_parser::Face<'a>,
32    pub(crate) units_per_em: u16,
33    pixels_per_em: Option<(u16, u16)>,
34    pub(crate) points_per_em: Option<f32>,
35    prefered_cmap_encoding_subtable: Option<u16>,
36    pub(crate) gsub: Option<SubstitutionTable<'a>>,
37    pub(crate) gpos: Option<PositioningTable<'a>>,
38}
39
40impl<'a> AsRef<ttf_parser::Face<'a>> for hb_font_t<'a> {
41    #[inline]
42    fn as_ref(&self) -> &ttf_parser::Face<'a> {
43        &self.ttfp_face
44    }
45}
46
47impl<'a> AsMut<ttf_parser::Face<'a>> for hb_font_t<'a> {
48    #[inline]
49    fn as_mut(&mut self) -> &mut ttf_parser::Face<'a> {
50        &mut self.ttfp_face
51    }
52}
53
54impl<'a> core::ops::Deref for hb_font_t<'a> {
55    type Target = ttf_parser::Face<'a>;
56
57    #[inline]
58    fn deref(&self) -> &Self::Target {
59        &self.ttfp_face
60    }
61}
62
63impl<'a> core::ops::DerefMut for hb_font_t<'a> {
64    #[inline]
65    fn deref_mut(&mut self) -> &mut Self::Target {
66        &mut self.ttfp_face
67    }
68}
69
70impl<'a> hb_font_t<'a> {
71    /// Creates a new `Face` from data.
72    ///
73    /// Data will be referenced, not owned.
74    pub fn from_slice(data: &'a [u8], face_index: u32) -> Option<Self> {
75        let face = ttf_parser::Face::parse(data, face_index).ok()?;
76        Some(Self::from_face(face))
77    }
78
79    /// Creates a new [`Face`] from [`ttf_parser::Face`].
80    ///
81    /// Data will be referenced, not owned.
82    pub fn from_face(face: ttf_parser::Face<'a>) -> Self {
83        hb_font_t {
84            units_per_em: face.units_per_em(),
85            pixels_per_em: None,
86            points_per_em: None,
87            prefered_cmap_encoding_subtable: find_best_cmap_subtable(&face),
88            gsub: face.tables().gsub.map(SubstitutionTable::new),
89            gpos: face.tables().gpos.map(PositioningTable::new),
90            ttfp_face: face,
91        }
92    }
93
94    // TODO: remove
95    /// Returns face’s units per EM.
96    #[inline]
97    pub fn units_per_em(&self) -> i32 {
98        self.units_per_em as i32
99    }
100
101    #[inline]
102    pub(crate) fn pixels_per_em(&self) -> Option<(u16, u16)> {
103        self.pixels_per_em
104    }
105
106    /// Sets pixels per EM.
107    ///
108    /// Used during raster glyphs processing and hinting.
109    ///
110    /// `None` by default.
111    #[inline]
112    pub fn set_pixels_per_em(&mut self, ppem: Option<(u16, u16)>) {
113        self.pixels_per_em = ppem;
114    }
115
116    /// Sets point size per EM.
117    ///
118    /// Used for optical-sizing in Apple fonts.
119    ///
120    /// `None` by default.
121    #[inline]
122    pub fn set_points_per_em(&mut self, ptem: Option<f32>) {
123        self.points_per_em = ptem;
124    }
125
126    /// Sets font variations.
127    pub fn set_variations(&mut self, variations: &[Variation]) {
128        for variation in variations {
129            self.set_variation(variation.tag, variation.value);
130        }
131    }
132
133    pub(crate) fn has_glyph(&self, c: u32) -> bool {
134        self.get_nominal_glyph(c).is_some()
135    }
136
137    pub(crate) fn get_nominal_glyph(&self, mut c: u32) -> Option<GlyphId> {
138        let subtable_idx = self.prefered_cmap_encoding_subtable?;
139        let subtable = self.tables().cmap?.subtables.get(subtable_idx)?;
140
141        if subtable.platform_id == ttf_parser::PlatformId::Macintosh && c > 0x7F {
142            c = unicode_to_macroman(c);
143        }
144
145        match subtable.glyph_index(c) {
146            Some(gid) => Some(gid),
147            None => {
148                // Special case for Windows Symbol fonts.
149                // TODO: add tests
150                if subtable.platform_id == ttf_parser::PlatformId::Windows
151                    && subtable.encoding_id == WINDOWS_SYMBOL_ENCODING
152                {
153                    if c <= 0x00FF {
154                        // For symbol-encoded OpenType fonts, we duplicate the
155                        // U+F000..F0FF range at U+0000..U+00FF.  That's what
156                        // Windows seems to do, and that's hinted about at:
157                        // https://docs.microsoft.com/en-us/typography/opentype/spec/recom
158                        // under "Non-Standard (Symbol) Fonts".
159                        return self.get_nominal_glyph(0xF000 + c);
160                    }
161                }
162
163                None
164            }
165        }
166    }
167
168    pub(crate) fn glyph_h_advance(&self, glyph: GlyphId) -> i32 {
169        self.glyph_advance(glyph, false) as i32
170    }
171
172    pub(crate) fn glyph_v_advance(&self, glyph: GlyphId) -> i32 {
173        -(self.glyph_advance(glyph, true) as i32)
174    }
175
176    fn glyph_advance(&self, glyph: GlyphId, is_vertical: bool) -> u32 {
177        let face = &self.ttfp_face;
178        if face.is_variable()
179            && face.has_non_default_variation_coordinates()
180            && face.tables().hvar.is_none()
181            && face.tables().vvar.is_none()
182            && face.glyph_phantom_points(glyph).is_none()
183        {
184            return match face.glyph_bounding_box(glyph) {
185                Some(bbox) => {
186                    (if is_vertical {
187                        bbox.y_max + bbox.y_min
188                    } else {
189                        bbox.x_max + bbox.x_min
190                    }) as u32
191                }
192                None => 0,
193            };
194        }
195
196        if is_vertical {
197            if face.tables().vmtx.is_some() {
198                face.glyph_ver_advance(glyph).unwrap_or(0) as u32
199            } else {
200                // TODO: Original code calls `h_extents_with_fallback`
201                (face.ascender() - face.descender()) as u32
202            }
203        } else if !is_vertical && face.tables().hmtx.is_some() {
204            face.glyph_hor_advance(glyph).unwrap_or(0) as u32
205        } else {
206            face.units_per_em() as u32
207        }
208    }
209
210    pub(crate) fn glyph_h_origin(&self, glyph: GlyphId) -> i32 {
211        self.glyph_h_advance(glyph) / 2
212    }
213
214    pub(crate) fn glyph_v_origin(&self, glyph: GlyphId) -> i32 {
215        match self.ttfp_face.glyph_y_origin(glyph) {
216            Some(y) => i32::from(y),
217            None => {
218                let mut extents = hb_glyph_extents_t::default();
219                if self.glyph_extents(glyph, &mut extents) {
220                    if self.ttfp_face.tables().vmtx.is_some() {
221                        extents.y_bearing + self.glyph_side_bearing(glyph, true)
222                    } else {
223                        let advance = self.ttfp_face.ascender() - self.ttfp_face.descender();
224                        let diff = advance as i32 - -extents.height;
225                        extents.y_bearing + (diff >> 1)
226                    }
227                } else {
228                    // TODO: Original code calls `h_extents_with_fallback`
229                    self.ttfp_face.ascender() as i32
230                }
231            }
232        }
233    }
234
235    pub(crate) fn glyph_side_bearing(&self, glyph: GlyphId, is_vertical: bool) -> i32 {
236        let face = &self.ttfp_face;
237        if face.is_variable() && face.tables().hvar.is_none() && face.tables().vvar.is_none() {
238            return match face.glyph_bounding_box(glyph) {
239                Some(bbox) => (if is_vertical { bbox.x_min } else { bbox.y_min }) as i32,
240                None => 0,
241            };
242        }
243
244        if is_vertical {
245            face.glyph_ver_side_bearing(glyph).unwrap_or(0) as i32
246        } else {
247            face.glyph_hor_side_bearing(glyph).unwrap_or(0) as i32
248        }
249    }
250
251    pub(crate) fn glyph_extents(
252        &self,
253        glyph: GlyphId,
254        glyph_extents: &mut hb_glyph_extents_t,
255    ) -> bool {
256        let pixels_per_em = self.pixels_per_em.map_or(u16::MAX, |ppem| ppem.0);
257
258        if let Some(img) = self.ttfp_face.glyph_raster_image(glyph, pixels_per_em) {
259            // HarfBuzz also supports only PNG.
260            if img.format == ttf_parser::RasterImageFormat::PNG {
261                let scale = self.units_per_em as f32 / img.pixels_per_em as f32;
262                glyph_extents.x_bearing = (f32::from(img.x) * scale).round() as i32;
263                glyph_extents.y_bearing =
264                    ((f32::from(img.y) + f32::from(img.height)) * scale).round() as i32;
265                glyph_extents.width = (f32::from(img.width) * scale).round() as i32;
266                glyph_extents.height = (-f32::from(img.height) * scale).round() as i32;
267                return true;
268            }
269        // TODO: Add tests for this. We should use all glyphs from
270        // https://github.com/googlefonts/color-fonts/blob/main/fonts/test_glyphs-glyf_colr_1_no_cliplist.ttf
271        // and test their output against harfbuzz.
272        } else if let Some(colr) = self.ttfp_face.tables().colr {
273            if colr.is_simple() {
274                return false;
275            }
276
277            if let Some(clip_box) = colr.clip_box(glyph, self.variation_coordinates()) {
278                // Floor
279                glyph_extents.x_bearing = (clip_box.x_min).round() as i32;
280                glyph_extents.y_bearing = (clip_box.y_max).round() as i32;
281                glyph_extents.width = (clip_box.x_max - clip_box.x_min).round() as i32;
282                glyph_extents.height = (clip_box.y_min - clip_box.y_max).round() as i32;
283                return true;
284            }
285
286            let mut extents_data = hb_paint_extents_context_t::new(&self.ttfp_face);
287            let ret = colr
288                .paint(
289                    glyph,
290                    0,
291                    &mut extents_data,
292                    self.variation_coordinates(),
293                    RgbaColor::new(0, 0, 0, 0),
294                )
295                .is_some();
296
297            let e = extents_data.get_extents();
298            if e.is_void() {
299                glyph_extents.x_bearing = 0;
300                glyph_extents.y_bearing = 0;
301                glyph_extents.width = 0;
302                glyph_extents.height = 0;
303            } else {
304                glyph_extents.x_bearing = e.x_min as i32;
305                glyph_extents.y_bearing = e.y_max as i32;
306                glyph_extents.width = (e.x_max - e.x_min) as i32;
307                glyph_extents.height = (e.y_min - e.y_max) as i32;
308            }
309
310            return ret;
311        }
312
313        let mut bbox = None;
314
315        if let Some(glyf) = self.ttfp_face.tables().glyf {
316            bbox = glyf.bbox(glyph);
317        }
318
319        // See https://github.com/harfbuzz/rustybuzz/pull/98#issuecomment-1948430785
320        if self.ttfp_face.tables().glyf.is_some() && bbox.is_none() {
321            // Empty glyph; zero extents.
322            return true;
323        }
324
325        let Some(bbox) = bbox else {
326            return false;
327        };
328
329        glyph_extents.x_bearing = i32::from(bbox.x_min);
330        glyph_extents.y_bearing = i32::from(bbox.y_max);
331        glyph_extents.width = i32::from(bbox.width());
332        glyph_extents.height = i32::from(bbox.y_min - bbox.y_max);
333
334        true
335    }
336
337    pub(crate) fn glyph_name(&self, glyph: GlyphId) -> Option<&str> {
338        self.ttfp_face.glyph_name(glyph)
339    }
340
341    pub(crate) fn glyph_props(&self, glyph: GlyphId) -> u16 {
342        let table = match self.tables().gdef {
343            Some(v) => v,
344            None => return 0,
345        };
346
347        match table.glyph_class(glyph) {
348            Some(GlyphClass::Base) => GlyphPropsFlags::BASE_GLYPH.bits(),
349            Some(GlyphClass::Ligature) => GlyphPropsFlags::LIGATURE.bits(),
350            Some(GlyphClass::Mark) => {
351                let class = table.glyph_mark_attachment_class(glyph);
352                (class << 8) | GlyphPropsFlags::MARK.bits()
353            }
354            _ => 0,
355        }
356    }
357
358    pub(crate) fn layout_table(&self, table_index: TableIndex) -> Option<&LayoutTable<'a>> {
359        match table_index {
360            TableIndex::GSUB => self.gsub.as_ref().map(|table| &table.inner),
361            TableIndex::GPOS => self.gpos.as_ref().map(|table| &table.inner),
362        }
363    }
364
365    pub(crate) fn layout_tables(
366        &self,
367    ) -> impl Iterator<Item = (TableIndex, &LayoutTable<'a>)> + '_ {
368        TableIndex::iter().filter_map(move |idx| self.layout_table(idx).map(|table| (idx, table)))
369    }
370}
371
372#[derive(Clone, Copy, Default)]
373#[repr(C)]
374pub struct hb_glyph_extents_t {
375    pub x_bearing: i32,
376    pub y_bearing: i32,
377    pub width: i32,
378    pub height: i32,
379}
380
381unsafe impl bytemuck::Zeroable for hb_glyph_extents_t {}
382unsafe impl bytemuck::Pod for hb_glyph_extents_t {}
383
384fn find_best_cmap_subtable(face: &ttf_parser::Face) -> Option<u16> {
385    use ttf_parser::PlatformId;
386
387    // Symbol subtable.
388    // Prefer symbol if available.
389    // https://github.com/harfbuzz/harfbuzz/issues/1918
390    find_cmap_subtable(face, PlatformId::Windows, WINDOWS_SYMBOL_ENCODING)
391        // 32-bit subtables:
392        .or_else(|| find_cmap_subtable(face, PlatformId::Windows, WINDOWS_UNICODE_FULL_ENCODING))
393        .or_else(|| find_cmap_subtable(face, PlatformId::Unicode, UNICODE_FULL_ENCODING))
394        .or_else(|| find_cmap_subtable(face, PlatformId::Unicode, UNICODE_2_0_FULL_ENCODING))
395        // 16-bit subtables:
396        .or_else(|| find_cmap_subtable(face, PlatformId::Windows, WINDOWS_UNICODE_BMP_ENCODING))
397        .or_else(|| find_cmap_subtable(face, PlatformId::Unicode, UNICODE_2_0_BMP_ENCODING))
398        .or_else(|| find_cmap_subtable(face, PlatformId::Unicode, UNICODE_ISO_ENCODING))
399        .or_else(|| find_cmap_subtable(face, PlatformId::Unicode, UNICODE_1_1_ENCODING))
400        .or_else(|| find_cmap_subtable(face, PlatformId::Unicode, UNICODE_1_0_ENCODING))
401        // MacRoman subtable:
402        .or_else(|| find_cmap_subtable(face, PlatformId::Macintosh, 0))
403}
404
405fn find_cmap_subtable(
406    face: &ttf_parser::Face,
407    platform_id: ttf_parser::PlatformId,
408    encoding_id: u16,
409) -> Option<u16> {
410    for (i, subtable) in face.tables().cmap?.subtables.into_iter().enumerate() {
411        if subtable.platform_id == platform_id && subtable.encoding_id == encoding_id {
412            return Some(i as u16);
413        }
414    }
415
416    None
417}
418
419#[rustfmt::skip]
420static UNICODE_TO_MACROMAN: &[u16] = &[
421    0x00C4, 0x00C5, 0x00C7, 0x00C9, 0x00D1, 0x00D6, 0x00DC, 0x00E1,
422    0x00E0, 0x00E2, 0x00E4, 0x00E3, 0x00E5, 0x00E7, 0x00E9, 0x00E8,
423    0x00EA, 0x00EB, 0x00ED, 0x00EC, 0x00EE, 0x00EF, 0x00F1, 0x00F3,
424    0x00F2, 0x00F4, 0x00F6, 0x00F5, 0x00FA, 0x00F9, 0x00FB, 0x00FC,
425    0x2020, 0x00B0, 0x00A2, 0x00A3, 0x00A7, 0x2022, 0x00B6, 0x00DF,
426    0x00AE, 0x00A9, 0x2122, 0x00B4, 0x00A8, 0x2260, 0x00C6, 0x00D8,
427    0x221E, 0x00B1, 0x2264, 0x2265, 0x00A5, 0x00B5, 0x2202, 0x2211,
428    0x220F, 0x03C0, 0x222B, 0x00AA, 0x00BA, 0x03A9, 0x00E6, 0x00F8,
429    0x00BF, 0x00A1, 0x00AC, 0x221A, 0x0192, 0x2248, 0x2206, 0x00AB,
430    0x00BB, 0x2026, 0x00A0, 0x00C0, 0x00C3, 0x00D5, 0x0152, 0x0153,
431    0x2013, 0x2014, 0x201C, 0x201D, 0x2018, 0x2019, 0x00F7, 0x25CA,
432    0x00FF, 0x0178, 0x2044, 0x20AC, 0x2039, 0x203A, 0xFB01, 0xFB02,
433    0x2021, 0x00B7, 0x201A, 0x201E, 0x2030, 0x00C2, 0x00CA, 0x00C1,
434    0x00CB, 0x00C8, 0x00CD, 0x00CE, 0x00CF, 0x00CC, 0x00D3, 0x00D4,
435    0xF8FF, 0x00D2, 0x00DA, 0x00DB, 0x00D9, 0x0131, 0x02C6, 0x02DC,
436    0x00AF, 0x02D8, 0x02D9, 0x02DA, 0x00B8, 0x02DD, 0x02DB, 0x02C7,
437];
438
439fn unicode_to_macroman(c: u32) -> u32 {
440    let u = c as u16;
441    let Some(index) = UNICODE_TO_MACROMAN.iter().position(|m| *m == u) else {
442        return 0;
443    };
444    (0x80 + index) as u32
445}