hb_subset/
font_face.rs

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