rustybuzz/hb/
face.rs

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