From 493224e8659a866383ed7f24c749eb83e183e498 Mon Sep 17 00:00:00 2001 From: Henrik Lievonen Date: Sun, 12 Nov 2023 20:28:07 +0200 Subject: [PATCH] feat: Allow getting nominal glyph mapping from font face --- src/font_face.rs | 30 +++++++++++++++++++++++++++--- src/subset.rs | 47 ++++++++++++++++++++++++++++++++++++++--------- 2 files changed, 65 insertions(+), 12 deletions(-) diff --git a/src/font_face.rs b/src/font_face.rs index 9d2c3d5..d509448 100644 --- a/src/font_face.rs +++ b/src/font_face.rs @@ -1,6 +1,6 @@ use std::{ffi::c_char, marker::PhantomData, ops::Deref, ptr::null_mut}; -use crate::{sys, AllocationError, Blob, CharSet, FontFaceExtractionError, Language}; +use crate::{sys, AllocationError, Blob, CharSet, FontFaceExtractionError, Language, Map}; /// A font face is an object that represents a single face from within a font family. /// @@ -52,12 +52,22 @@ impl<'a> FontFace<'a> { /// Collects all of the Unicode characters covered by the font face. #[doc(alias = "hb_face_collect_unicodes")] - pub fn collect_unicodes(&self) -> Result { + pub fn covered_codepoints(&self) -> Result { let set = CharSet::new()?; unsafe { sys::hb_face_collect_unicodes(self.as_raw(), set.as_raw()) }; Ok(set) } + /// Collects the mapping from Unicode characters to nominal glyphs of the face. + #[doc(alias = "hb_face_collect_nominal_glyph_mapping")] + pub fn nominal_glyph_mapping(&self) -> Result, AllocationError> { + let map = Map::new()?; + unsafe { + sys::hb_face_collect_nominal_glyph_mapping(self.as_raw(), map.as_raw(), null_mut()) + }; + Ok(map) + } + /// Preprocesses the face and attaches data that will be needed by the subsetter. /// /// Future subsetting operations can use the precomputed data to speed up the subsetting operation. The @@ -557,7 +567,7 @@ mod tests { #[test] fn loaded_font_contains_correct_number_of_codepoints_and_glyphs() { let font_face = FontFace::new(Blob::from_file(NOTO_SANS).unwrap()).unwrap(); - assert_eq!(font_face.collect_unicodes().unwrap().len(), 3094); + assert_eq!(font_face.covered_codepoints().unwrap().len(), 3094); assert_eq!(font_face.glyph_count(), 4671); } @@ -568,6 +578,20 @@ mod tests { assert_eq!(&*font_face.underlying_blob(), &*blob); } + #[test] + fn nominal_glyph_mapping_works() { + let font_face = FontFace::new(Blob::from_file(NOTO_SANS).unwrap()).unwrap(); + let map = font_face.nominal_glyph_mapping().unwrap(); + assert_eq!(map.get('a').unwrap(), 68); + assert_eq!(map.get('b').unwrap(), 69); + assert_eq!(map.get('c').unwrap(), 70); + assert_eq!(map.get('d').unwrap(), 71); + assert_eq!(map.get('e').unwrap(), 72); + assert_eq!(map.get('f').unwrap(), 73); + assert_eq!(map.get('i').unwrap(), 76); + assert_eq!(map.get('ffi').unwrap(), 1656); + } + #[test] fn convert_into_raw_and_back() { let font_face = FontFace::new(Blob::from_file(NOTO_SANS).unwrap()).unwrap(); diff --git a/src/subset.rs b/src/subset.rs index fcf463c..c75c6c5 100644 --- a/src/subset.rs +++ b/src/subset.rs @@ -371,36 +371,65 @@ mod tests { let orig = FontFace::new(Blob::from_file(NOTO_SANS).unwrap()).unwrap(); let new = subset.subset_font(&orig).unwrap(); assert_eq!( - orig.collect_unicodes().unwrap().len(), - new.collect_unicodes().unwrap().len() + orig.covered_codepoints().unwrap().len(), + new.covered_codepoints().unwrap().len() ); assert_eq!(orig.glyph_count(), new.glyph_count()); } #[test] fn keeping_codepoints_should_keep_ligatures() { + let font = FontFace::new(Blob::from_file(NOTO_SANS).unwrap()).unwrap(); let mut subset = SubsetInput::new().unwrap(); subset.unicode_set().insert('f'); subset.unicode_set().insert('i'); - let font = subset - .subset_font(&FontFace::new(Blob::from_file(NOTO_SANS).unwrap()).unwrap()) - .unwrap(); - assert_eq!(font.collect_unicodes().unwrap().len(), 2); + let font = subset.subset_font(&font).unwrap(); + assert_eq!(font.covered_codepoints().unwrap().len(), 2); assert_eq!(font.glyph_count(), 6); // TODO: Actually check *which* glyphs are included // Currently just assuming [empty], f, i, fi, ffi, and ff } #[test] - #[ignore] fn old_to_new_glyph_mapping() { - todo!() + let font = FontFace::new(Blob::from_file(NOTO_SANS).unwrap()).unwrap(); + let char_to_glyph = font.nominal_glyph_mapping().unwrap(); + + // Map 'a' and 'b' to arbitrary glyphs + let mut subset = SubsetInput::new().unwrap(); + subset + .old_to_new_glyph_mapping() + .insert(char_to_glyph.get('a').unwrap(), 5); + subset + .old_to_new_glyph_mapping() + .insert(char_to_glyph.get('b').unwrap(), 709); + subset.unicode_set().insert('a'); + subset.unicode_set().insert('b'); + + let font = subset.subset_font(&font).unwrap(); + // Most of the glyphs should be empty + assert_eq!(font.glyph_count(), 710); + + let char_to_glyph = font.nominal_glyph_mapping().unwrap(); + // But the specified ones should be what we set + assert_eq!(char_to_glyph.get('a').unwrap(), 5); + assert_eq!(char_to_glyph.get('b').unwrap(), 709); } #[test] - fn convert_into_raw_and_back() { + fn convert_subset_into_raw_and_back() { let subset = SubsetInput::new().unwrap(); let subset_ptr = subset.into_raw(); let subset = unsafe { SubsetInput::from_raw(subset_ptr) }; drop(subset); } + + #[test] + fn convert_plan_into_raw_and_back() { + let font = FontFace::new(Blob::from_file(NOTO_SANS).unwrap()).unwrap(); + let subset = SubsetInput::new().unwrap(); + let plan = subset.plan(&font).unwrap(); + let plan_ptr = plan.into_raw(); + let plan = unsafe { SubsetPlan::from_raw(plan_ptr) }; + drop(plan); + } }