1mod 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;
96const MAX_GID: GlyphId = GlyphId::new(0xFFFFFFFF);
100
101pub static DEFAULT_LAYOUT_FEATURES: &[Tag] = &[
103 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 Tag::new(b"frac"),
114 Tag::new(b"numr"),
115 Tag::new(b"dnom"),
116 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 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 Tag::new(b"ltra"),
130 Tag::new(b"ltrm"),
131 Tag::new(b"rtla"),
133 Tag::new(b"rtlm"),
134 Tag::new(b"rand"),
136 Tag::new(b"jalt"),
138 Tag::new(b"chws"),
140 Tag::new(b"vchw"),
141 Tag::new(b"halt"),
142 Tag::new(b"vhal"),
143 Tag::new(b"Harf"),
145 Tag::new(b"HARF"),
146 Tag::new(b"Buzz"),
147 Tag::new(b"BUZZ"),
148 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 Tag::new(b"ljmo"),
162 Tag::new(b"vjmo"),
163 Tag::new(b"tjmo"),
164 Tag::new(b"abvs"),
166 Tag::new(b"blws"),
167 Tag::new(b"abvm"),
168 Tag::new(b"blwm"),
169 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 pub const SUBSET_FLAGS_DEFAULT: Self = Self(0x0000);
199
200 pub const SUBSET_FLAGS_NO_HINTING: Self = Self(0x0001);
203
204 pub const SUBSET_FLAGS_RETAIN_GIDS: Self = Self(0x0002);
207
208 pub const SUBSET_FLAGS_DESUBROUTINIZE: Self = Self(0x0004);
211
212 pub const SUBSET_FLAGS_NAME_LEGACY: Self = Self(0x0008);
215
216 pub const SUBSET_FLAGS_SET_OVERLAPS_FLAG: Self = Self(0x0010);
218
219 pub const SUBSET_FLAGS_PASSTHROUGH_UNRECOGNIZED: Self = Self(0x0020);
222
223 pub const SUBSET_FLAGS_NOTDEF_OUTLINE: Self = Self(0x0040);
225
226 pub const SUBSET_FLAGS_GLYPH_NAMES: Self = Self(0x0080);
229
230 pub const SUBSET_FLAGS_NO_PRUNE_UNICODE_RANGES: Self = Self(0x0100);
233
234 pub const SUBSET_FLAGS_NO_LAYOUT_CLOSURE: Self = Self(0x0200);
237
238 pub const SUBSET_FLAGS_OPTIMIZE_IUP_DELTAS: Self = Self(0x0400);
241
242 #[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 #[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 #[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 glyph_map: FnvHashMap<GlyphId, GlyphId>,
295 glyph_map_gsub: FnvHashMap<GlyphId, GlyphId>,
297 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 gsub_features: FnvHashMap<u16, u16>,
317 gpos_features: FnvHashMap<u16, u16>,
318
319 gsub_features_w_duplicates: FnvHashMap<u16, u16>,
321 gpos_features_w_duplicates: FnvHashMap<u16, u16>,
322
323 gsub_lookups: FnvHashMap<u16, u16>,
325 gpos_lookups: FnvHashMap<u16, u16>,
326
327 gsub_script_langsys: FnvHashMap<u16, IntSet<u16>>,
329 gpos_script_langsys: FnvHashMap<u16, IntSet<u16>>,
330
331 used_mark_sets_map: FnvHashMap<u16, u16>,
333
334 colrv1_layers: FnvHashMap<u32, u32>,
336 colr_palettes: FnvHashMap<u16, u16>,
338 colr_varstore_inner_maps: Vec<IncBiMap>,
340 colr_varidx_delta_map: FnvHashMap<u32, (u32, i32)>,
342 colr_new_deltaset_idx_varidx_map: FnvHashMap<u32, u32>,
344
345 os2_info: Os2Info,
346
347 base_varidx_delta_map: FnvHashMap<u32, (u32, i32)>,
349 base_varstore_inner_maps: Vec<IncBiMap>,
351
352 layout_varidx_delta_map: FnvHashMap<u32, (u32, i32)>,
354 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 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 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 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 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 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 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 self.glyphset_gsub.insert(GlyphId::NOTDEF);
521
522 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 self.layout_populate_gids_to_retain(font);
530
531 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 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 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 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 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 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 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 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
805fn 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}
857fn 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
883fn 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#[derive(Default)]
960pub struct SubsetState {
961 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 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 fn prune_features(
1001 &self,
1002 lookup_indices: &IntSet<u16>,
1003 feature_indices: IntSet<u16>,
1004 ) -> IntSet<u16>;
1005
1006 fn find_duplicate_features(
1009 &self,
1010 lookup_indices: &IntSet<u16>,
1011 feature_indices: IntSet<u16>,
1012 ) -> FnvHashMap<u16, u16>;
1013
1014 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");
1041pub trait Subset {
1043 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 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
1068pub(crate) trait SubsetTable<'a> {
1070 type ArgsForSubset: 'a;
1071 type Output: 'a;
1072 fn subset(
1074 &self,
1075 plan: &Plan,
1076 s: &mut Serializer,
1077 args: Self::ArgsForSubset,
1078 ) -> Result<Self::Output, SerializeErrorFlags>;
1079}
1080
1081trait Serialize<'a> {
1083 type Args: 'a;
1084 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 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 Cvar::TAG | CVT | FPGM | PREP | Hdmx::TAG | VDMX => no_hinting,
1124 _ => 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 if needed.is_err() {
1146 return Ok(());
1147 }
1148
1149 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 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 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 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 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 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 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 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 bulk += src_glyphs * 16;
1355 } else if table_tag == Cff2::TAG {
1356 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}