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}