hb_subset/
font_face.rs

1use std::{ffi::c_char, marker::PhantomData, ops::Deref, ptr::null_mut};
2
3use crate::{
4    map::Map, set::CharSet, sys, AllocationError, Blob, FontFaceExtractionError, Language,
5};
6
7/// A font face is an object that represents a single face from within a font family.
8///
9/// More precisely, a font face represents a single face in a binary font file. Font faces are typically built from a
10/// binary blob and a face index. Font faces are used to create fonts.
11#[repr(transparent)]
12pub struct FontFace<'a>(*mut sys::hb_face_t, PhantomData<Blob<'a>>);
13
14impl<'a> FontFace<'a> {
15    /// Constructs a new face object from the specified blob.
16    ///
17    /// This defaults to taking the first face in the blob. If you need to specify which font face to load, you can use
18    /// [`new_with_index`] instead.
19    ///
20    /// [`new_with_index`]: Self::new_with_index
21    #[doc(alias = "hb_face_create")]
22    pub fn new(blob: Blob<'a>) -> Result<Self, FontFaceExtractionError> {
23        Self::new_with_index(blob, 0)
24    }
25
26    /// Constructs a new face object from the specified blob and a face index into that blob.
27    ///
28    /// The face index is used for blobs of file formats such as TTC and DFont that can contain more than one face. Face
29    /// indices within such collections are zero-based.
30    #[doc(alias = "hb_face_create")]
31    pub fn new_with_index(blob: Blob<'a>, index: u32) -> Result<Self, FontFaceExtractionError> {
32        let face = unsafe { sys::hb_face_create(blob.as_raw(), index) };
33        if face.is_null() {
34            return Err(FontFaceExtractionError);
35        }
36        Ok(Self(face, PhantomData))
37    }
38
39    /// Gets the blob underlying this font face.
40    ///
41    /// Useful when you want to output the font face to a file.
42    ///
43    /// Returns an empty blob if referencing face data is not possible.
44    #[doc(alias = "hb_face_reference_blob")]
45    pub fn underlying_blob(&self) -> Blob<'_> {
46        unsafe { Blob::from_raw(sys::hb_face_reference_blob(self.as_raw())) }
47    }
48
49    /// Fetches the glyph-count value of the specified face object.
50    #[doc(alias = "hb_face_get_glyph_count")]
51    pub fn glyph_count(&self) -> usize {
52        (unsafe { sys::hb_face_get_glyph_count(self.as_raw()) }) as usize
53    }
54
55    /// Collects all of the Unicode characters covered by the font face.
56    #[doc(alias = "hb_face_collect_unicodes")]
57    pub fn covered_codepoints(&self) -> Result<CharSet, AllocationError> {
58        let set = CharSet::new()?;
59        unsafe { sys::hb_face_collect_unicodes(self.as_raw(), set.as_raw()) };
60        Ok(set)
61    }
62
63    /// Collects the mapping from Unicode characters to nominal glyphs of the face.
64    #[doc(alias = "hb_face_collect_nominal_glyph_mapping")]
65    pub fn nominal_glyph_mapping(&self) -> Result<Map<'static, char, u32>, AllocationError> {
66        let map = Map::new()?;
67        unsafe {
68            sys::hb_face_collect_nominal_glyph_mapping(self.as_raw(), map.as_raw(), null_mut())
69        };
70        Ok(map)
71    }
72
73    /// Preprocesses the face and attaches data that will be needed by the subsetter.
74    ///
75    /// Future subsetting operations can use the precomputed data to speed up the subsetting operation. The
76    /// preprocessing operation may take longer than the time it takes to produce a subset from the source font. Thus
77    /// the main performance gains are made when a preprocessed face is reused for multiple subsetting operations.
78    ///
79    /// # Example
80    /// ```
81    /// # use hb_subset::*;
82    /// # fn subsets() -> impl IntoIterator<Item = SubsetInput> { [SubsetInput::new().unwrap()] }
83    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
84    /// let font = FontFace::new(Blob::from_file("tests/fonts/NotoSans.ttf")?)?;
85    /// let processed = font.preprocess_for_subsetting();
86    /// for subset in subsets() {
87    ///     subset.subset_font(&processed)?;
88    /// }
89    /// # Ok(())
90    /// # }
91    /// ```
92    pub fn preprocess_for_subsetting(&self) -> PreprocessedFontFace<'a> {
93        PreprocessedFontFace(unsafe { sys::hb_subset_preprocess(self.0) }, PhantomData)
94    }
95}
96
97impl<'a> FontFace<'a> {
98    /// Converts the font face into raw [`sys::hb_face_t`] pointer.
99    ///
100    /// This method transfers the ownership of the font face to the caller. It is up to the caller to call
101    /// [`sys::hb_face_destroy`] to free the pointer, or call [`Self::from_raw`] to convert it back into [`FontFace`].
102    pub fn into_raw(self) -> *mut sys::hb_face_t {
103        let ptr = self.0;
104        std::mem::forget(self);
105        ptr
106    }
107
108    /// Exposes the raw inner pointer without transferring the ownership.
109    ///
110    /// Unlike [`Self::into_raw`], this method does not transfer the ownership of the pointer to the caller.
111    pub fn as_raw(&self) -> *mut sys::hb_face_t {
112        self.0
113    }
114
115    /// Constructs a font face from raw [`sys::hb_face_t`] pointer.
116    ///
117    /// # Safety
118    /// The given `font_face` pointer must either be constructed by some Harfbuzz function, or be returned from
119    /// [`Self::into_raw`].
120    pub unsafe fn from_raw(font_face: *mut sys::hb_face_t) -> Self {
121        Self(font_face, PhantomData)
122    }
123}
124
125/// Functions for fetching name strings from OpenType fonts.
126///
127/// See [OpenType spec](https://learn.microsoft.com/en-us/typography/opentype/spec/name#name-ids) for more information
128/// on these strings.
129impl<'a> FontFace<'a> {
130    /// Gets value from OpenType name table for given language.
131    ///
132    /// Instead of using this method directly, consider using one of the convenience methods for getting the correct
133    /// string directly.
134    ///
135    /// If `language` is `null()`, English is assumed.
136    #[doc(alias = "hb_ot_name_get_utf8")]
137    #[doc(alias = "hb_ot_name_get_utf16")]
138    #[doc(alias = "hb_ot_name_get_utf32")]
139    pub fn ot_name(&self, name: impl Into<sys::hb_ot_name_id_t>, language: Language) -> String {
140        let name = name.into();
141        let mut len = unsafe {
142            sys::hb_ot_name_get_utf8(self.as_raw(), name, language.as_raw(), null_mut(), null_mut())
143        };
144        len += 1; // Reserve space for NUL termination
145        let mut buf = vec![0; len as usize];
146        let full_len = unsafe {
147            sys::hb_ot_name_get_utf8(
148                self.as_raw(),
149                name,
150                language.as_raw(),
151                &mut len as *mut u32,
152                buf.as_mut_ptr() as *mut c_char,
153            )
154        };
155        assert!(len <= full_len);
156        buf.truncate(len as usize);
157
158        String::from_utf8(buf).expect("Output is promised to be valid UTF-8")
159    }
160
161    /// Gets copyright notice.
162    ///
163    /// # Example
164    /// ```
165    /// # use hb_subset::*;
166    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
167    /// let font = FontFace::new(Blob::from_file("tests/fonts/NotoSans.ttf")?)?;
168    /// assert_eq!(font.copyright(), "Copyright 2022 The Noto Project Authors (https://github.com/notofonts/latin-greek-cyrillic)");
169    /// # Ok(())
170    /// # }
171    /// ```
172    #[doc(alias = "HB_OT_NAME_ID_COPYRIGHT")]
173    pub fn copyright(&self) -> String {
174        self.ot_name(sys::hb_ot_name_id_predefined_t::COPYRIGHT, Language::default())
175    }
176
177    /// Gets font family name.
178    ///
179    /// # Example
180    /// ```
181    /// # use hb_subset::*;
182    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
183    /// let font = FontFace::new(Blob::from_file("tests/fonts/NotoSans.ttf")?)?;
184    /// assert_eq!(font.font_family(), "Noto Sans");
185    /// # Ok(())
186    /// # }
187    /// ```
188    #[doc(alias = "HB_OT_NAME_ID_FONT_FAMILY")]
189    pub fn font_family(&self) -> String {
190        self.ot_name(sys::hb_ot_name_id_predefined_t::FONT_FAMILY, Language::default())
191    }
192
193    /// Gets font subfamily name.
194    ///
195    /// # Example
196    /// ```
197    /// # use hb_subset::*;
198    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
199    /// let font = FontFace::new(Blob::from_file("tests/fonts/NotoSans.ttf")?)?;
200    /// assert_eq!(font.font_subfamily(), "Regular");
201    /// # Ok(())
202    /// # }
203    /// ```
204    #[doc(alias = "HB_OT_NAME_ID_FONT_SUBFAMILY")]
205    pub fn font_subfamily(&self) -> String {
206        self.ot_name(sys::hb_ot_name_id_predefined_t::FONT_SUBFAMILY, Language::default())
207    }
208
209    /// Gets unique font identifier.
210    ///
211    /// # Example
212    /// ```
213    /// # use hb_subset::*;
214    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
215    /// let font = FontFace::new(Blob::from_file("tests/fonts/NotoSans.ttf")?)?;
216    /// assert_eq!(font.unique_id(), "2.013;GOOG;NotoSans-Regular");
217    /// # Ok(())
218    /// # }
219    /// ```
220    #[doc(alias = "HB_OT_NAME_ID_UNIQUE_ID")]
221    pub fn unique_id(&self) -> String {
222        self.ot_name(sys::hb_ot_name_id_predefined_t::UNIQUE_ID, Language::default())
223    }
224
225    /// Gets full font name that reflects all family and relevant subfamily descriptors.
226    ///
227    /// # Example
228    /// ```
229    /// # use hb_subset::*;
230    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
231    /// let font = FontFace::new(Blob::from_file("tests/fonts/NotoSans.ttf")?)?;
232    /// assert_eq!(font.full_name(), "Noto Sans Regular");
233    /// # Ok(())
234    /// # }
235    /// ```
236    #[doc(alias = "HB_OT_NAME_ID_FULL_NAME")]
237    pub fn full_name(&self) -> String {
238        self.ot_name(sys::hb_ot_name_id_predefined_t::FULL_NAME, Language::default())
239    }
240
241    /// Gets version string.
242    ///
243    /// # Example
244    /// ```
245    /// # use hb_subset::*;
246    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
247    /// let font = FontFace::new(Blob::from_file("tests/fonts/NotoSans.ttf")?)?;
248    /// assert_eq!(font.version_string(), "Version 2.013; ttfautohint (v1.8.4.7-5d5b)");
249    /// # Ok(())
250    /// # }
251    /// ```
252    #[doc(alias = "HB_OT_NAME_ID_VERSION_STRING")]
253    pub fn version_string(&self) -> String {
254        self.ot_name(sys::hb_ot_name_id_predefined_t::VERSION_STRING, Language::default())
255    }
256
257    /// Gets PostScript name for the font.
258    ///
259    /// # Example
260    /// ```
261    /// # use hb_subset::*;
262    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
263    /// let font = FontFace::new(Blob::from_file("tests/fonts/NotoSans.ttf")?)?;
264    /// assert_eq!(font.postscript_name(), "NotoSans-Regular");
265    /// # Ok(())
266    /// # }
267    /// ```
268    #[doc(alias = "HB_OT_NAME_ID_POSTSCRIPT_NAME")]
269    pub fn postscript_name(&self) -> String {
270        self.ot_name(sys::hb_ot_name_id_predefined_t::POSTSCRIPT_NAME, Language::default())
271    }
272
273    /// Gets trademark information.
274    ///
275    /// # Example
276    /// ```
277    /// # use hb_subset::*;
278    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
279    /// let font = FontFace::new(Blob::from_file("tests/fonts/NotoSans.ttf")?)?;
280    /// assert_eq!(font.trademark(), "Noto is a trademark of Google LLC.");
281    /// # Ok(())
282    /// # }
283    /// ```
284    #[doc(alias = "HB_OT_NAME_ID_TRADEMARK")]
285    pub fn trademark(&self) -> String {
286        self.ot_name(sys::hb_ot_name_id_predefined_t::TRADEMARK, Language::default())
287    }
288
289    /// Gets manufacturer name.
290    ///
291    /// # Example
292    /// ```
293    /// # use hb_subset::*;
294    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
295    /// let font = FontFace::new(Blob::from_file("tests/fonts/NotoSans.ttf")?)?;
296    /// assert_eq!(font.manufacturer(), "Monotype Imaging Inc.");
297    /// # Ok(())
298    /// # }
299    /// ```
300    #[doc(alias = "HB_OT_NAME_ID_MANUFACTURER")]
301    pub fn manufacturer(&self) -> String {
302        self.ot_name(sys::hb_ot_name_id_predefined_t::MANUFACTURER, Language::default())
303    }
304
305    /// Gets designer name.
306    ///
307    /// # Example
308    /// ```
309    /// # use hb_subset::*;
310    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
311    /// let font = FontFace::new(Blob::from_file("tests/fonts/NotoSans.ttf")?)?;
312    /// assert_eq!(font.designer(), "Monotype Design Team");
313    /// # Ok(())
314    /// # }
315    /// ```
316    #[doc(alias = "HB_OT_NAME_ID_DESIGNER")]
317    pub fn designer(&self) -> String {
318        self.ot_name(sys::hb_ot_name_id_predefined_t::DESIGNER, Language::default())
319    }
320
321    /// Gets description.
322    ///
323    /// # Example
324    /// ```
325    /// # use hb_subset::*;
326    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
327    /// let font = FontFace::new(Blob::from_file("tests/fonts/NotoSans.ttf")?)?;
328    /// assert_eq!(font.description(), "Designed by Monotype design team, Irene Vlachou.");
329    /// # Ok(())
330    /// # }
331    /// ```
332    #[doc(alias = "HB_OT_NAME_ID_DESCRIPTION")]
333    pub fn description(&self) -> String {
334        self.ot_name(sys::hb_ot_name_id_predefined_t::DESCRIPTION, Language::default())
335    }
336
337    /// Gets URL of font vendor.
338    ///
339    /// # Example
340    /// ```
341    /// # use hb_subset::*;
342    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
343    /// let font = FontFace::new(Blob::from_file("tests/fonts/NotoSans.ttf")?)?;
344    /// assert_eq!(font.vendor_url(), "http://www.google.com/get/noto/");
345    /// # Ok(())
346    /// # }
347    /// ```
348    #[doc(alias = "HB_OT_NAME_ID_VENDOR_URL")]
349    pub fn vendor_url(&self) -> String {
350        self.ot_name(sys::hb_ot_name_id_predefined_t::VENDOR_URL, Language::default())
351    }
352
353    /// Gets URL of typeface designer.
354    ///
355    /// # Example
356    /// ```
357    /// # use hb_subset::*;
358    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
359    /// let font = FontFace::new(Blob::from_file("tests/fonts/NotoSans.ttf")?)?;
360    /// assert_eq!(font.designer_url(), "http://www.monotype.com/studio");
361    /// # Ok(())
362    /// # }
363    /// ```
364    #[doc(alias = "HB_OT_NAME_ID_DESIGNER_URL")]
365    pub fn designer_url(&self) -> String {
366        self.ot_name(sys::hb_ot_name_id_predefined_t::DESIGNER_URL, Language::default())
367    }
368
369    /// Gets license description.
370    ///
371    /// # Example
372    /// ```
373    /// # use hb_subset::*;
374    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
375    /// let font = FontFace::new(Blob::from_file("tests/fonts/NotoSans.ttf")?)?;
376    /// assert_eq!(font.license(), "This Font Software is licensed under the SIL Open Font License, Version 1.1. This license is available with a FAQ at: https://scripts.sil.org/OFL");
377    /// # Ok(())
378    /// # }
379    /// ```
380    #[doc(alias = "HB_OT_NAME_ID_LICENSE")]
381    pub fn license(&self) -> String {
382        self.ot_name(sys::hb_ot_name_id_predefined_t::LICENSE, Language::default())
383    }
384
385    /// Gets URL where additional licensing information can be found.
386    ///
387    /// # Example
388    /// ```
389    /// # use hb_subset::*;
390    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
391    /// let font = FontFace::new(Blob::from_file("tests/fonts/NotoSans.ttf")?)?;
392    /// assert_eq!(font.license_url(), "https://scripts.sil.org/OFL");
393    /// # Ok(())
394    /// # }
395    /// ```
396    #[doc(alias = "HB_OT_NAME_ID_LICENSE_URL")]
397    pub fn license_url(&self) -> String {
398        self.ot_name(sys::hb_ot_name_id_predefined_t::LICENSE_URL, Language::default())
399    }
400
401    /// Gets typographic family name.
402    #[doc(alias = "HB_OT_NAME_ID_TYPOGRAPHIC_FAMILY")]
403    pub fn typographic_family(&self) -> String {
404        self.ot_name(sys::hb_ot_name_id_predefined_t::TYPOGRAPHIC_FAMILY, Language::default())
405    }
406
407    /// Gets typographic subfamily name.
408    #[doc(alias = "HB_OT_NAME_ID_TYPOGRAPHIC_SUBFAMILY")]
409    pub fn typographic_subfamily(&self) -> String {
410        self.ot_name(sys::hb_ot_name_id_predefined_t::TYPOGRAPHIC_SUBFAMILY, Language::default())
411    }
412
413    /// Gets compatible full name for MacOS.
414    #[doc(alias = "HB_OT_NAME_ID_MAC_FULL_NAME")]
415    pub fn mac_full_name(&self) -> String {
416        self.ot_name(sys::hb_ot_name_id_predefined_t::MAC_FULL_NAME, Language::default())
417    }
418
419    /// Gets sample text.
420    #[doc(alias = "HB_OT_NAME_ID_SAMPLE_TEXT")]
421    pub fn sample_text(&self) -> String {
422        self.ot_name(sys::hb_ot_name_id_predefined_t::SAMPLE_TEXT, Language::default())
423    }
424
425    /// Gets PostScript CID findfont name.
426    #[doc(alias = "HB_OT_NAME_ID_CID_FINDFONT_NAME")]
427    pub fn cid_findfont_name(&self) -> String {
428        self.ot_name(sys::hb_ot_name_id_predefined_t::CID_FINDFONT_NAME, Language::default())
429    }
430
431    /// Gets WWS family Name.
432    #[doc(alias = "HB_OT_NAME_ID_WWS_FAMILY")]
433    pub fn wws_family(&self) -> String {
434        self.ot_name(sys::hb_ot_name_id_predefined_t::WWS_FAMILY, Language::default())
435    }
436
437    /// Gets WWS subfamily Name.
438    #[doc(alias = "HB_OT_NAME_ID_WWS_SUBFAMILY")]
439    pub fn wws_subfamily(&self) -> String {
440        self.ot_name(sys::hb_ot_name_id_predefined_t::WWS_SUBFAMILY, Language::default())
441    }
442
443    /// Gets light background palette.
444    #[doc(alias = "HB_OT_NAME_ID_LIGHT_BACKGROUND")]
445    pub fn light_background(&self) -> String {
446        self.ot_name(sys::hb_ot_name_id_predefined_t::LIGHT_BACKGROUND, Language::default())
447    }
448
449    /// Gets dark background palette.
450    #[doc(alias = "HB_OT_NAME_ID_DARK_BACKGROUND")]
451    pub fn dark_background(&self) -> String {
452        self.ot_name(sys::hb_ot_name_id_predefined_t::DARK_BACKGROUND, Language::default())
453    }
454
455    /// Gets variations PostScript name prefix.
456    #[doc(alias = "HB_OT_NAME_ID_VARIATIONS_PS_PREFIX")]
457    pub fn variations_ps_prefix(&self) -> String {
458        self.ot_name(sys::hb_ot_name_id_predefined_t::VARIATIONS_PS_PREFIX, Language::default())
459    }
460}
461
462impl<'a> Drop for FontFace<'a> {
463    #[doc(alias = "hb_face_destroy")]
464    fn drop(&mut self) {
465        unsafe { sys::hb_face_destroy(self.0) }
466    }
467}
468
469/// Font face that has been preprocessed for subsetting.
470///
471/// See [FontFace::preprocess_for_subsetting()].
472#[repr(transparent)]
473pub struct PreprocessedFontFace<'a>(*mut sys::hb_face_t, PhantomData<Blob<'a>>);
474
475impl<'a> Deref for PreprocessedFontFace<'a> {
476    type Target = FontFace<'a>;
477
478    fn deref(&self) -> &Self::Target {
479        unsafe { std::mem::transmute(self) }
480    }
481}
482
483impl<'a> Drop for PreprocessedFontFace<'a> {
484    #[doc(alias = "hb_face_destroy")]
485    fn drop(&mut self) {
486        unsafe { sys::hb_face_destroy(self.0) }
487    }
488}
489
490#[cfg(test)]
491mod tests {
492    use super::*;
493    use crate::tests::NOTO_SANS;
494
495    #[test]
496    fn loaded_font_contains_correct_number_of_codepoints_and_glyphs() {
497        let font_face = FontFace::new(Blob::from_file(NOTO_SANS).unwrap()).unwrap();
498        assert_eq!(font_face.covered_codepoints().unwrap().len(), 3094);
499        assert_eq!(font_face.glyph_count(), 4671);
500    }
501
502    #[test]
503    fn underlying_blob_works() {
504        let blob = Blob::from_file(NOTO_SANS).unwrap();
505        let font_face = FontFace::new(blob.clone()).unwrap();
506        assert_eq!(&*font_face.underlying_blob(), &*blob);
507    }
508
509    #[test]
510    fn nominal_glyph_mapping_works() {
511        let font_face = FontFace::new(Blob::from_file(NOTO_SANS).unwrap()).unwrap();
512        let map = font_face.nominal_glyph_mapping().unwrap();
513        assert_eq!(map.get('a').unwrap(), 68);
514        assert_eq!(map.get('b').unwrap(), 69);
515        assert_eq!(map.get('c').unwrap(), 70);
516        assert_eq!(map.get('d').unwrap(), 71);
517        assert_eq!(map.get('e').unwrap(), 72);
518        assert_eq!(map.get('f').unwrap(), 73);
519        assert_eq!(map.get('i').unwrap(), 76);
520        assert_eq!(map.get('ffi').unwrap(), 1656);
521    }
522
523    #[test]
524    fn convert_into_raw_and_back() {
525        let font_face = FontFace::new(Blob::from_file(NOTO_SANS).unwrap()).unwrap();
526        let font_face_ptr = font_face.into_raw();
527        let font_face = unsafe { FontFace::from_raw(font_face_ptr) };
528        drop(font_face);
529    }
530}