hb_subset/
subset.rs

1use std::marker::PhantomData;
2
3use crate::{sys, AllocationError, CharSet, FontFace, Map, Set, SubsettingError, TagSet, U32Set};
4
5mod flags;
6
7pub use flags::*;
8
9/// A description of how a font should be subset.
10///
11/// Subsetting reduces the codepoint coverage of font files and removes all data that is no longer needed. A subset
12/// input describes the desired subset. The input is provided along with a font to the subsetting operation. Output is a
13/// new font file containing only the data specified in the input.
14///
15/// Currently most outline and bitmap tables are supported: glyf, CFF, CFF2, sbix, COLR, and CBDT/CBLC. This also
16/// includes fonts with variable outlines via OpenType variations. Notably EBDT/EBLC and SVG are not supported. Layout
17/// subsetting is supported only for OpenType Layout tables (GSUB, GPOS, GDEF). Notably subsetting of graphite or AAT
18/// tables is not yet supported.
19///
20/// Fonts with graphite or AAT tables may still be subsetted but will likely need to use the retain glyph ids option and
21/// configure the subset to pass through the layout tables untouched.
22pub struct SubsetInput(*mut sys::hb_subset_input_t);
23
24impl SubsetInput {
25    /// Creates a new subset input object.
26    #[doc(alias = "hb_subset_input_create_or_fail")]
27    pub fn new() -> Result<Self, AllocationError> {
28        let input = unsafe { sys::hb_subset_input_create_or_fail() };
29        if input.is_null() {
30            return Err(AllocationError);
31        }
32        Ok(Self(input))
33    }
34
35    /// Configures input object to keep everything in the font face. That is, all Unicodes, glyphs, names, layout items,
36    /// glyph names, etc.
37    ///
38    /// The input can be tailored afterwards by the caller.
39    #[doc(alias = "hb_subset_input_keep_everything")]
40    pub fn keep_everything(&mut self) {
41        unsafe { sys::hb_subset_input_keep_everything(self.as_raw()) }
42    }
43
44    /// Gets a proxy for modifying flags.
45    ///
46    /// # Example
47    /// ```
48    /// # use hb_subset::*;
49    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
50    /// let mut subset = SubsetInput::new()?;
51    /// subset.flags().retain_glyph_names();
52    /// assert_eq!(*subset.flags(), *Flags::default().retain_glyph_names());
53    ///
54    /// *subset.flags() = Flags::default();
55    /// assert_eq!(*subset.flags(), Flags::default());
56    /// # Ok(())
57    /// # }
58    /// ```
59    #[doc(alias = "hb_subset_input_set_flags")]
60    #[doc(alias = "hb_subset_input_get_flags")]
61    pub fn flags(&mut self) -> FlagRef<'_> {
62        FlagRef(
63            self,
64            Flags(unsafe { sys::hb_subset_input_get_flags(self.as_raw()) }),
65        )
66    }
67
68    /// Gets the set of glyph IDs to retain.
69    ///
70    /// The caller should modify the set as needed.
71    #[doc(alias = "hb_subset_input_glyph_set")]
72    #[doc(alias = "hb_subset_input_set")]
73    #[doc(alias = "HB_SUBSET_SETS_GLYPH_INDEX")]
74    pub fn glyph_set(&mut self) -> U32Set<'_> {
75        unsafe {
76            Set::from_raw(sys::hb_set_reference(sys::hb_subset_input_glyph_set(
77                self.as_raw(),
78            )))
79        }
80    }
81
82    /// Gets the set of Unicode codepoints to retain.
83    ///
84    /// The caller should modify the set as needed.
85    #[doc(alias = "hb_subset_input_unicode_set")]
86    #[doc(alias = "hb_subset_input_set")]
87    #[doc(alias = "HB_SUBSET_SETS_UNICODE")]
88    pub fn unicode_set(&mut self) -> CharSet<'_> {
89        unsafe {
90            Set::from_raw(sys::hb_set_reference(sys::hb_subset_input_unicode_set(
91                self.as_raw(),
92            )))
93        }
94    }
95
96    /// Gets the set of table tags which specifies tables that should not be subsetted.
97    ///
98    /// The caller should modify the set as needed.
99    #[doc(alias = "hb_subset_input_set")]
100    #[doc(alias = "HB_SUBSET_SETS_NO_SUBSET_TABLE_TAG")]
101    pub fn no_subset_table_tag_set(&mut self) -> TagSet<'_> {
102        unsafe {
103            Set::from_raw(sys::hb_set_reference(sys::hb_subset_input_set(
104                self.as_raw(),
105                sys::hb_subset_sets_t::NO_SUBSET_TABLE_TAG,
106            )))
107        }
108    }
109
110    /// Gets the set of table tags which specifies tables which will be dropped in the subset.
111    ///
112    /// The caller should modify the set as needed.
113    #[doc(alias = "hb_subset_input_set")]
114    #[doc(alias = "HB_SUBSET_SETS_DROP_TABLE_TAG")]
115    pub fn drop_table_tag_set(&mut self) -> TagSet<'_> {
116        unsafe {
117            Set::from_raw(sys::hb_set_reference(sys::hb_subset_input_set(
118                self.as_raw(),
119                sys::hb_subset_sets_t::DROP_TABLE_TAG,
120            )))
121        }
122    }
123
124    /// Gets the set of name ids that will be retained.
125    ///
126    /// The caller should modify the set as needed.
127    #[doc(alias = "hb_subset_input_set")]
128    #[doc(alias = "HB_SUBSET_SETS_NAME_ID")]
129    pub fn name_id_set(&mut self) -> U32Set<'_> {
130        unsafe {
131            Set::from_raw(sys::hb_set_reference(sys::hb_subset_input_set(
132                self.as_raw(),
133                sys::hb_subset_sets_t::NAME_ID,
134            )))
135        }
136    }
137
138    /// Gets the set of name lang ids that will be retained.
139    ///
140    /// The caller should modify the set as needed.
141    #[doc(alias = "hb_subset_input_set")]
142    #[doc(alias = "HB_SUBSET_SETS_NAME_LANG_ID")]
143    pub fn name_lang_id_set(&mut self) -> U32Set<'_> {
144        unsafe {
145            Set::from_raw(sys::hb_set_reference(sys::hb_subset_input_set(
146                self.as_raw(),
147                sys::hb_subset_sets_t::NAME_LANG_ID,
148            )))
149        }
150    }
151
152    /// Gets the set of layout feature tags that will be retained in the subset.
153    ///
154    /// The caller should modify the set as needed.
155    #[doc(alias = "hb_subset_input_set")]
156    #[doc(alias = "HB_SUBSET_SETS_LAYOUT_FEATURE_TAG")]
157    pub fn layout_feature_tag_set(&mut self) -> TagSet<'_> {
158        unsafe {
159            Set::from_raw(sys::hb_set_reference(sys::hb_subset_input_set(
160                self.as_raw(),
161                sys::hb_subset_sets_t::LAYOUT_FEATURE_TAG,
162            )))
163        }
164    }
165
166    /// Gets the set of layout script tags that will be retained in the subset.
167    ///
168    /// Defaults to all tags. The caller should modify the set as needed.
169    #[doc(alias = "hb_subset_input_set")]
170    #[doc(alias = "HB_SUBSET_SETS_LAYOUT_SCRIPT_TAG")]
171    pub fn layout_script_tag_set(&mut self) -> TagSet<'_> {
172        unsafe {
173            Set::from_raw(sys::hb_set_reference(sys::hb_subset_input_set(
174                self.as_raw(),
175                sys::hb_subset_sets_t::LAYOUT_SCRIPT_TAG,
176            )))
177        }
178    }
179
180    /// Returns a map which can be used to provide an explicit mapping from old to new glyph id's in the produced
181    /// subset. The caller should populate the map as desired. If this map is left empty then glyph ids will be
182    /// automatically mapped to new values by the subsetter. If populated, the mapping must be unique. That is no two
183    /// original glyph ids can be mapped to the same new id. Additionally, if a mapping is provided then the retain gids
184    /// option cannot be enabled.
185    ///
186    /// Any glyphs that are retained in the subset which are not specified in this mapping will be assigned glyph ids
187    /// after the highest glyph id in the mapping.
188    ///
189    /// Note: this will accept and apply non-monotonic mappings, however this may result in unsorted Coverage tables.
190    /// Such fonts may not work for all use cases (for example ots will reject unsorted coverage tables). So it's
191    /// recommended, if possible, to supply a monotonic mapping.
192    #[doc(alias = "hb_subset_input_old_to_new_glyph_mapping")]
193    pub fn old_to_new_glyph_mapping(&mut self) -> Map<'_, u32, u32> {
194        unsafe {
195            Map::from_raw(sys::hb_map_reference(
196                sys::hb_subset_input_old_to_new_glyph_mapping(self.as_raw()),
197            ))
198        }
199    }
200
201    /// Subsets a font according to provided input.
202    #[doc(alias = "hb_subset_or_fail")]
203    pub fn subset_font(&self, font: &FontFace<'_>) -> Result<FontFace<'static>, SubsettingError> {
204        let face = unsafe { sys::hb_subset_or_fail(font.as_raw(), self.as_raw()) };
205        if face.is_null() {
206            return Err(SubsettingError);
207        }
208        Ok(unsafe { FontFace::from_raw(face) })
209    }
210
211    /// Computes a plan for subsetting the supplied face according to a provided input.
212    ///
213    /// The plan describes which tables and glyphs should be retained.
214    #[doc(alias = "hb_subset_plan_create_or_fail")]
215    pub fn plan<'f>(&self, font: &'f FontFace<'_>) -> Result<SubsetPlan<'f, '_>, SubsettingError> {
216        let plan = unsafe { sys::hb_subset_plan_create_or_fail(font.as_raw(), self.as_raw()) };
217        if plan.is_null() {
218            return Err(SubsettingError);
219        }
220        Ok(unsafe { SubsetPlan::from_raw(plan) })
221    }
222}
223
224impl SubsetInput {
225    /// Converts the subset input into raw [`sys::hb_subset_input_t`] pointer.
226    ///
227    /// This method transfers the ownership of the subset input to the caller. It is up to the caller to call
228    /// [`sys::hb_subset_input_destroy`] to free the pointer, or call [`Self::from_raw`] to convert it back into
229    /// [`SubsetInput`].
230    pub fn into_raw(self) -> *mut sys::hb_subset_input_t {
231        let ptr = self.0;
232        std::mem::forget(self);
233        ptr
234    }
235
236    /// Exposes the raw inner pointer without transferring the ownership.
237    ///
238    /// Unlike [`Self::into_raw`], this method does not transfer the ownership of the pointer to the caller.
239    pub fn as_raw(&self) -> *mut sys::hb_subset_input_t {
240        self.0
241    }
242
243    /// Constructs a subset input from raw [`sys::hb_subset_input_t`] pointer.
244    ///
245    /// # Safety
246    /// The given `subset` pointer must either be constructed by some Harfbuzz function, or be returned from
247    /// [`Self::into_raw`].
248    pub unsafe fn from_raw(subset: *mut sys::hb_subset_input_t) -> Self {
249        Self(subset)
250    }
251}
252
253impl Drop for SubsetInput {
254    #[doc(alias = "hb_subset_input_destroy")]
255    fn drop(&mut self) {
256        unsafe { sys::hb_subset_input_destroy(self.0) }
257    }
258}
259
260/// Information about how a subsetting operation will be executed.
261///
262/// This includes e.g. how glyph ids are mapped from the original font to the subset.
263pub struct SubsetPlan<'f, 'b> {
264    plan: *mut sys::hb_subset_plan_t,
265    // The lifetime here is actually referring to the lifetime of SubsetPlan
266    unicode_to_old_glyph_mapping: Map<'static, char, u32>,
267    new_to_old_glyph_mapping: Map<'static, u32, u32>,
268    old_to_new_glyph_mapping: Map<'static, u32, u32>,
269    _font: PhantomData<&'f FontFace<'b>>,
270}
271
272impl<'f, 'b> SubsetPlan<'f, 'b> {
273    /// Executes the subsetting plan.
274    #[doc(alias = "hb_subset_plan_execute_or_fail")]
275    pub fn subset(&self) -> Result<FontFace<'b>, SubsettingError> {
276        let font = unsafe { sys::hb_subset_plan_execute_or_fail(self.as_raw()) };
277        if font.is_null() {
278            return Err(SubsettingError);
279        }
280        Ok(unsafe { FontFace::from_raw(font) })
281    }
282
283    /// Returns the mapping between codepoints in the original font and the associated glyph id in the original font.
284    #[doc(alias = "hb_subset_plan_unicode_to_old_glyph_mapping")]
285    pub fn unicode_to_old_glyph_mapping(&self) -> &'_ Map<'_, char, u32> {
286        &self.unicode_to_old_glyph_mapping
287    }
288
289    /// Returns the mapping between glyphs in the subset that will be produced by plan and the glyph in the original font.
290    #[doc(alias = "hb_subset_plan_new_to_old_glyph_mapping")]
291    pub fn new_to_old_glyph_mapping(&self) -> &'_ Map<'_, u32, u32> {
292        &self.new_to_old_glyph_mapping
293    }
294
295    /// Returns the mapping between glyphs in the original font to glyphs in the subset that will be produced by plan.
296    #[doc(alias = "hb_subset_plan_old_to_new_glyph_mapping")]
297    pub fn old_to_new_glyph_mapping(&self) -> &'_ Map<'_, u32, u32> {
298        &self.old_to_new_glyph_mapping
299    }
300}
301
302impl<'f, 'b> SubsetPlan<'f, 'b> {
303    /// Converts the subset plan into raw [`sys::hb_subset_plan_t`] pointer.
304    ///
305    /// This method transfers the ownership of the subset plan to the caller. It is up to the caller to call
306    /// [`sys::hb_subset_plan_destroy`] to free the pointer, or call [`Self::from_raw`] to convert it back into
307    /// [`SubsetPlan`].
308    pub fn into_raw(self) -> *mut sys::hb_subset_plan_t {
309        let ptr = self.plan;
310        std::mem::forget(self);
311        ptr
312    }
313
314    /// Exposes the raw inner pointer without transferring the ownership.
315    ///
316    /// Unlike [`Self::into_raw`], this method does not transfer the ownership of the pointer to the caller.
317    pub fn as_raw(&self) -> *mut sys::hb_subset_plan_t {
318        self.plan
319    }
320
321    /// Constructs a subset plan from raw [`sys::hb_subset_plan_t`] pointer.
322    ///
323    /// # Safety
324    /// The given `plan` pointer must either be constructed by some Harfbuzz function, or be returned from
325    /// [`Self::into_raw`].
326    pub unsafe fn from_raw(plan: *mut sys::hb_subset_plan_t) -> Self {
327        let unicode_to_old_glyph_mapping = unsafe {
328            Map::from_raw(sys::hb_map_reference(
329                sys::hb_subset_plan_unicode_to_old_glyph_mapping(plan),
330            ))
331        };
332        let new_to_old_glyph_mapping = unsafe {
333            Map::from_raw(sys::hb_map_reference(
334                sys::hb_subset_plan_new_to_old_glyph_mapping(plan),
335            ))
336        };
337        let old_to_new_glyph_mapping = unsafe {
338            Map::from_raw(sys::hb_map_reference(
339                sys::hb_subset_plan_old_to_new_glyph_mapping(plan),
340            ))
341        };
342
343        Self {
344            plan,
345            unicode_to_old_glyph_mapping,
346            new_to_old_glyph_mapping,
347            old_to_new_glyph_mapping,
348            _font: PhantomData,
349        }
350    }
351}
352
353impl<'f, 'b> Drop for SubsetPlan<'f, 'b> {
354    #[doc(alias = "hb_subset_plan_destroy")]
355    fn drop(&mut self) {
356        unsafe { sys::hb_subset_plan_destroy(self.plan) }
357    }
358}
359
360#[cfg(test)]
361mod tests {
362    use super::*;
363    use crate::{tests::NOTO_SANS, Blob};
364
365    #[test]
366    fn keep_everything_should_keep_all_codepoints_and_glyphs() {
367        let mut subset = SubsetInput::new().unwrap();
368        subset.keep_everything();
369        assert_eq!(subset.unicode_set().len(), u32::MAX as usize);
370        assert_eq!(subset.glyph_set().len(), u32::MAX as usize);
371        let orig = FontFace::new(Blob::from_file(NOTO_SANS).unwrap()).unwrap();
372        let new = subset.subset_font(&orig).unwrap();
373        assert_eq!(
374            orig.covered_codepoints().unwrap().len(),
375            new.covered_codepoints().unwrap().len()
376        );
377        assert_eq!(orig.glyph_count(), new.glyph_count());
378    }
379
380    #[test]
381    fn keeping_codepoints_should_keep_ligatures() {
382        let font = FontFace::new(Blob::from_file(NOTO_SANS).unwrap()).unwrap();
383        let mut subset = SubsetInput::new().unwrap();
384        subset.unicode_set().insert('f');
385        subset.unicode_set().insert('i');
386        let font = subset.subset_font(&font).unwrap();
387        assert_eq!(font.covered_codepoints().unwrap().len(), 2);
388        assert_eq!(font.glyph_count(), 6); // TODO: Actually check *which* glyphs are included
389                                           // Currently just assuming [empty], f, i, fi, ffi, and ff
390    }
391
392    #[test]
393    fn old_to_new_glyph_mapping() {
394        let font = FontFace::new(Blob::from_file(NOTO_SANS).unwrap()).unwrap();
395        let char_to_glyph = font.nominal_glyph_mapping().unwrap();
396
397        // Map 'a' and 'b' to arbitrary glyphs
398        let mut subset = SubsetInput::new().unwrap();
399        subset
400            .old_to_new_glyph_mapping()
401            .insert(char_to_glyph.get('a').unwrap(), 5);
402        subset
403            .old_to_new_glyph_mapping()
404            .insert(char_to_glyph.get('b').unwrap(), 709);
405        subset.unicode_set().insert('a');
406        subset.unicode_set().insert('b');
407
408        let font = subset.subset_font(&font).unwrap();
409        // Most of the glyphs should be empty
410        assert_eq!(font.glyph_count(), 710);
411
412        let char_to_glyph = font.nominal_glyph_mapping().unwrap();
413        // But the specified ones should be what we set
414        assert_eq!(char_to_glyph.get('a').unwrap(), 5);
415        assert_eq!(char_to_glyph.get('b').unwrap(), 709);
416    }
417
418    #[test]
419    fn convert_subset_into_raw_and_back() {
420        let subset = SubsetInput::new().unwrap();
421        let subset_ptr = subset.into_raw();
422        let subset = unsafe { SubsetInput::from_raw(subset_ptr) };
423        drop(subset);
424    }
425
426    #[test]
427    fn convert_plan_into_raw_and_back() {
428        let font = FontFace::new(Blob::from_file(NOTO_SANS).unwrap()).unwrap();
429        let subset = SubsetInput::new().unwrap();
430        let plan = subset.plan(&font).unwrap();
431        let plan_ptr = plan.into_raw();
432        let plan = unsafe { SubsetPlan::from_raw(plan_ptr) };
433        drop(plan);
434    }
435}