fontcull_klippa/
lib.rs

1//! try to define Subset trait so I can add methods for Hmtx
2//! TODO: make it generic for all tables
3mod base;
4mod cblc;
5mod cmap;
6mod colr;
7mod cpal;
8mod fvar;
9mod gdef;
10mod glyf_loca;
11mod gpos;
12mod graph;
13mod gsub;
14mod gsubgpos;
15mod gvar;
16mod hdmx;
17mod head;
18mod hmtx;
19mod hvar;
20mod inc_bimap;
21mod layout;
22mod maxp;
23mod name;
24mod offset;
25mod offset_array;
26mod os2;
27mod parsing_util;
28mod post;
29mod priority_queue;
30mod repack;
31mod sbix;
32pub mod serialize;
33mod stat;
34mod variations;
35mod vorg;
36mod vvar;
37use crate::repack::resolve_overflows;
38use gdef::CollectUsedMarkSets;
39use inc_bimap::IncBiMap;
40use layout::{
41    collect_features_with_retained_subs, find_duplicate_features, prune_features,
42    remap_feature_indices, PruneLangSysContext, SubsetLayoutContext,
43};
44pub use parsing_util::{
45    parse_name_ids, parse_name_languages, parse_tag_list, parse_unicodes, populate_gids,
46};
47
48use fnv::FnvHashMap;
49use fontcull_skrifa::MetadataProvider;
50use fontcull_write_fonts::types::GlyphId;
51use fontcull_write_fonts::types::Tag;
52use fontcull_write_fonts::{
53    read::{
54        collections::{int_set::Domain, IntSet},
55        tables::{
56            base::Base,
57            cbdt::Cbdt,
58            cblc::Cblc,
59            cff::Cff,
60            cff2::Cff2,
61            cmap::{Cmap, CmapSubtable},
62            colr::Colr,
63            cpal::Cpal,
64            cvar::Cvar,
65            gasp,
66            gdef::Gdef,
67            glyf::{Glyf, Glyph},
68            gpos::Gpos,
69            gsub::Gsub,
70            gvar::Gvar,
71            hdmx::Hdmx,
72            head::Head,
73            hvar::Hvar,
74            loca::Loca,
75            name::Name,
76            os2::Os2,
77            post::Post,
78            sbix::Sbix,
79            vorg::Vorg,
80            vvar::Vvar,
81        },
82        types::NameId,
83        FontRef, TableProvider, TopLevelTable,
84    },
85    tables::cmap::PlatformId,
86};
87use fontcull_write_fonts::{
88    tables::hhea::Hhea, tables::hmtx::Hmtx, tables::maxp::Maxp, FontBuilder,
89};
90use serialize::SerializeErrorFlags;
91use serialize::Serializer;
92use thiserror::Error;
93
94const MAX_COMPOSITE_OPERATIONS_PER_GLYPH: u8 = 64;
95const MAX_NESTING_LEVEL: u8 = 64;
96// Support 24-bit gids. This should probably be extended to u32::MAX but
97// this causes tests to fail with 'subtract with overflow error'.
98// See <https://github.com/googlefonts/fontations/issues/997>
99const MAX_GID: GlyphId = GlyphId::new(0xFFFFFFFF);
100
101// ref: <https://github.com/harfbuzz/harfbuzz/blob/021b44388667903d7bc9c92c924ad079f13b90ce/src/hb-subset-input.cc#L82>
102pub static DEFAULT_LAYOUT_FEATURES: &[Tag] = &[
103    // default shaper
104    // common
105    Tag::new(b"rvrn"),
106    Tag::new(b"ccmp"),
107    Tag::new(b"liga"),
108    Tag::new(b"locl"),
109    Tag::new(b"mark"),
110    Tag::new(b"mkmk"),
111    Tag::new(b"rlig"),
112    //fractions
113    Tag::new(b"frac"),
114    Tag::new(b"numr"),
115    Tag::new(b"dnom"),
116    // horizontal
117    Tag::new(b"calt"),
118    Tag::new(b"clig"),
119    Tag::new(b"curs"),
120    Tag::new(b"kern"),
121    Tag::new(b"rclt"),
122    //vertical
123    Tag::new(b"valt"),
124    Tag::new(b"vert"),
125    Tag::new(b"vkrn"),
126    Tag::new(b"vpal"),
127    Tag::new(b"vrt2"),
128    //ltr
129    Tag::new(b"ltra"),
130    Tag::new(b"ltrm"),
131    //rtl
132    Tag::new(b"rtla"),
133    Tag::new(b"rtlm"),
134    //random
135    Tag::new(b"rand"),
136    //justify
137    Tag::new(b"jalt"),
138    //east asian spacing
139    Tag::new(b"chws"),
140    Tag::new(b"vchw"),
141    Tag::new(b"halt"),
142    Tag::new(b"vhal"),
143    //private
144    Tag::new(b"Harf"),
145    Tag::new(b"HARF"),
146    Tag::new(b"Buzz"),
147    Tag::new(b"BUZZ"),
148    //complex shapers
149    //arabic
150    Tag::new(b"init"),
151    Tag::new(b"medi"),
152    Tag::new(b"fina"),
153    Tag::new(b"isol"),
154    Tag::new(b"med2"),
155    Tag::new(b"fin2"),
156    Tag::new(b"fin3"),
157    Tag::new(b"cswh"),
158    Tag::new(b"mset"),
159    Tag::new(b"stch"),
160    //hangul
161    Tag::new(b"ljmo"),
162    Tag::new(b"vjmo"),
163    Tag::new(b"tjmo"),
164    //tibetan
165    Tag::new(b"abvs"),
166    Tag::new(b"blws"),
167    Tag::new(b"abvm"),
168    Tag::new(b"blwm"),
169    //indic
170    Tag::new(b"nukt"),
171    Tag::new(b"akhn"),
172    Tag::new(b"rphf"),
173    Tag::new(b"rkrf"),
174    Tag::new(b"pref"),
175    Tag::new(b"blwf"),
176    Tag::new(b"half"),
177    Tag::new(b"abvf"),
178    Tag::new(b"pstf"),
179    Tag::new(b"cfar"),
180    Tag::new(b"vatu"),
181    Tag::new(b"cjct"),
182    Tag::new(b"init"),
183    Tag::new(b"pres"),
184    Tag::new(b"abvs"),
185    Tag::new(b"blws"),
186    Tag::new(b"psts"),
187    Tag::new(b"haln"),
188    Tag::new(b"dist"),
189    Tag::new(b"abvm"),
190    Tag::new(b"blwm"),
191];
192
193#[derive(Clone, Copy, Debug)]
194pub struct SubsetFlags(u16);
195
196impl SubsetFlags {
197    //all flags at their default value of false.
198    pub const SUBSET_FLAGS_DEFAULT: Self = Self(0x0000);
199
200    //If set hinting instructions will be dropped in the produced subset.
201    //Otherwise hinting instructions will be retained.
202    pub const SUBSET_FLAGS_NO_HINTING: Self = Self(0x0001);
203
204    //If set glyph indices will not be modified in the produced subset.
205    //If glyphs are dropped their indices will be retained as an empty glyph.
206    pub const SUBSET_FLAGS_RETAIN_GIDS: Self = Self(0x0002);
207
208    //If set and subsetting a CFF font the subsetter will attempt to remove subroutines from the CFF glyphs.
209    //This flag is UNIMPLEMENTED yet
210    pub const SUBSET_FLAGS_DESUBROUTINIZE: Self = Self(0x0004);
211
212    //If set non-unicode name records will be retained in the subset.
213    //This flag is UNIMPLEMENTED yet
214    pub const SUBSET_FLAGS_NAME_LEGACY: Self = Self(0x0008);
215
216    //If set the subsetter will set the OVERLAP_SIMPLE flag on each simple glyph.
217    pub const SUBSET_FLAGS_SET_OVERLAPS_FLAG: Self = Self(0x0010);
218
219    //If set the subsetter will not drop unrecognized tables and instead pass them through untouched.
220    //This flag is UNIMPLEMENTED yet
221    pub const SUBSET_FLAGS_PASSTHROUGH_UNRECOGNIZED: Self = Self(0x0020);
222
223    //If set the notdef glyph outline will be retained in the final subset.
224    pub const SUBSET_FLAGS_NOTDEF_OUTLINE: Self = Self(0x0040);
225
226    //If set the PS glyph names will be retained in the final subset.
227    //This flag is UNIMPLEMENTED yet
228    pub const SUBSET_FLAGS_GLYPH_NAMES: Self = Self(0x0080);
229
230    //If set then the unicode ranges in OS/2 will not be recalculated.
231    //This flag is UNIMPLEMENTED yet
232    pub const SUBSET_FLAGS_NO_PRUNE_UNICODE_RANGES: Self = Self(0x0100);
233
234    //If set don't perform glyph closure on layout substitution rules (GSUB)
235    //This flag is UNIMPLEMENTED yet
236    pub const SUBSET_FLAGS_NO_LAYOUT_CLOSURE: Self = Self(0x0200);
237
238    //If set perform IUP delta optimization on the remaining gvar table's deltas.
239    //This flag is UNIMPLEMENTED yet
240    pub const SUBSET_FLAGS_OPTIMIZE_IUP_DELTAS: Self = Self(0x0400);
241
242    /// Returns `true` if all of the flags in `other` are contained within `self`.
243    #[inline]
244    pub const fn contains(&self, other: Self) -> bool {
245        (self.0 & other.0) == other.0
246    }
247}
248
249impl Default for SubsetFlags {
250    fn default() -> Self {
251        Self::SUBSET_FLAGS_DEFAULT
252    }
253}
254
255impl PartialEq for SubsetFlags {
256    fn eq(&self, other: &Self) -> bool {
257        self.0 == other.0
258    }
259}
260
261impl std::ops::BitOr for SubsetFlags {
262    type Output = Self;
263
264    /// Returns the union of the two sets of flags.
265    #[inline]
266    fn bitor(self, other: SubsetFlags) -> Self {
267        Self(self.0 | other.0)
268    }
269}
270
271impl From<u16> for SubsetFlags {
272    fn from(value: u16) -> Self {
273        Self(value)
274    }
275}
276
277impl std::ops::BitOrAssign for SubsetFlags {
278    /// Adds the set of flags.
279    #[inline]
280    fn bitor_assign(&mut self, other: Self) {
281        self.0 |= other.0;
282    }
283}
284
285#[allow(dead_code)]
286#[derive(Default)]
287pub struct Plan {
288    unicodes: IntSet<u32>,
289    glyphs_requested: IntSet<GlyphId>,
290    glyphset_gsub: IntSet<GlyphId>,
291    glyphset_colred: IntSet<GlyphId>,
292    glyphset: IntSet<GlyphId>,
293    /// Old->New glyph id mapping,
294    glyph_map: FnvHashMap<GlyphId, GlyphId>,
295    // Old->New glyph id (in glyph_set_gsub) mapping
296    glyph_map_gsub: FnvHashMap<GlyphId, GlyphId>,
297    /// New->Old glyph id mapping,
298    reverse_glyph_map: FnvHashMap<GlyphId, GlyphId>,
299
300    new_to_old_gid_list: Vec<(GlyphId, GlyphId)>,
301
302    num_output_glyphs: usize,
303    font_num_glyphs: usize,
304    unicode_to_new_gid_list: Vec<(u32, GlyphId)>,
305    codepoint_to_glyph: FnvHashMap<u32, GlyphId>,
306
307    subset_flags: SubsetFlags,
308    no_subset_tables: IntSet<Tag>,
309    drop_tables: IntSet<Tag>,
310    name_ids: IntSet<NameId>,
311    name_languages: IntSet<u16>,
312    layout_scripts: IntSet<Tag>,
313    layout_features: IntSet<Tag>,
314
315    //active old->new feature index map after removing redundant langsys and prune_features
316    gsub_features: FnvHashMap<u16, u16>,
317    gpos_features: FnvHashMap<u16, u16>,
318
319    //active features(with duplicates) old->new feature index map, used by Script/FeatureVariations
320    gsub_features_w_duplicates: FnvHashMap<u16, u16>,
321    gpos_features_w_duplicates: FnvHashMap<u16, u16>,
322
323    // active old->new lookup index map
324    gsub_lookups: FnvHashMap<u16, u16>,
325    gpos_lookups: FnvHashMap<u16, u16>,
326
327    // active script-langsys
328    gsub_script_langsys: FnvHashMap<u16, IntSet<u16>>,
329    gpos_script_langsys: FnvHashMap<u16, IntSet<u16>>,
330
331    // used_mark_sets mapping: old->new
332    used_mark_sets_map: FnvHashMap<u16, u16>,
333
334    //old->new colrv1 layer index map
335    colrv1_layers: FnvHashMap<u32, u32>,
336    //old->new CPAL palette index map
337    colr_palettes: FnvHashMap<u16, u16>,
338    // COLR varstore retained varidx mapping
339    colr_varstore_inner_maps: Vec<IncBiMap>,
340    // COLR table old variation index -> (New varidx, new delta) mapping
341    colr_varidx_delta_map: FnvHashMap<u32, (u32, i32)>,
342    // COLR table new delta set index -> new var index mapping
343    colr_new_deltaset_idx_varidx_map: FnvHashMap<u32, u32>,
344
345    os2_info: Os2Info,
346
347    //BASE table old variation index -> (New varidx, new delta) mapping
348    base_varidx_delta_map: FnvHashMap<u32, (u32, i32)>,
349    //BASE table varstore retained varidx mapping
350    base_varstore_inner_maps: Vec<IncBiMap>,
351
352    //Old layout item variation index -> (New varidx, delta) mapping
353    layout_varidx_delta_map: FnvHashMap<u32, (u32, i32)>,
354    //GDEF table varstore retained varidx mapping
355    gdef_varstore_inner_maps: Vec<IncBiMap>,
356}
357
358#[derive(Default)]
359struct Os2Info {
360    min_cmap_codepoint: u32,
361    max_cmap_codepoint: u32,
362}
363
364impl Plan {
365    #[allow(clippy::too_many_arguments)]
366    pub fn new(
367        input_gids: &IntSet<GlyphId>,
368        input_unicodes: &IntSet<u32>,
369        font: &FontRef,
370        flags: SubsetFlags,
371        drop_tables: &IntSet<Tag>,
372        layout_scripts: &IntSet<Tag>,
373        layout_features: &IntSet<Tag>,
374        name_ids: &IntSet<NameId>,
375        name_languages: &IntSet<u16>,
376    ) -> Self {
377        let mut this = Plan {
378            glyphs_requested: input_gids.clone(),
379            font_num_glyphs: get_font_num_glyphs(font),
380            subset_flags: flags,
381            drop_tables: drop_tables.clone(),
382            layout_scripts: layout_scripts.clone(),
383            layout_features: layout_features.clone(),
384            name_ids: name_ids.clone(),
385            name_languages: name_languages.clone(),
386            ..Default::default()
387        };
388
389        // ref: <https://github.com/harfbuzz/harfbuzz/blob/b5a65e0f20c30a7f13b2f6619479a6d666e603e0/src/hb-subset-input.cc#L71>
390        let default_no_subset_tables = [gasp::Gasp::TAG, FPGM, PREP, VDMX, DSIG];
391        this.no_subset_tables
392            .extend(default_no_subset_tables.iter().copied());
393
394        this.populate_unicodes_to_retain(input_gids, input_unicodes, font);
395        this.populate_gids_to_retain(font);
396        this.create_old_gid_to_new_gid_map();
397
398        this.create_glyph_map_gsub();
399        //update the unicode to new gid list
400        let num = this.unicode_to_new_gid_list.len();
401        for i in 0..num {
402            let old_gid = this.unicode_to_new_gid_list[i].1;
403            let new_gid = this.glyph_map.get(&old_gid).unwrap();
404            this.unicode_to_new_gid_list[i].1 = *new_gid;
405        }
406        this.collect_base_var_indices(font);
407        this
408    }
409
410    fn populate_unicodes_to_retain(
411        &mut self,
412        input_gids: &IntSet<GlyphId>,
413        input_unicodes: &IntSet<u32>,
414        font: &FontRef,
415    ) {
416        let charmap = font.charmap();
417        if input_gids.is_empty() && input_unicodes.len() < (self.font_num_glyphs as u64) {
418            let cap: usize = input_unicodes.len().try_into().unwrap_or(usize::MAX);
419            self.unicode_to_new_gid_list.reserve(cap);
420            self.codepoint_to_glyph.reserve(cap);
421            //TODO: add support for subset accelerator?
422
423            for cp in input_unicodes.iter() {
424                match charmap.map(cp) {
425                    Some(gid) => {
426                        self.codepoint_to_glyph.insert(cp, gid);
427                        self.unicode_to_new_gid_list.push((cp, gid));
428                    }
429                    None => {
430                        continue;
431                    }
432                }
433            }
434        } else {
435            //TODO: add support for subset accelerator?
436            let cmap_unicodes = charmap.mappings().map(|t| t.0).collect::<IntSet<u32>>();
437            let unicode_gid_map = charmap.mappings().collect::<FnvHashMap<u32, GlyphId>>();
438
439            let vec_cap: u64 = input_gids.len() + input_unicodes.len();
440            let vec_cap: usize = vec_cap
441                .min(cmap_unicodes.len())
442                .try_into()
443                .unwrap_or(usize::MAX);
444            self.codepoint_to_glyph.reserve(vec_cap);
445            self.unicode_to_new_gid_list.reserve(vec_cap);
446            for range in cmap_unicodes.iter_ranges() {
447                for cp in range {
448                    match unicode_gid_map.get(&cp) {
449                        Some(gid) => {
450                            if !input_gids.contains(*gid) && !input_unicodes.contains(cp) {
451                                continue;
452                            }
453                            self.codepoint_to_glyph.insert(cp, *gid);
454                            self.unicode_to_new_gid_list.push((cp, *gid));
455                        }
456                        None => {
457                            continue;
458                        }
459                    }
460                }
461            }
462
463            /* Add gids which where requested, but not mapped in cmap */
464            for range in input_gids.iter_ranges() {
465                if range.start().to_u32() as usize >= self.font_num_glyphs {
466                    break;
467                }
468                let mut last = range.end().to_u32() as usize;
469                if last >= self.font_num_glyphs {
470                    last = self.font_num_glyphs - 1;
471                }
472                self.glyphset_gsub
473                    .insert_range(*range.start()..=GlyphId::from(last as u32));
474            }
475        }
476        self.unicode_to_new_gid_list.sort();
477        self.glyphset_gsub
478            .extend(self.unicode_to_new_gid_list.iter().map(|t| t.1));
479        self.unicodes
480            .extend(self.unicode_to_new_gid_list.iter().map(|t| t.0));
481
482        // ref: <https://github.com/harfbuzz/harfbuzz/blob/e451e91ec3608a2ebfec34d0c4f0b3d880e00e33/src/hb-subset-plan.cc#L802>
483        self.os2_info.min_cmap_codepoint = self.unicodes.first().unwrap_or(0xFFFF_u32);
484        self.os2_info.max_cmap_codepoint = self.unicodes.last().unwrap_or(0xFFFF_u32);
485
486        self.collect_variation_selectors(font, input_unicodes);
487    }
488
489    fn collect_variation_selectors(&mut self, font: &FontRef, input_unicodes: &IntSet<u32>) {
490        if let Ok(cmap) = font.cmap() {
491            let encoding_records = cmap.encoding_records();
492            if let Ok(i) = encoding_records.binary_search_by(|r| {
493                if r.platform_id() != PlatformId::Unicode {
494                    r.platform_id().cmp(&PlatformId::Unicode)
495                } else if r.encoding_id() != 5 {
496                    r.encoding_id().cmp(&5)
497                } else {
498                    std::cmp::Ordering::Equal
499                }
500            }) {
501                if let Ok(CmapSubtable::Format14(cmap14)) = encoding_records
502                    .get(i)
503                    .unwrap()
504                    .subtable(cmap.offset_data())
505                {
506                    self.unicodes.extend(
507                        cmap14
508                            .var_selector()
509                            .iter()
510                            .map(|s| s.var_selector().to_u32())
511                            .filter(|v| input_unicodes.contains(*v)),
512                    );
513                }
514            }
515        }
516    }
517
518    fn populate_gids_to_retain(&mut self, font: &FontRef) {
519        //not-def
520        self.glyphset_gsub.insert(GlyphId::NOTDEF);
521
522        //glyph closure for cmap
523        if let Ok(cmap) = font.cmap() {
524            cmap.closure_glyphs(&self.unicodes, &mut self.glyphset_gsub);
525        }
526        remove_invalid_gids(&mut self.glyphset_gsub, self.font_num_glyphs);
527
528        // layout closure
529        self.layout_populate_gids_to_retain(font);
530
531        //skip glyph closure for MATH table, it's not supported yet
532
533        //glyph closure for COLR
534        if !self.drop_tables.contains(Tag::new(b"COLR")) {
535            self.colr_closure(font);
536            remove_invalid_gids(&mut self.glyphset_colred, self.font_num_glyphs);
537        } else {
538            self.glyphset_colred = self.glyphset_gsub.clone();
539        }
540
541        /* Populate a full set of glyphs to retain by adding all referenced composite glyphs. */
542        if let Ok(loca) = font.loca(None) {
543            let glyf = font.glyf().expect("Error reading glyf table");
544            let operation_count =
545                self.glyphset_gsub.len() * (MAX_COMPOSITE_OPERATIONS_PER_GLYPH as u64);
546            for gid in self.glyphset_colred.iter() {
547                glyf_closure_glyphs(
548                    &loca,
549                    &glyf,
550                    gid,
551                    &mut self.glyphset,
552                    operation_count as i32,
553                    0,
554                );
555            }
556            remove_invalid_gids(&mut self.glyphset, self.font_num_glyphs);
557        } else {
558            self.glyphset = self.glyphset_colred.clone();
559        }
560
561        self.nameid_closure(font);
562        self.collect_layout_var_indices(font);
563    }
564
565    fn layout_populate_gids_to_retain(&mut self, font: &FontRef) {
566        if !self.drop_tables.contains(Tag::new(b"GSUB")) {
567            if let Ok(gsub) = font.gsub() {
568                gsub.closure_glyphs_lookups_features(self);
569            }
570        }
571
572        if !self.drop_tables.contains(Tag::new(b"GPOS")) {
573            if let Ok(gpos) = font.gpos() {
574                gpos.closure_glyphs_lookups_features(self);
575            }
576        }
577    }
578
579    fn create_old_gid_to_new_gid_map(&mut self) {
580        let pop = self.glyphset.len();
581        self.glyph_map.reserve(pop as usize);
582        self.reverse_glyph_map.reserve(pop as usize);
583        self.new_to_old_gid_list.reserve(pop as usize);
584
585        //TODO: Add support for requested_glyph_map, command line option --gid-map
586        if !self
587            .subset_flags
588            .contains(SubsetFlags::SUBSET_FLAGS_RETAIN_GIDS)
589        {
590            self.new_to_old_gid_list.extend(
591                self.glyphset
592                    .iter()
593                    .zip(0u16..)
594                    .map(|x| (GlyphId::from(x.1), x.0)),
595            );
596            self.num_output_glyphs = self.new_to_old_gid_list.len();
597        } else {
598            self.new_to_old_gid_list
599                .extend(self.glyphset.iter().map(|x| (x, x)));
600            let Some(max_glyph) = self.glyphset.last() else {
601                return;
602            };
603            self.num_output_glyphs = max_glyph.to_u32() as usize + 1;
604        }
605        self.glyph_map
606            .extend(self.new_to_old_gid_list.iter().map(|x| (x.1, x.0)));
607        self.reverse_glyph_map
608            .extend(self.new_to_old_gid_list.iter().map(|x| (x.0, x.1)));
609    }
610
611    fn create_glyph_map_gsub(&mut self) {
612        let map: FnvHashMap<GlyphId, GlyphId> = self
613            .glyphset_gsub
614            .iter()
615            .filter_map(|g| self.glyph_map.get(&g).map(|new_gid| (g, *new_gid)))
616            .collect();
617        let _ = std::mem::replace(&mut self.glyph_map_gsub, map);
618    }
619
620    fn colr_closure(&mut self, font: &FontRef) {
621        if let Ok(colr) = font.colr() {
622            colr.v0_closure_glyphs(&self.glyphset_gsub, &mut self.glyphset_colred);
623            let mut layer_indices = IntSet::empty();
624            let mut palette_indices = IntSet::empty();
625            let mut variation_indices = IntSet::empty();
626            colr.v1_closure(
627                &mut self.glyphset_colred,
628                &mut layer_indices,
629                &mut palette_indices,
630                &mut variation_indices,
631            );
632
633            colr.v0_closure_palette_indices(&self.glyphset_colred, &mut palette_indices);
634            let _ = std::mem::replace(&mut self.colrv1_layers, remap_indices(layer_indices));
635            let _ = std::mem::replace(
636                &mut self.colr_palettes,
637                remap_palette_indices(palette_indices),
638            );
639
640            if variation_indices.is_empty() {
641                return;
642            }
643            // generate 3 maps:
644            // colr_varidx_delta_map
645            // When delta set index map is not included, it's a mapping from varIdx-> (new varIdx,delta).
646            // Otherwise, it's a mapping from old delta set idx-> (new delta set idx, delta).
647            // Mapping delta set indices is the same as gid mapping.
648            //
649            // colr_varstore_inner_maps:
650            // mapping from old varidx -> new varidx
651            //
652            // colr_new_deltaset_idx_varidx_map:
653            // generate new delta set idx-> new var_idx map if DeltsSetIndexMap exists
654            if let Some(Ok(var_store)) = colr.item_variation_store() {
655                let vardata_count = var_store.item_variation_data_count() as u32;
656                let Ok(var_index_map) = colr.var_index_map().transpose() else {
657                    return;
658                };
659
660                let mut delta_set_indices = IntSet::empty();
661                let mut deltaset_idx_var_idx_map = FnvHashMap::default();
662                // when a DeltaSetIndexMap is included, collected variation indices are actually delta set indices,
663                // we need to map them into variation indices
664                if let Some(var_index_map) = &var_index_map {
665                    delta_set_indices.extend(variation_indices.iter());
666                    variation_indices.clear();
667                    for idx in delta_set_indices.iter() {
668                        if let Ok(var_idx) = var_index_map.get(idx) {
669                            let var_idx = ((var_idx.outer as u32) << 16) + var_idx.inner as u32;
670                            variation_indices.insert(var_idx);
671                            deltaset_idx_var_idx_map.insert(idx, var_idx);
672                        }
673                    }
674                }
675                remap_variation_indices(
676                    vardata_count,
677                    &variation_indices,
678                    &mut self.colr_varidx_delta_map,
679                );
680                generate_varstore_inner_maps(
681                    &variation_indices,
682                    vardata_count,
683                    &mut self.colr_varstore_inner_maps,
684                );
685
686                // if DeltaSetIndexMap exists, we need to use deltaset index instead of var_idx
687                if var_index_map.is_some() {
688                    let (new_deltaset_idx_varidx_map, deltaset_idx_delta_map) =
689                        remap_delta_set_indices(
690                            &delta_set_indices,
691                            &deltaset_idx_var_idx_map,
692                            &self.colr_varidx_delta_map,
693                        );
694                    let _ = std::mem::replace(
695                        &mut self.colr_new_deltaset_idx_varidx_map,
696                        new_deltaset_idx_varidx_map,
697                    );
698                    let _ =
699                        std::mem::replace(&mut self.colr_varidx_delta_map, deltaset_idx_delta_map);
700                }
701            }
702        } else {
703            self.glyphset_colred.union(&self.glyphset_gsub);
704        }
705    }
706
707    fn nameid_closure(&mut self, font: &FontRef) {
708        if !self.drop_tables.contains(Tag::new(b"STAT")) {
709            if let Ok(stat) = font.stat() {
710                stat.collect_name_ids(self);
711            }
712        };
713
714        //TODO: skip fvar table when all axes are pinned
715        if !self.drop_tables.contains(Tag::new(b"fvar")) {
716            if let Ok(fvar) = font.fvar() {
717                fvar.collect_name_ids(self);
718            }
719        }
720
721        if !self.drop_tables.contains(Tag::new(b"CPAL")) {
722            if let Ok(cpal) = font.cpal() {
723                cpal.collect_name_ids(self);
724            }
725        }
726
727        if !self.drop_tables.contains(Tag::new(b"GSUB")) {
728            if let Ok(gsub) = font.gsub() {
729                gsub.collect_name_ids(self);
730            }
731        }
732
733        if !self.drop_tables.contains(Tag::new(b"GPOS")) {
734            if let Ok(gpos) = font.gpos() {
735                gpos.collect_name_ids(self);
736            }
737        }
738    }
739
740    fn collect_layout_var_indices(&mut self, font: &FontRef) {
741        if self.drop_tables.contains(Tag::new(b"GDEF")) {
742            return;
743        }
744        let Ok(gdef) = font.gdef() else {
745            return;
746        };
747
748        let mut used_mark_sets = IntSet::empty();
749        gdef.collect_used_mark_sets(self, &mut used_mark_sets);
750        let _ = std::mem::replace(&mut self.used_mark_sets_map, remap_indices(used_mark_sets));
751
752        let Some(Ok(var_store)) = gdef.item_var_store() else {
753            return;
754        };
755        let mut varidx_set = IntSet::empty();
756        gdef.collect_variation_indices(self, &mut varidx_set);
757
758        //TODO: collect variation indices from GPOS
759
760        let vardata_count = var_store.item_variation_data_count() as u32;
761        remap_variation_indices(
762            vardata_count,
763            &varidx_set,
764            &mut self.layout_varidx_delta_map,
765        );
766
767        generate_varstore_inner_maps(
768            &varidx_set,
769            vardata_count,
770            &mut self.gdef_varstore_inner_maps,
771        );
772    }
773
774    fn collect_base_var_indices(&mut self, font: &FontRef) {
775        if self.drop_tables.contains(Tag::new(b"BASE")) {
776            return;
777        }
778
779        if font.fvar().is_err() {
780            return;
781        }
782        let Ok(base) = font.base() else {
783            return;
784        };
785
786        let Some(Ok(var_store)) = base.item_var_store() else {
787            return;
788        };
789
790        let mut varidx_set = IntSet::empty();
791        {
792            base.collect_variation_indices(self, &mut varidx_set);
793        }
794
795        let vardata_count = var_store.item_variation_data_count() as u32;
796        remap_variation_indices(vardata_count, &varidx_set, &mut self.base_varidx_delta_map);
797        generate_varstore_inner_maps(
798            &varidx_set,
799            vardata_count,
800            &mut self.base_varstore_inner_maps,
801        );
802    }
803}
804
805// TODO: when instancing, calculate delta value and set new varidx to NO_VARIATIONS_IDX if all axes are pinned
806fn remap_variation_indices(
807    vardata_count: u32,
808    varidx_set: &IntSet<u32>,
809    varidx_delta_map: &mut FnvHashMap<u32, (u32, i32)>,
810) {
811    if vardata_count == 0 || varidx_set.is_empty() {
812        return;
813    }
814
815    let mut new_major: u32 = 0;
816    let mut new_minor: u32 = 0;
817    let mut last_major = varidx_set.first().unwrap() >> 16;
818    for var_idx in varidx_set.iter() {
819        let major = var_idx >> 16;
820        if major >= vardata_count {
821            break;
822        }
823
824        if major != last_major {
825            new_minor = 0;
826            new_major += 1;
827        }
828
829        let new_idx = (new_major << 16) + new_minor;
830        varidx_delta_map.insert(var_idx, (new_idx, 0));
831
832        new_minor += 1;
833        last_major = major;
834    }
835}
836
837fn generate_varstore_inner_maps(
838    varidx_set: &IntSet<u32>,
839    vardata_count: u32,
840    inner_maps: &mut Vec<IncBiMap>,
841) {
842    if varidx_set.is_empty() || vardata_count == 0 {
843        return;
844    }
845
846    inner_maps.resize_with(vardata_count as usize, Default::default);
847    for idx in varidx_set.iter() {
848        let major = idx >> 16;
849        let minor = idx & 0xFFFF;
850        if major >= vardata_count {
851            break;
852        }
853
854        inner_maps[major as usize].add(minor);
855    }
856}
857//
858fn remap_delta_set_indices(
859    delta_set_indices: &IntSet<u32>,
860    deltaset_idx_var_idx_map: &FnvHashMap<u32, u32>,
861    varidx_delta_map: &FnvHashMap<u32, (u32, i32)>,
862) -> (FnvHashMap<u32, u32>, FnvHashMap<u32, (u32, i32)>) {
863    let mut new_deltaset_idx_varidx_map = FnvHashMap::default();
864    let mut deltaset_idx_delta_map = FnvHashMap::default();
865    let mut new_idx = 0_u32;
866
867    for deltaset_idx in delta_set_indices.iter() {
868        let Some(var_idx) = deltaset_idx_var_idx_map.get(&deltaset_idx) else {
869            continue;
870        };
871
872        let Some((new_var_idx, delta)) = varidx_delta_map.get(var_idx) else {
873            continue;
874        };
875
876        new_deltaset_idx_varidx_map.insert(new_idx, *new_var_idx);
877        deltaset_idx_delta_map.insert(deltaset_idx, (new_idx, *delta));
878        new_idx += 1;
879    }
880    (new_deltaset_idx_varidx_map, deltaset_idx_delta_map)
881}
882
883/// glyph closure for Composite glyphs in glyf table
884/// limit the number of operations through returning an operation count
885fn glyf_closure_glyphs(
886    loca: &Loca,
887    glyf: &Glyf,
888    gid: GlyphId,
889    gids_to_retain: &mut IntSet<GlyphId>,
890    operation_count: i32,
891    depth: u8,
892) -> i32 {
893    if gids_to_retain.contains(gid) {
894        return operation_count;
895    }
896    gids_to_retain.insert(gid);
897
898    if depth > MAX_NESTING_LEVEL {
899        return operation_count;
900    }
901    let depth = depth + 1;
902
903    let mut operation_count = operation_count - 1;
904    if operation_count < 0 {
905        return operation_count;
906    }
907
908    if let Some(Glyph::Composite(glyph)) = loca.get_glyf(gid, glyf).ok().flatten() {
909        for child in glyph.components() {
910            operation_count = glyf_closure_glyphs(
911                loca,
912                glyf,
913                child.glyph.into(),
914                gids_to_retain,
915                operation_count,
916                depth,
917            );
918        }
919    }
920    operation_count
921}
922
923fn remove_invalid_gids(gids: &mut IntSet<GlyphId>, num_glyphs: usize) {
924    gids.remove_range(GlyphId::new(num_glyphs as u32)..=MAX_GID);
925}
926
927fn get_font_num_glyphs(font: &FontRef) -> usize {
928    let ret = font.loca(None).map(|loca| loca.len()).unwrap_or_default();
929    let maxp = font.maxp().expect("Error reading maxp table");
930    ret.max(maxp.num_glyphs() as usize)
931}
932
933pub(crate) fn remap_indices<T: Domain + std::cmp::Eq + std::hash::Hash + From<u16>>(
934    indices: IntSet<T>,
935) -> FnvHashMap<T, T> {
936    indices
937        .iter()
938        .enumerate()
939        .map(|x| (x.1, T::from(x.0 as u16)))
940        .collect()
941}
942
943fn remap_palette_indices(indices: IntSet<u16>) -> FnvHashMap<u16, u16> {
944    indices
945        .iter()
946        .enumerate()
947        .map(|x| {
948            if x.1 == 0xFFFF {
949                (0xFFFF, 0xFFFF)
950            } else {
951                (x.1, x.0 as u16)
952            }
953        })
954        .collect()
955}
956
957/// mutable struct, updated during table subsetting
958/// some tables depend on other tables' subset output
959#[derive(Default)]
960pub struct SubsetState {
961    // whether GDEF ItemVariationStore is retained after subsetting
962    has_gdef_varstore: bool,
963}
964
965#[derive(Debug, Error)]
966pub enum SubsetError {
967    #[error("Invalid input gid {0}")]
968    InvalidGid(String),
969
970    #[error("Invalid gid range {start}-{end}")]
971    InvalidGidRange { start: u32, end: u32 },
972
973    #[error("Invalid input unicode {0}")]
974    InvalidUnicode(String),
975
976    #[error("Invalid unicode range {start}-{end}")]
977    InvalidUnicodeRange { start: u32, end: u32 },
978
979    #[error("Invalid tag {0}")]
980    InvalidTag(String),
981
982    #[error("Invalid ID {0}")]
983    InvalidId(String),
984
985    #[error("Subsetting table '{0}' failed")]
986    SubsetTableError(Tag),
987}
988
989pub trait NameIdClosure {
990    /// collect name_ids
991    fn collect_name_ids(&self, plan: &mut Plan);
992}
993
994pub(crate) trait CollectVariationIndices {
995    fn collect_variation_indices(&self, plan: &Plan, varidx_set: &mut IntSet<u32>);
996}
997
998pub(crate) trait LayoutClosure {
999    /// Remove unreferenced features
1000    fn prune_features(
1001        &self,
1002        lookup_indices: &IntSet<u16>,
1003        feature_indices: IntSet<u16>,
1004    ) -> IntSet<u16>;
1005
1006    /// Return a duplicate feature(after subsetting) index map
1007    /// feature index -> the first index of all duplicates for this feature
1008    fn find_duplicate_features(
1009        &self,
1010        lookup_indices: &IntSet<u16>,
1011        feature_indices: IntSet<u16>,
1012    ) -> FnvHashMap<u16, u16>;
1013
1014    //remove unreferenced langsys and return (script->langsys mapping, retained feature indices)
1015    fn prune_langsys(
1016        &self,
1017        duplicate_feature_index_map: &FnvHashMap<u16, u16>,
1018        layout_scripts: &IntSet<Tag>,
1019    ) -> (FnvHashMap<u16, IntSet<u16>>, IntSet<u16>);
1020
1021    fn closure_glyphs_lookups_features(&self, plan: &mut Plan);
1022}
1023
1024pub const CVT: Tag = Tag::new(b"cvt ");
1025pub const DSIG: Tag = Tag::new(b"DSIG");
1026pub const EBSC: Tag = Tag::new(b"EBSC");
1027pub const FPGM: Tag = Tag::new(b"fpgm");
1028pub const GLAT: Tag = Tag::new(b"Glat");
1029pub const GLOC: Tag = Tag::new(b"Gloc");
1030pub const JSTF: Tag = Tag::new(b"JSTF");
1031pub const LTSH: Tag = Tag::new(b"LTSH");
1032pub const MORX: Tag = Tag::new(b"morx");
1033pub const MORT: Tag = Tag::new(b"mort");
1034pub const KERX: Tag = Tag::new(b"kerx");
1035pub const KERN: Tag = Tag::new(b"kern");
1036pub const PCLT: Tag = Tag::new(b"PCLT");
1037pub const PREP: Tag = Tag::new(b"prep");
1038pub const SILF: Tag = Tag::new(b"Silf");
1039pub const SILL: Tag = Tag::new(b"Sill");
1040pub const VDMX: Tag = Tag::new(b"VDMX");
1041// This trait is implemented for all font top-level tables
1042pub trait Subset {
1043    /// Subset this table, if successful a subset version of this table will be added to builder
1044    fn subset(
1045        &self,
1046        _plan: &Plan,
1047        _font: &FontRef,
1048        _s: &mut Serializer,
1049        _builder: &mut FontBuilder,
1050    ) -> Result<(), SubsetError> {
1051        Ok(())
1052    }
1053
1054    /// Subset this table with a mutable Subsetstate
1055    /// This is needed when some tables have dependencies on other table's subset output
1056    fn subset_with_state(
1057        &self,
1058        _plan: &Plan,
1059        _font: &FontRef,
1060        _state: &mut SubsetState,
1061        _s: &mut Serializer,
1062        _builder: &mut FontBuilder,
1063    ) -> Result<(), SubsetError> {
1064        Ok(())
1065    }
1066}
1067
1068// A helper trait providing a 'subset' method for various subtables that have no associated tag
1069pub(crate) trait SubsetTable<'a> {
1070    type ArgsForSubset: 'a;
1071    type Output: 'a;
1072    /// Subset this table and write a subset version of this table into serializer
1073    fn subset(
1074        &self,
1075        plan: &Plan,
1076        s: &mut Serializer,
1077        args: Self::ArgsForSubset,
1078    ) -> Result<Self::Output, SerializeErrorFlags>;
1079}
1080
1081// A helper trait providing a 'serialize' method
1082trait Serialize<'a> {
1083    type Args: 'a;
1084    /// Serialize this table
1085    fn serialize(s: &mut Serializer, args: Self::Args) -> Result<(), SerializeErrorFlags>;
1086}
1087
1088pub fn subset_font(font: &FontRef, plan: &Plan) -> Result<Vec<u8>, SubsetError> {
1089    let mut builder = FontBuilder::default();
1090
1091    let mut state = SubsetState::default();
1092    let mut tags_with_dependencies = Vec::with_capacity(5);
1093    for record in font.table_directory().table_records() {
1094        let tag = record.tag();
1095        if should_drop_table(tag, plan) {
1096            continue;
1097        }
1098
1099        // TODO: add more tags with dependencies for instancing
1100        match tag {
1101            Gpos::TAG => tags_with_dependencies.push((tag, record.length())),
1102            _ => subset(tag, font, plan, &mut builder, record.length(), &mut state)?,
1103        }
1104    }
1105
1106    for (tag, table_len) in tags_with_dependencies {
1107        subset(tag, font, plan, &mut builder, table_len, &mut state)?;
1108    }
1109    Ok(builder.build())
1110}
1111
1112fn should_drop_table(tag: Tag, plan: &Plan) -> bool {
1113    if plan.drop_tables.contains(tag) {
1114        return true;
1115    }
1116
1117    let no_hinting = plan
1118        .subset_flags
1119        .contains(SubsetFlags::SUBSET_FLAGS_NO_HINTING);
1120
1121    match tag {
1122        // hint tables
1123        Cvar::TAG | CVT | FPGM | PREP | Hdmx::TAG | VDMX => no_hinting,
1124        //TODO: drop var tables during instancing when all axes are pinned
1125        _ => false,
1126    }
1127}
1128
1129fn subset<'a>(
1130    table_tag: Tag,
1131    font: &FontRef<'a>,
1132    plan: &Plan,
1133    builder: &mut FontBuilder<'a>,
1134    table_len: u32,
1135    state: &mut SubsetState,
1136) -> Result<(), SubsetError> {
1137    let buf_size = estimate_subset_table_size(font, table_tag, plan);
1138    let mut s = Serializer::new(buf_size);
1139    let needed = try_subset(table_tag, font, plan, builder, &mut s, table_len, state);
1140    if s.in_error() && !s.only_offset_overflow() {
1141        return Err(SubsetError::SubsetTableError(table_tag));
1142    }
1143
1144    // table subsetted to empty
1145    if needed.is_err() {
1146        return Ok(());
1147    }
1148
1149    //TODO: complete overflow resolution
1150    let subsetted_data = if !s.offset_overflow() {
1151        s.copy_bytes()
1152    } else {
1153        resolve_overflows(&s, table_tag, 32)
1154            .map_err(|_| SubsetError::SubsetTableError(table_tag))?
1155    };
1156
1157    if !subsetted_data.is_empty() {
1158        builder.add_raw(table_tag, subsetted_data);
1159    }
1160    Ok(())
1161}
1162
1163fn try_subset<'a>(
1164    table_tag: Tag,
1165    font: &FontRef<'a>,
1166    plan: &Plan,
1167    builder: &mut FontBuilder<'a>,
1168    s: &mut Serializer,
1169    table_len: u32,
1170    state: &mut SubsetState,
1171) -> Result<(), SubsetError> {
1172    s.start_serialize()
1173        .map_err(|_| SubsetError::SubsetTableError(table_tag))?;
1174
1175    let ret = subset_table(table_tag, font, plan, builder, s, state);
1176    if !s.ran_out_of_room() {
1177        s.end_serialize();
1178        return ret;
1179    }
1180
1181    // ran out of room, reallocate more bytes
1182    let buf_size = s.allocated() * 2 + 16;
1183    if buf_size > (table_len as usize) * 256 {
1184        return ret;
1185    }
1186    s.reset_size(buf_size);
1187    try_subset(table_tag, font, plan, builder, s, table_len, state)
1188}
1189
1190fn subset_table<'a>(
1191    tag: Tag,
1192    font: &FontRef<'a>,
1193    plan: &Plan,
1194    builder: &mut FontBuilder<'a>,
1195    s: &mut Serializer,
1196    state: &mut SubsetState,
1197) -> Result<(), SubsetError> {
1198    if plan.no_subset_tables.contains(tag) {
1199        return passthrough_table(tag, font, s);
1200    }
1201
1202    match tag {
1203        Base::TAG => font
1204            .base()
1205            .map_err(|_| SubsetError::SubsetTableError(Base::TAG))?
1206            .subset(plan, font, s, builder),
1207
1208        //Skip, handled by Cblc
1209        Cbdt::TAG => Ok(()),
1210
1211        Cblc::TAG => font
1212            .cblc()
1213            .map_err(|_| SubsetError::SubsetTableError(Cblc::TAG))?
1214            .subset(plan, font, s, builder),
1215
1216        Cmap::TAG => font
1217            .cmap()
1218            .map_err(|_| SubsetError::SubsetTableError(Cmap::TAG))?
1219            .subset(plan, font, s, builder),
1220
1221        Colr::TAG => font
1222            .colr()
1223            .map_err(|_| SubsetError::SubsetTableError(Colr::TAG))?
1224            .subset(plan, font, s, builder),
1225
1226        //TODO: if SVG is present and we support subsetting SVG table, pass through CPAL table
1227        // see fonttools: <https://github.com/fonttools/fonttools/blob/64e5277d040e1a5c84f21f8fb8a5dc7d8ad3c3fa/Lib/fontTools/subset/__init__.py#L2545>
1228        Cpal::TAG => font
1229            .cpal()
1230            .map_err(|_| SubsetError::SubsetTableError(Cpal::TAG))?
1231            .subset(plan, font, s, builder),
1232
1233        Gdef::TAG => font
1234            .gdef()
1235            .map_err(|_| SubsetError::SubsetTableError(Gdef::TAG))?
1236            .subset_with_state(plan, font, state, s, builder),
1237
1238        Glyf::TAG => font
1239            .glyf()
1240            .map_err(|_| SubsetError::SubsetTableError(Glyf::TAG))?
1241            .subset(plan, font, s, builder),
1242
1243        Gpos::TAG => font
1244            .gpos()
1245            .map_err(|_| SubsetError::SubsetTableError(Gpos::TAG))?
1246            .subset_with_state(plan, font, state, s, builder),
1247
1248        Gsub::TAG => font
1249            .gsub()
1250            .map_err(|_| SubsetError::SubsetTableError(Gsub::TAG))?
1251            .subset_with_state(plan, font, state, s, builder),
1252
1253        Gvar::TAG => font
1254            .gvar()
1255            .map_err(|_| SubsetError::SubsetTableError(Gvar::TAG))?
1256            .subset(plan, font, s, builder),
1257
1258        Hdmx::TAG => font
1259            .hdmx()
1260            .map_err(|_| SubsetError::SubsetTableError(Hdmx::TAG))?
1261            .subset(plan, font, s, builder),
1262
1263        //handled by glyf table if exists
1264        Head::TAG => font.glyf().map(|_| ()).or_else(|_| {
1265            font.head()
1266                .map_err(|_| SubsetError::SubsetTableError(Head::TAG))?
1267                .subset(plan, font, s, builder)
1268        }),
1269
1270        //Skip, handled by Hmtx
1271        Hhea::TAG => Ok(()),
1272
1273        Hmtx::TAG => font
1274            .hmtx()
1275            .map_err(|_| SubsetError::SubsetTableError(Hmtx::TAG))?
1276            .subset(plan, font, s, builder),
1277
1278        Hvar::TAG => font
1279            .hvar()
1280            .map_err(|_| SubsetError::SubsetTableError(Hvar::TAG))?
1281            .subset(plan, font, s, builder),
1282
1283        Vvar::TAG => font
1284            .vvar()
1285            .map_err(|_| SubsetError::SubsetTableError(Vvar::TAG))?
1286            .subset(plan, font, s, builder),
1287
1288        //Skip, handled by glyf
1289        Loca::TAG => Ok(()),
1290
1291        Maxp::TAG => font
1292            .maxp()
1293            .map_err(|_| SubsetError::SubsetTableError(Maxp::TAG))?
1294            .subset(plan, font, s, builder),
1295
1296        Name::TAG => font
1297            .name()
1298            .map_err(|_| SubsetError::SubsetTableError(Name::TAG))?
1299            .subset(plan, font, s, builder),
1300
1301        Os2::TAG => font
1302            .os2()
1303            .map_err(|_| SubsetError::SubsetTableError(Os2::TAG))?
1304            .subset(plan, font, s, builder),
1305
1306        Post::TAG => font
1307            .post()
1308            .map_err(|_| SubsetError::SubsetTableError(Post::TAG))?
1309            .subset(plan, font, s, builder),
1310
1311        Sbix::TAG => font
1312            .sbix()
1313            .map_err(|_| SubsetError::SubsetTableError(Sbix::TAG))?
1314            .subset(plan, font, s, builder),
1315
1316        Vorg::TAG => font
1317            .vorg()
1318            .map_err(|_| SubsetError::SubsetTableError(Vorg::TAG))?
1319            .subset(plan, font, s, builder),
1320
1321        _ => passthrough_table(tag, font, s),
1322    }
1323}
1324
1325fn passthrough_table(tag: Tag, font: &FontRef<'_>, s: &mut Serializer) -> Result<(), SubsetError> {
1326    if let Some(data) = font.data_for_tag(tag) {
1327        s.embed_bytes(data.as_bytes())
1328            .map_err(|_| SubsetError::SubsetTableError(tag))?;
1329    }
1330    Ok(())
1331}
1332
1333pub fn estimate_subset_table_size(font: &FontRef, table_tag: Tag, plan: &Plan) -> usize {
1334    let Some(table_data) = font.data_for_tag(table_tag) else {
1335        return 0;
1336    };
1337
1338    let table_len = table_data.len();
1339    let mut bulk: usize = 8192;
1340    let src_glyphs = plan.font_num_glyphs;
1341    let dst_glyphs = plan.num_output_glyphs;
1342
1343    // ported from HB: Tables that we want to allocate same space as the source table.
1344    // For GSUB/GPOS it's because those are expensive to subset, so giving them more room is fine.
1345    let same_size: bool =
1346        table_tag == Gsub::TAG || table_tag == Gpos::TAG || table_tag == Name::TAG;
1347
1348    if plan
1349        .subset_flags
1350        .contains(SubsetFlags::SUBSET_FLAGS_RETAIN_GIDS)
1351    {
1352        if table_tag == Cff::TAG {
1353            //Add some extra room for the CFF charset
1354            bulk += src_glyphs * 16;
1355        } else if table_tag == Cff2::TAG {
1356            // Just extra CharString offsets
1357            bulk += src_glyphs * 4;
1358        }
1359    }
1360
1361    if src_glyphs == 0 || same_size {
1362        return bulk + table_len;
1363    }
1364
1365    bulk + table_len * ((dst_glyphs as f32 / src_glyphs as f32).sqrt() as usize)
1366}
1367
1368#[cfg(test)]
1369mod test {
1370    use super::*;
1371    #[test]
1372    fn populate_unicodes_wo_input_gid() {
1373        let mut plan = Plan::default();
1374        let font = FontRef::new(fontcull_font_test_data::GLYF_COMPONENTS).unwrap();
1375        plan.font_num_glyphs = get_font_num_glyphs(&font);
1376
1377        let input_gids = IntSet::empty();
1378        let mut input_unicodes = IntSet::empty();
1379        input_unicodes.insert(0x2c_u32);
1380        input_unicodes.insert(0x31_u32);
1381
1382        plan.populate_unicodes_to_retain(&input_gids, &input_unicodes, &font);
1383
1384        assert_eq!(plan.unicodes.len(), 2);
1385        assert!(plan.unicodes.contains(0x2c_u32));
1386        assert!(plan.unicodes.contains(0x31_u32));
1387
1388        assert_eq!(plan.glyphset_gsub.len(), 2);
1389        assert!(plan.glyphset_gsub.contains(GlyphId::new(2)));
1390        assert!(plan.glyphset_gsub.contains(GlyphId::new(4)));
1391
1392        assert_eq!(plan.unicode_to_new_gid_list.len(), 2);
1393        assert_eq!(plan.unicode_to_new_gid_list[0], (0x2c_u32, GlyphId::new(2)));
1394        assert_eq!(plan.unicode_to_new_gid_list[1], (0x31_u32, GlyphId::new(4)));
1395
1396        assert_eq!(plan.codepoint_to_glyph.len(), 2);
1397        assert_eq!(
1398            plan.codepoint_to_glyph.get(&0x2c_u32),
1399            Some(GlyphId::new(2)).as_ref()
1400        );
1401        assert_eq!(
1402            plan.codepoint_to_glyph.get(&0x31_u32),
1403            Some(GlyphId::new(4)).as_ref()
1404        );
1405    }
1406
1407    #[test]
1408    fn populate_unicodes_w_input_gid() {
1409        let mut plan = Plan::default();
1410        let font = FontRef::new(fontcull_font_test_data::GLYF_COMPONENTS).unwrap();
1411        plan.font_num_glyphs = get_font_num_glyphs(&font);
1412
1413        let mut input_gids = IntSet::empty();
1414        let input_unicodes = IntSet::empty();
1415        input_gids.insert(GlyphId::new(2));
1416        input_gids.insert(GlyphId::new(4));
1417
1418        plan.populate_unicodes_to_retain(&input_gids, &input_unicodes, &font);
1419        assert_eq!(plan.unicodes.len(), 2);
1420        assert!(plan.unicodes.contains(0x2c_u32));
1421        assert!(plan.unicodes.contains(0x31_u32));
1422
1423        assert_eq!(plan.glyphset_gsub.len(), 2);
1424        assert!(plan.glyphset_gsub.contains(GlyphId::new(2)));
1425        assert!(plan.glyphset_gsub.contains(GlyphId::new(4)));
1426
1427        assert_eq!(plan.unicode_to_new_gid_list.len(), 2);
1428        assert_eq!(plan.unicode_to_new_gid_list[0], (0x2c_u32, GlyphId::new(2)));
1429        assert_eq!(plan.unicode_to_new_gid_list[1], (0x31_u32, GlyphId::new(4)));
1430
1431        assert_eq!(plan.codepoint_to_glyph.len(), 2);
1432        assert_eq!(
1433            plan.codepoint_to_glyph.get(&0x2c_u32),
1434            Some(GlyphId::new(2)).as_ref()
1435        );
1436        assert_eq!(
1437            plan.codepoint_to_glyph.get(&0x31_u32),
1438            Some(GlyphId::new(4)).as_ref()
1439        );
1440    }
1441
1442    #[test]
1443    fn glyf_closure_composite_glyphs() {
1444        let font = FontRef::new(fontcull_font_test_data::GLYF_COMPONENTS).unwrap();
1445        let loca = font.loca(None).unwrap();
1446        let glyf = font.glyf().unwrap();
1447        let mut gids = IntSet::empty();
1448
1449        glyf_closure_glyphs(&loca, &glyf, GlyphId::new(5), &mut gids, 64, 0);
1450        assert_eq!(gids.len(), 2);
1451        assert!(gids.contains(GlyphId::new(5)));
1452        assert!(gids.contains(GlyphId::new(1)));
1453    }
1454
1455    #[test]
1456    fn populate_gids_wo_cmap_colr_layout() {
1457        let mut plan = Plan::default();
1458        let font = FontRef::new(fontcull_font_test_data::GLYF_COMPONENTS).unwrap();
1459        plan.font_num_glyphs = get_font_num_glyphs(&font);
1460        plan.unicodes.insert(0x2c_u32);
1461        plan.unicodes.insert(0x34_u32);
1462
1463        plan.glyphset_gsub.insert(GlyphId::new(2));
1464        plan.glyphset_gsub.insert(GlyphId::new(7));
1465
1466        plan.populate_gids_to_retain(&font);
1467        assert_eq!(plan.glyphset_gsub.len(), 3);
1468        assert!(plan.glyphset_gsub.contains(GlyphId::new(0)));
1469        assert!(plan.glyphset_gsub.contains(GlyphId::new(2)));
1470        assert!(plan.glyphset_gsub.contains(GlyphId::new(7)));
1471
1472        assert_eq!(plan.glyphset.len(), 5);
1473        assert!(plan.glyphset.contains(GlyphId::new(0)));
1474        assert!(plan.glyphset.contains(GlyphId::new(1)));
1475        assert!(plan.glyphset.contains(GlyphId::new(2)));
1476        assert!(plan.glyphset.contains(GlyphId::new(4)));
1477        assert!(plan.glyphset.contains(GlyphId::new(7)));
1478    }
1479}