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}