Skip to main content

incremental_font_transfer/
patchmap.rs

1//! Loads incremental font transfer <https://w3c.github.io/IFT/Overview.html> patch mappings.
2//!
3//! The IFT and IFTX tables encode mappings from subset definitions to URL's which host patches
4//! that can be applied to the font to add support for the corresponding subset definition.
5
6use std::cmp::Ordering;
7use std::collections::BTreeMap;
8use std::collections::BTreeSet;
9use std::collections::HashMap;
10use std::io::Cursor;
11use std::io::Read;
12use std::ops::RangeInclusive;
13
14use font_types::Fixed;
15use font_types::Int24;
16use font_types::Tag;
17
18use read_fonts::{
19    collections::{IntSet, RangeSet},
20    tables::ift::{
21        CompatibilityId, EntryData, EntryFormatFlags, EntryMapRecord, Ift, PatchMapFormat1,
22        PatchMapFormat2, IFTX_TAG, IFT_TAG,
23    },
24    types::Uint24,
25    FontData, FontRead, FontRef, ReadError, TableProvider,
26};
27
28use skrifa::charmap::Charmap;
29
30use crate::url_templates;
31use crate::url_templates::UrlTemplateError;
32
33// TODO(garretrieger): implement support for building and compiling mapping tables.
34
35/// Find the set of patches which intersect the specified subset definition.
36pub fn intersecting_patches(
37    font: &FontRef,
38    subset_definition: &SubsetDefinition,
39) -> Result<Vec<PatchMapEntry>, ReadError> {
40    // TODO(garretrieger): move this function to a struct so we can optionally store
41    //  indexes or other data to accelerate intersection.
42    let mut result: Vec<PatchMapEntry> = vec![];
43
44    for (tag, table) in IftTableTag::tables_in(font)? {
45        add_intersecting_patches(font, tag, &table, subset_definition, &mut result)?;
46    }
47
48    Ok(result)
49}
50
51fn add_intersecting_patches(
52    font: &FontRef,
53    source_table: IftTableTag,
54    ift: &Ift,
55    subset_definition: &SubsetDefinition,
56    patches: &mut Vec<PatchMapEntry>,
57) -> Result<(), ReadError> {
58    match ift {
59        Ift::Format1(format_1) => add_intersecting_format1_patches(
60            font,
61            &source_table,
62            format_1,
63            &subset_definition.codepoints,
64            &subset_definition.feature_tags,
65            patches,
66        ),
67        Ift::Format2(format_2) => {
68            add_intersecting_format2_patches(&source_table, format_2, subset_definition, patches)
69        }
70    }
71}
72
73fn add_intersecting_format1_patches(
74    font: &FontRef,
75    source_table: &IftTableTag,
76    map: &PatchMapFormat1,
77    codepoints: &IntSet<u32>,
78    features: &FeatureSet,
79    patches: &mut Vec<PatchMapEntry>,
80) -> Result<(), ReadError> {
81    // Step 0: Top Level Field Validation
82    let maxp = font.maxp()?;
83    if map.glyph_count() != Uint24::new(maxp.num_glyphs() as u32) {
84        return Err(ReadError::MalformedData(
85            "IFT glyph count must match maxp glyph count.",
86        ));
87    }
88
89    let patches_start = patches.len();
90
91    let max_entry_index = map.max_entry_index();
92    let max_glyph_map_entry_index = map.max_glyph_map_entry_index();
93    if max_glyph_map_entry_index > max_entry_index {
94        return Err(ReadError::MalformedData(
95            "max_glyph_map_entry_index() must be >= max_entry_index().",
96        ));
97    }
98
99    let url_template = map.url_template();
100    let format = PatchFormat::from_format_number(map.patch_format())?;
101
102    // Step 1: Collect the glyph and feature map entries.
103    let charmap = Charmap::new(font);
104    let entries = if PatchFormat::is_invalidating_format(map.patch_format()) {
105        intersect_format1_glyph_and_feature_map::<true>(&charmap, map, codepoints, features)?
106    } else {
107        intersect_format1_glyph_and_feature_map::<false>(&charmap, map, codepoints, features)?
108    };
109
110    // Step 2: produce final output.
111    let mut applied_entries_indices: HashMap<PatchUrl, IntSet<u32>> = Default::default();
112    let applied_entries_start_bit_index = map.applied_entries_bitmap_byte_range().start * 8;
113
114    for (index, subset_def) in entries
115        .into_iter()
116        // Entry 0 is the entry for codepoints already in the font, so it's always considered applied and skipped.
117        .filter(|(index, _)| *index > 0)
118        .filter(|(index, _)| !map.is_entry_applied(*index))
119    {
120        let url = PatchUrl::expand_template(url_template, &PatchId::Numeric(index as u32))
121            .map_err(|_| {
122                ReadError::MalformedData("Failure expanding url template in format 1 patch map.")
123            })?;
124        let intersection_info = if PatchFormat::is_invalidating_format(map.patch_format()) {
125            IntersectionInfo::from_subset(
126                subset_def,
127                // For format 1 the entry index is the "order",
128                // see: https://w3c.github.io/IFT/Overview.html#font-patch-invalidations
129                index.into(),
130            )
131        } else {
132            // For non-invalidating entries we only need to know the order (index here).
133            IntersectionInfo::from_order(index.into())
134        };
135
136        applied_entries_indices
137            .entry(url.clone())
138            .or_default()
139            .insert(applied_entries_start_bit_index as u32 + index as u32);
140
141        patches.push(url.into_format_1_entry(source_table.clone(), format, intersection_info));
142    }
143
144    if patches.len() > patches_start {
145        for p in patches[patches_start..].iter_mut() {
146            if let Some(indices) = applied_entries_indices.get(&p.url) {
147                p.application_bit_indices = indices.clone();
148            }
149        }
150    }
151
152    Ok(())
153}
154
155fn intersect_format1_glyph_and_feature_map<const RECORD_INTERSECTION: bool>(
156    charmap: &Charmap,
157    map: &PatchMapFormat1,
158    codepoints: &IntSet<u32>,
159    features: &FeatureSet,
160) -> Result<BTreeMap<u16, SubsetDefinition>, ReadError> {
161    let mut entries = Default::default();
162    intersect_format1_glyph_map::<RECORD_INTERSECTION>(charmap, map, codepoints, &mut entries)?;
163    intersect_format1_feature_map::<RECORD_INTERSECTION>(map, features, &mut entries)?;
164    Ok(entries)
165}
166
167fn intersect_format1_glyph_map<const RECORD_INTERSECTION: bool>(
168    charmap: &Charmap,
169    map: &PatchMapFormat1,
170    codepoints: &IntSet<u32>,
171    entries: &mut BTreeMap<u16, SubsetDefinition>,
172) -> Result<(), ReadError> {
173    if codepoints.is_inverted() {
174        // TODO(garretrieger): consider invoking this path if codepoints set is above a size threshold
175        //                     relative to the fonts cmap.
176        let cp_gids = charmap
177            .mappings()
178            .filter(|(cp, _)| codepoints.contains(*cp))
179            .map(|(cp, gid)| (cp, gid.to_u32()));
180        return intersect_format1_glyph_map_inner::<RECORD_INTERSECTION>(map, cp_gids, entries);
181    }
182
183    // TODO(garretrieger): since codepoints are looked up in sorted order we may be able to speed up the charmap lookup
184    // (eg. walking the charmap in parallel with the codepoints, or caching the last binary search index)
185    let cp_gids = codepoints
186        .iter()
187        .flat_map(|cp| charmap.map(cp).map(|gid| (cp, gid.to_u32())));
188    intersect_format1_glyph_map_inner::<RECORD_INTERSECTION>(map, cp_gids, entries)
189}
190
191fn intersect_format1_glyph_map_inner<const RECORD_INTERSECTION: bool>(
192    map: &PatchMapFormat1,
193    gids: impl Iterator<Item = (u32, u32)>,
194    entries: &mut BTreeMap<u16, SubsetDefinition>,
195) -> Result<(), ReadError> {
196    let glyph_map = map.glyph_map()?;
197    if glyph_map.entry_index_byte_range().end > glyph_map.offset_data().len() {
198        return Err(ReadError::OutOfBounds);
199    }
200    let first_gid = glyph_map.first_mapped_glyph() as u32;
201    let max_glyph_map_entry_index = map.max_glyph_map_entry_index();
202
203    for (cp, gid) in gids {
204        let entry_index = if gid < first_gid {
205            0
206        } else {
207            glyph_map
208                .entry_index()
209                // TODO(garretrieger): this branches to determine item size on each individual lookup, would
210                //                     likely be faster if we bypassed that since all items have the same length.
211                .get((gid - first_gid) as usize)?
212                .get()
213        };
214        if entry_index > max_glyph_map_entry_index {
215            continue;
216        }
217
218        let e = entries.entry(entry_index);
219        let subset = e.or_default();
220        if RECORD_INTERSECTION {
221            subset.codepoints.insert(cp);
222        }
223    }
224
225    Ok(())
226}
227
228fn intersect_format1_feature_map<const RECORD_INTERSECTION: bool>(
229    map: &PatchMapFormat1,
230    features: &FeatureSet,
231    entries: &mut BTreeMap<u16, SubsetDefinition>,
232) -> Result<(), ReadError> {
233    let Some(feature_map) = map.feature_map() else {
234        return Ok(());
235    };
236    let feature_map = feature_map?;
237
238    let max_entry_index = map.max_entry_index();
239    let max_glyph_map_entry_index = map.max_glyph_map_entry_index();
240    let entry_map_record_size = if max_entry_index < 256 {
241        2usize
242    } else {
243        4usize
244    };
245
246    if feature_map.feature_records_byte_range().end > feature_map.offset_data().len() {
247        return Err(ReadError::OutOfBounds);
248    }
249
250    // We need to check up front there is enough data for all of the listed entry records, this
251    // isn't checked by the read_fonts generated code. Specification requires the operation to fail
252    // up front if the data is too short.
253    if feature_map.entry_records_size(max_entry_index)? > feature_map.entry_map_data().len() {
254        return Err(ReadError::OutOfBounds);
255    }
256
257    let mut maybe_tag_it = match features {
258        FeatureSet::All => None,
259        FeatureSet::Set(f) => Some(f.iter().peekable()),
260    };
261    let mut record_it = feature_map.feature_records().iter().peekable();
262
263    let mut cumulative_entry_map_count: usize = 0;
264    let mut largest_tag: Option<Tag> = None;
265    loop {
266        let record = if let Some(tag_it) = &mut maybe_tag_it {
267            let Some((tag, record)) = tag_it.peek().cloned().zip(record_it.peek().cloned()) else {
268                break;
269            };
270            let record = record?;
271
272            if *tag > record.feature_tag() {
273                cumulative_entry_map_count = cumulative_entry_map_count
274                    .checked_add(record.entry_map_count().get() as usize)
275                    .ok_or(ReadError::OutOfBounds)?;
276                record_it.next();
277                continue;
278            }
279
280            if let Some(largest_tag) = largest_tag {
281                if *tag <= largest_tag {
282                    // Out of order or duplicate tag, skip this record.
283                    tag_it.next();
284                    continue;
285                }
286            }
287
288            largest_tag = Some(*tag);
289
290            if *tag < record.feature_tag() {
291                tag_it.next();
292                continue;
293            }
294
295            let Some(record) = record_it.next() else {
296                break;
297            };
298            record?
299        } else {
300            // Specialization where the target set matches all feature records.
301            let Some(record) = record_it.next() else {
302                break;
303            };
304            let record = record?;
305
306            if let Some(largest_tag) = largest_tag {
307                if record.feature_tag() <= largest_tag {
308                    // Out of order or duplicate tag, skip this record.
309                    cumulative_entry_map_count = cumulative_entry_map_count
310                        .checked_add(record.entry_map_count().get() as usize)
311                        .ok_or(ReadError::OutOfBounds)?;
312                    continue;
313                }
314            }
315
316            largest_tag = Some(record.feature_tag());
317            record
318        };
319
320        let entry_count = record.entry_map_count().get();
321
322        for i in 0..entry_count {
323            let index = i as usize + cumulative_entry_map_count;
324            let byte_index = index * entry_map_record_size;
325            let data = FontData::new(&feature_map.entry_map_data()[byte_index..]);
326            let mapped_entry_index = record.first_new_entry_index().get() as u32 + i as u32;
327            let entry_record = EntryMapRecord::read(data, max_entry_index)?;
328            let first = entry_record.first_entry_index().get();
329            let last = entry_record.last_entry_index().get();
330            if first > last
331                || first > max_glyph_map_entry_index
332                || last > max_glyph_map_entry_index
333                || mapped_entry_index <= max_glyph_map_entry_index as u32
334                || mapped_entry_index > max_entry_index as u32
335            {
336                // Invalid, continue on
337                continue;
338            }
339
340            // If any entries exist which intersect the range of this record add all of their subset defs
341            // to the new entry.
342            merge_intersecting_entries::<RECORD_INTERSECTION>(
343                first..=last,
344                mapped_entry_index as u16,
345                record.feature_tag(),
346                entries,
347            );
348        }
349
350        cumulative_entry_map_count = cumulative_entry_map_count
351            .checked_add(entry_count as usize)
352            .ok_or(ReadError::OutOfBounds)?;
353    }
354
355    Ok(())
356}
357
358fn merge_intersecting_entries<const RECORD_INTERSECTION: bool>(
359    intersection: RangeInclusive<u16>,
360    mapped_entry_index: u16,
361    mapped_tag: Tag,
362    entries: &mut BTreeMap<u16, SubsetDefinition>,
363) {
364    let mut range = entries.range(intersection).peekable();
365    let merged_subset_def = if range.peek().is_some() {
366        let mut merged_subset_def = SubsetDefinition::default();
367        if RECORD_INTERSECTION {
368            range.for_each(|(_, subset_def)| {
369                merged_subset_def.union(subset_def);
370            });
371            merged_subset_def
372                .feature_tags
373                .extend([mapped_tag].iter().copied());
374        }
375        Some(merged_subset_def)
376    } else {
377        None
378    };
379    if let Some(merged_subset_def) = merged_subset_def {
380        entries
381            .entry(mapped_entry_index)
382            .or_default()
383            .union(&merged_subset_def);
384    }
385}
386
387struct EntryIntersectionCache<'a> {
388    entries: &'a [Format2Entry],
389    target_subset_definition: &'a SubsetDefinition,
390    cache: HashMap<usize, bool>,
391    coverage_cache: HashMap<usize, SubsetDefinition>,
392}
393
394impl EntryIntersectionCache<'_> {
395    /// Returns true if the target_subset_definition intersects the entry at index.
396    fn intersects(&mut self, index: usize) -> bool {
397        if let Some(result) = self.cache.get(&index) {
398            return *result;
399        }
400
401        let Some(entry) = self.entries.get(index) else {
402            return false;
403        };
404
405        let result = self.compute_intersection(entry);
406        self.cache.insert(index, result);
407        result
408    }
409
410    /// Returns the intersection of target_subset_definition and the union of entry and all of it's descendants.
411    fn coverage_intersection(&mut self, index: usize) -> SubsetDefinition {
412        if let Some(result) = self.coverage_cache.get(&index) {
413            return result.clone();
414        }
415
416        let Some(entry) = self.entries.get(index) else {
417            return Default::default();
418        };
419
420        let mut self_intersection = entry
421            .subset_definition
422            .intersection(self.target_subset_definition);
423        for child_index in entry.child_indices.iter() {
424            self_intersection.union(&self.coverage_intersection(*child_index));
425        }
426
427        self.coverage_cache
428            .entry(index)
429            .or_insert(self_intersection)
430            .clone()
431    }
432
433    fn compute_intersection(&mut self, entry: &Format2Entry) -> bool {
434        // See: https://w3c.github.io/IFT/Overview.html#abstract-opdef-check-entry-intersection
435        if !entry.intersects(self.target_subset_definition) {
436            return false;
437        }
438
439        if entry.child_indices.is_empty() {
440            return true;
441        }
442
443        if entry.conjunctive_child_match {
444            self.all_children_intersect(entry)
445        } else {
446            self.some_children_intersect(entry)
447        }
448    }
449
450    fn all_children_intersect(&mut self, entry: &Format2Entry) -> bool {
451        for child_index in entry.child_indices.iter() {
452            if !self.intersects(*child_index) {
453                return false;
454            }
455        }
456        true
457    }
458
459    fn some_children_intersect(&mut self, entry: &Format2Entry) -> bool {
460        for child_index in entry.child_indices.iter() {
461            if self.intersects(*child_index) {
462                return true;
463            }
464        }
465        false
466    }
467}
468
469fn add_intersecting_format2_patches(
470    source_table: &IftTableTag,
471    map: &PatchMapFormat2,
472    subset_definition: &SubsetDefinition,
473    patches: &mut Vec<PatchMapEntry>,
474) -> Result<(), ReadError> {
475    let entries = decode_format2_entries(map)?;
476
477    // Caches the result of intersection check for an entry index.
478    let mut entry_intersection_cache = EntryIntersectionCache {
479        entries: &entries,
480        cache: Default::default(),
481        coverage_cache: Default::default(),
482        target_subset_definition: subset_definition,
483    };
484
485    let mut application_bit_indices: HashMap<PatchUrl, IntSet<u32>> = Default::default();
486    let new_patches_first_index = patches.len();
487
488    for (order, e) in entries.iter().enumerate() {
489        if e.ignored {
490            continue;
491        }
492
493        if !entry_intersection_cache.intersects(order) {
494            continue;
495        }
496
497        let mut it = e.urls.iter();
498        let Some(first_url) = it.next() else {
499            continue;
500        };
501
502        // for invalidating keyed patches we need to record information about
503        // intersection size to use later for patch selection. Only the first
504        // url in an entry needs to be updated because only the first url is
505        // used for selection.
506        let intersection_info = if e.format.is_invalidating() {
507            IntersectionInfo::from_subset(
508                entry_intersection_cache.coverage_intersection(order),
509                order,
510            )
511        } else {
512            // non-invalidating entries still require information on entry order so just record that.
513            IntersectionInfo::from_order(order)
514        };
515        let preload_urls: Vec<PatchUrl> = it.cloned().collect();
516        patches.push(first_url.clone().into_format_2_entry(
517            preload_urls,
518            source_table.clone(),
519            e.format,
520            intersection_info,
521        ));
522
523        application_bit_indices
524            .entry(first_url.clone())
525            .or_default()
526            .insert(e.application_flag_bit_index);
527    }
528
529    // In format 2 there may be non intersected entries that have urls
530    // that are the same as other intersected entries. We need to record
531    // the application bit indices for these
532    //
533    // So reloop through all decoded entries and collect the indices for
534    // any which match an intersected entry.
535    for e in entries.iter().filter(|e| !e.ignored) {
536        let Some(first_url) = e.urls.first() else {
537            continue;
538        };
539
540        if let Some(indices) = application_bit_indices.get_mut(first_url) {
541            indices.insert(e.application_flag_bit_index);
542        }
543    }
544
545    // Lastly copy the aggregated application bit indices back into
546    // the individual patch map entries.
547    if patches.len() > new_patches_first_index {
548        for p in patches[new_patches_first_index..].iter_mut() {
549            p.application_bit_indices = application_bit_indices
550                .get(&p.url)
551                .cloned()
552                .unwrap_or_default();
553        }
554    }
555
556    Ok(())
557}
558
559fn decode_format2_entries(map: &PatchMapFormat2) -> Result<Vec<Format2Entry>, ReadError> {
560    let url_template = map.url_template();
561    let entries_data = map.entries()?.entry_data();
562    let default_encoding = PatchFormat::from_format_number(map.default_patch_format())?;
563
564    let mut entry_count = map.entry_count().to_u32();
565    let mut entries_data = FontData::new(entries_data);
566    let mut entries: Vec<Format2Entry> = vec![];
567
568    let mut entry_start_byte = map.entries_offset().to_u32() as usize;
569
570    let mut id_string_data = map
571        .entry_id_string_data()
572        .transpose()?
573        .map(|table| table.id_data())
574        .map(Cursor::new);
575
576    let mut last_entry_id = if id_string_data.is_none() {
577        PatchId::Numeric(0)
578    } else {
579        PatchId::String(vec![])
580    };
581
582    while entry_count > 0 {
583        let consumed_bytes;
584        // TODO(garretrieger): processing context type ovject to reduce argument passing to decode_format2_entry(...)
585        (entries_data, consumed_bytes) = decode_format2_entry(
586            entries_data,
587            entry_start_byte,
588            url_template,
589            &default_encoding,
590            &mut id_string_data,
591            &mut entries,
592            &mut last_entry_id,
593        )?;
594        entry_start_byte += consumed_bytes;
595        entry_count -= 1;
596    }
597
598    Ok(entries)
599}
600
601fn decode_format2_entry<'a>(
602    data: FontData<'a>,
603    data_start_index: usize,
604    url_template: &[u8],
605    default_format: &PatchFormat,
606    id_string_data: &mut Option<Cursor<&[u8]>>,
607    entries: &mut Vec<Format2Entry>,
608    last_entry_id: &mut PatchId,
609) -> Result<(FontData<'a>, usize), ReadError> {
610    let entry_data = EntryData::read(data)?;
611
612    // Record the index of the bit which when set causes this entry to be ignored.
613    // See: https://w3c.github.io/IFT/Overview.html#mapping-entry-formatflags
614    let mut entry: Format2Entry =
615        Format2Entry::base_entry(*default_format, (data_start_index as u32 * 8) + 6);
616
617    // Features
618    if let Some(features) = entry_data.feature_tags() {
619        entry
620            .subset_definition
621            .feature_tags
622            .extend(features.iter().map(|t| t.get()));
623    }
624
625    // Copy indices
626    if let (Some(child_indices), Some(match_mode)) = (
627        entry_data.child_indices(),
628        entry_data.match_mode_and_count(),
629    ) {
630        let max_index = entries.len();
631        let it = child_indices.iter().map(|v| Into::<usize>::into(v.get()));
632        for i in it.clone() {
633            if i >= max_index {
634                return Err(ReadError::MalformedData(
635                    "Child index must refer to only prior entries.",
636                ));
637            }
638        }
639        entry.child_indices = it.collect();
640        entry.conjunctive_child_match = match_mode.conjunctive_match();
641    }
642
643    // Design space
644    if let Some(design_space_segments) = entry_data.design_space_segments() {
645        let mut ranges = HashMap::<Tag, RangeSet<Fixed>>::new();
646
647        for dss in design_space_segments {
648            if dss.start() > dss.end() {
649                return Err(ReadError::MalformedData(
650                    "Design space segment start > end.",
651                ));
652            }
653            ranges
654                .entry(dss.axis_tag())
655                .or_default()
656                .insert(dss.start()..=dss.end());
657        }
658
659        entry.subset_definition.design_space = DesignSpace::Ranges(ranges);
660    }
661
662    // Entry ID
663    let (entry_deltas, trailing_data) = if id_string_data.is_some() {
664        decode_format2_entry_deltas::<true>(entry_data.format_flags(), entry_data.trailing_data())?
665    } else {
666        decode_format2_entry_deltas::<false>(entry_data.format_flags(), entry_data.trailing_data())?
667    };
668
669    // Encoding
670    let (patch_format, trailing_data) =
671        decode_format2_patch_format(entry_data.format_flags(), trailing_data)?;
672    entry.format = patch_format.unwrap_or(*default_format);
673
674    // We now have info information to generate the associated urls.
675    entry.populate_urls(url_template, entry_deltas, last_entry_id, id_string_data)?;
676
677    // Codepoints
678    let (codepoints, trailing_data) =
679        decode_format2_codepoints(entry_data.format_flags(), trailing_data)?;
680    if entry.subset_definition.codepoints.is_empty() {
681        // as an optimization move the existing set instead of copying it in if possible.
682        entry.subset_definition.codepoints = codepoints;
683    } else {
684        entry.subset_definition.codepoints.union(&codepoints);
685    }
686
687    // Ignored
688    entry.ignored = entry_data
689        .format_flags()
690        .contains(EntryFormatFlags::IGNORED);
691
692    entries.push(entry);
693
694    let consumed_bytes = entry_data.trailing_data_byte_range().end - trailing_data.len();
695    Ok((FontData::new(trailing_data), consumed_bytes))
696}
697
698fn format2_new_entry_id(
699    delta_or_length: Option<i32>,
700    last_id: &PatchId,
701    id_string_data: &mut Option<Cursor<&[u8]>>,
702) -> Result<PatchId, ReadError> {
703    let Some(id_string_data) = id_string_data else {
704        let last_entry_index = match last_id {
705            PatchId::Numeric(index) => *index,
706            PatchId::String(_) => return Err(ReadError::MalformedData("Unexpected string id.")),
707        };
708
709        return Ok(PatchId::Numeric(compute_format2_new_entry_index(
710            delta_or_length.unwrap_or_default(),
711            last_entry_index,
712        )?));
713    };
714
715    let Some(length) = delta_or_length else {
716        // If no length was provided the spec says to copy the previous entries
717        // id string.
718        let last_id_string = match last_id {
719            PatchId::String(id_string) => id_string.clone(),
720            PatchId::Numeric(_) => return Err(ReadError::MalformedData("Unexpected numeric id.")),
721        };
722        return Ok(PatchId::String(last_id_string));
723    };
724
725    match length.cmp(&0) {
726        Ordering::Equal => return Ok(PatchId::String(Default::default())),
727        Ordering::Less => return Err(ReadError::MalformedData("Negative string length.")),
728        Ordering::Greater => {}
729    };
730
731    let mut id_string: Vec<u8> = vec![0; length as usize];
732    id_string_data
733        .read_exact(id_string.as_mut_slice())
734        .map_err(|_| ReadError::MalformedData("ID string is out of bounds."))?;
735    Ok(PatchId::String(id_string))
736}
737
738fn compute_format2_new_entry_index(delta: i32, last_entry_index: u32) -> Result<u32, ReadError> {
739    let new_index = (last_entry_index as i64) + 1 + (delta as i64);
740
741    if new_index.is_negative() {
742        return Err(ReadError::MalformedData("Negative entry id encountered."));
743    }
744
745    u32::try_from(new_index).map_err(|_| {
746        ReadError::MalformedData("Entry index exceeded maximum size (unsigned 32 bit).")
747    })
748}
749
750fn decode_format2_patch_format(
751    flags: EntryFormatFlags,
752    format_data: &[u8],
753) -> Result<(Option<PatchFormat>, &[u8]), ReadError> {
754    if !flags.contains(EntryFormatFlags::PATCH_FORMAT) {
755        return Ok((None, format_data));
756    }
757
758    let format_byte = format_data.first().ok_or(ReadError::OutOfBounds)?;
759
760    let patch_format = PatchFormat::from_format_number(*format_byte)?;
761
762    Ok((Some(patch_format), &format_data[1..]))
763}
764
765fn decode_format2_entry_deltas<const HAS_STRING_DATA: bool>(
766    flags: EntryFormatFlags,
767    delta_data: &[u8],
768) -> Result<(Vec<i32>, &[u8]), ReadError> {
769    if !flags.contains(EntryFormatFlags::ENTRY_ID_DELTA) {
770        return Ok((vec![], delta_data));
771    }
772
773    let mut result: Vec<i32> = vec![];
774    const WIDTH: usize = 3;
775    let mut index = 0usize;
776    loop {
777        let (value, has_more) =
778            decode_format2_entry_delta::<HAS_STRING_DATA>(&delta_data[index * WIDTH..])?;
779        result.push(value);
780        index += 1;
781
782        if !has_more {
783            break;
784        }
785    }
786
787    Ok((result, &delta_data[index * WIDTH..]))
788}
789
790fn decode_format2_entry_delta<const HAS_STRING_DATA: bool>(
791    delta_data: &[u8],
792) -> Result<(i32, bool), ReadError> {
793    if HAS_STRING_DATA {
794        // For length values the most significant bit signals the presence of
795        // another value. The remaining bits are the length value (unsigned).
796        let val: Uint24 = FontData::new(delta_data).read_at(0)?;
797        let val = val.to_u32();
798        let has_more = (val & (1 << 23)) > 0;
799        let val = val & !(1 << 23);
800        Ok((val as i32, has_more))
801    } else {
802        // For delta values the least significant bit signals the presence of
803        // another value. The delta is computed by dividing the entire value (signed)
804        // by 2
805        let val: Int24 = FontData::new(delta_data).read_at(0)?;
806        let val: i32 = val.to_i32();
807        let has_more = (val & 1) > 0;
808        let val = val / 2;
809        Ok((val, has_more))
810    }
811}
812
813fn decode_format2_codepoints(
814    flags: EntryFormatFlags,
815    codepoint_data: &[u8],
816) -> Result<(IntSet<u32>, &[u8]), ReadError> {
817    let format =
818        flags.intersection(EntryFormatFlags::CODEPOINTS_BIT_1 | EntryFormatFlags::CODEPOINTS_BIT_2);
819
820    if format.bits() == 0 {
821        return Ok((IntSet::<u32>::empty(), codepoint_data));
822    }
823
824    // See: https://w3c.github.io/IFT/Overview.html#abstract-opdef-interpret-format-2-patch-map-entry
825    // for interpretation of codepoint bit balues.
826    let codepoint_data = FontData::new(codepoint_data);
827    let (bias, skipped) = if format == EntryFormatFlags::CODEPOINTS_BIT_2 {
828        (codepoint_data.read_at::<u16>(0)? as u32, 2)
829    } else if format == (EntryFormatFlags::CODEPOINTS_BIT_1 | EntryFormatFlags::CODEPOINTS_BIT_2) {
830        (codepoint_data.read_at::<Uint24>(0)?.to_u32(), 3)
831    } else {
832        (0, 0)
833    };
834
835    let Some(codepoint_data) = codepoint_data.split_off(skipped) else {
836        return Err(ReadError::MalformedData("Codepoints data is too short."));
837    };
838
839    let (set, remaining_data) =
840        IntSet::<u32>::from_sparse_bit_set_bounded(codepoint_data.as_bytes(), bias, 0x10FFFF)
841            .map_err(|_| {
842                ReadError::MalformedData("Failed to decode sparse bit set data stream.")
843            })?;
844
845    Ok((set, remaining_data))
846}
847
848/// Models the encoding type for a incremental font transfer patch.
849/// See: <https://w3c.github.io/IFT/Overview.html#font-patch-formats-summary>
850#[derive(Clone, Eq, PartialEq, Debug, Hash, Copy)]
851pub enum PatchFormat {
852    TableKeyed { fully_invalidating: bool },
853    GlyphKeyed,
854}
855
856impl PatchFormat {
857    fn is_invalidating(&self) -> bool {
858        matches!(self, PatchFormat::TableKeyed { .. })
859    }
860
861    fn is_invalidating_format(format: u8) -> bool {
862        match format {
863            1 | 2 => true,
864            3 => false,
865            _ => false,
866        }
867    }
868
869    fn from_format_number(format: u8) -> Result<Self, ReadError> {
870        // Based on https://w3c.github.io/IFT/Overview.html#font-patch-formats-summary
871        match format {
872            1 => Ok(Self::TableKeyed {
873                fully_invalidating: true,
874            }),
875            2 => Ok(Self::TableKeyed {
876                fully_invalidating: false,
877            }),
878            3 => Ok(Self::GlyphKeyed),
879            _ => Err(ReadError::MalformedData("Invalid format number.")),
880        }
881    }
882}
883
884/// Id for a patch which will be subbed into a URL template. The spec allows integer or string IDs.
885#[derive(Debug, Clone, Eq, PartialEq, Hash)]
886pub enum PatchId {
887    Numeric(u32),
888    String(Vec<u8>), // TODO(garretrieger): Make this a reference?
889}
890
891/// List of possible IFT mapping table tags.
892#[derive(PartialEq, Eq, Debug, Clone, Hash)]
893pub(crate) enum IftTableTag {
894    Ift(CompatibilityId),
895    Iftx(CompatibilityId),
896}
897
898impl IftTableTag {
899    pub(crate) fn tables_in<'a>(
900        font: &'a FontRef,
901    ) -> Result<impl Iterator<Item = (IftTableTag, Ift<'a>)>, ReadError> {
902        let ift = font
903            .data_for_tag(IFT_TAG)
904            .map(Ift::read)
905            .transpose()?
906            .map(|t| (IftTableTag::Ift(t.compatibility_id()), t))
907            .into_iter();
908        let iftx = font
909            .data_for_tag(IFTX_TAG)
910            .map(Ift::read)
911            .transpose()?
912            .map(|t| (IftTableTag::Iftx(t.compatibility_id()), t))
913            .into_iter();
914
915        Ok(ift.chain(iftx))
916    }
917
918    pub(crate) fn font_compat_id(&self, font: &FontRef) -> Result<CompatibilityId, ReadError> {
919        Ok(self.mapping_table(font)?.compatibility_id())
920    }
921
922    pub(crate) fn tag(&self) -> Tag {
923        match self {
924            Self::Ift(_) => IFT_TAG,
925            Self::Iftx(_) => IFTX_TAG,
926        }
927    }
928
929    pub(crate) fn mapping_table<'a>(&self, font: &'a FontRef) -> Result<Ift<'a>, ReadError> {
930        font.expect_data_for_tag(self.tag())
931            .and_then(FontRead::read)
932    }
933
934    pub(crate) fn expected_compat_id(&self) -> &CompatibilityId {
935        match self {
936            Self::Ift(cid) | Self::Iftx(cid) => cid,
937        }
938    }
939}
940
941/// Stores a collection of URLs associated with each patch mapping entry.
942///
943/// Each entry has a primary URL which is what is loaded and applied when the entry is selected.
944/// Additionally each entry has an optional set of preload URL's which should be preloaded if the
945/// entry is selected
946#[derive(PartialEq, Eq, Debug, Clone)]
947pub struct PatchMapEntry {
948    pub(crate) url: PatchUrl,
949    pub(crate) preload_urls: Vec<PatchUrl>,
950    pub(crate) format: PatchFormat,
951    pub(crate) source_table: IftTableTag,
952    pub(crate) application_bit_indices: IntSet<u32>,
953    pub(crate) intersection_info: IntersectionInfo,
954}
955
956impl PatchMapEntry {
957    pub fn url(&self) -> &PatchUrl {
958        &self.url
959    }
960
961    pub fn format(&self) -> PatchFormat {
962        self.format
963    }
964
965    pub(crate) fn expected_compat_id(&self) -> &CompatibilityId {
966        self.source_table.expected_compat_id()
967    }
968}
969
970/// An expanded PatchUrl string which identifies where a patch is located.
971#[derive(Debug, Clone, Eq, PartialEq, Hash, PartialOrd, Ord)]
972pub struct PatchUrl(pub String);
973
974impl PatchUrl {
975    pub(crate) fn expand_template(
976        template_string: &[u8],
977        patch_id: &PatchId,
978    ) -> Result<Self, UrlTemplateError> {
979        url_templates::expand_template(template_string, patch_id).map(Self)
980    }
981
982    pub(crate) fn into_format_1_entry(
983        self,
984        source_table: IftTableTag,
985        format: PatchFormat,
986        intersection_info: IntersectionInfo,
987    ) -> PatchMapEntry {
988        PatchMapEntry {
989            url: self,
990            preload_urls: vec![], // Format 1 has no preload urls
991            format,
992            source_table,
993            application_bit_indices: IntSet::<u32>::empty(), // these are populated later on
994            intersection_info,
995        }
996    }
997
998    fn into_format_2_entry(
999        self,
1000        preload_urls: Vec<PatchUrl>,
1001        source_table: IftTableTag,
1002        format: PatchFormat,
1003        intersection_info: IntersectionInfo,
1004    ) -> PatchMapEntry {
1005        PatchMapEntry {
1006            url: self,
1007            preload_urls,
1008            format,
1009            source_table,
1010            application_bit_indices: IntSet::<u32>::empty(), // these are populated later on
1011            intersection_info,
1012        }
1013    }
1014}
1015
1016impl AsRef<str> for PatchUrl {
1017    fn as_ref(&self) -> &str {
1018        &self.0
1019    }
1020}
1021
1022/// Stores information on the intersection which lead to the selection of this patch.
1023///
1024/// Intersection details are used later on to choose a specific patch to apply next.
1025/// See: <https://w3c.github.io/IFT/Overview.html#invalidating-patch-selection>
1026#[derive(Debug, Clone, Eq, PartialEq, Hash, Default)]
1027pub(crate) struct IntersectionInfo {
1028    intersecting_codepoints: u64,
1029    intersecting_layout_tags: usize,
1030    intersecting_design_space: BTreeMap<Tag, Fixed>,
1031    entry_order: usize,
1032}
1033
1034impl PartialOrd for IntersectionInfo {
1035    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
1036        Some(self.cmp(other))
1037    }
1038}
1039
1040impl Ord for IntersectionInfo {
1041    fn cmp(&self, other: &Self) -> Ordering {
1042        // See: https://w3c.github.io/IFT/Overview.html#invalidating-patch-selection
1043        // for information on how these are ordered.
1044        match self
1045            .intersecting_codepoints
1046            .cmp(&other.intersecting_codepoints)
1047        {
1048            Ordering::Equal => {}
1049            ord => return ord,
1050        }
1051        match self
1052            .intersecting_layout_tags
1053            .cmp(&other.intersecting_layout_tags)
1054        {
1055            Ordering::Equal => {}
1056            ord => return ord,
1057        }
1058        match self
1059            .intersecting_design_space
1060            .cmp(&other.intersecting_design_space)
1061        {
1062            Ordering::Equal => {}
1063            ord => return ord,
1064        }
1065
1066        // We select the largest intersection info, and the spec requires in ties that the lowest entry order
1067        // is selected. So reverse the ordering of comparing entry_order.
1068        self.entry_order.cmp(&other.entry_order).reverse()
1069    }
1070}
1071
1072impl IntersectionInfo {
1073    fn from_subset(value: SubsetDefinition, order: usize) -> Self {
1074        Self {
1075            intersecting_codepoints: value.codepoints.len(),
1076            intersecting_layout_tags: value.feature_tags.len(),
1077            intersecting_design_space: Self::design_space_size(value.design_space),
1078            entry_order: order,
1079        }
1080    }
1081
1082    fn from_order(entry_order: usize) -> Self {
1083        Self {
1084            entry_order,
1085            ..Default::default()
1086        }
1087    }
1088
1089    fn design_space_size(value: DesignSpace) -> BTreeMap<Tag, Fixed> {
1090        match value {
1091            DesignSpace::All => Default::default(),
1092            DesignSpace::Ranges(value) => value
1093                .into_iter()
1094                .map(|(tag, ranges)| {
1095                    let total = ranges
1096                        .iter()
1097                        .map(|range| *range.end() - *range.start())
1098                        .fold(Fixed::ZERO, |acc, x| acc + x);
1099
1100                    (tag, total)
1101                })
1102                .collect(),
1103        }
1104    }
1105
1106    pub(crate) fn entry_order(&self) -> usize {
1107        self.entry_order
1108    }
1109}
1110
1111/// Stores a set of features tags, can additionally represent all features.
1112#[derive(Debug, Clone, PartialEq)]
1113pub enum FeatureSet {
1114    Set(BTreeSet<Tag>),
1115    All,
1116}
1117
1118impl Default for FeatureSet {
1119    fn default() -> Self {
1120        Self::Set(Default::default())
1121    }
1122}
1123
1124impl FeatureSet {
1125    fn len(&self) -> usize {
1126        match self {
1127            Self::All => usize::MAX,
1128            Self::Set(set) => set.len(),
1129        }
1130    }
1131
1132    /// Add tag to this feature set.
1133    ///
1134    /// Returns true if the tag was newly inserted.
1135    pub fn insert(&mut self, tag: Tag) -> bool {
1136        match self {
1137            FeatureSet::All => false,
1138            FeatureSet::Set(feature_set) => feature_set.insert(tag),
1139        }
1140    }
1141
1142    pub fn extend<It>(&mut self, tags: It)
1143    where
1144        It: Iterator<Item = Tag>,
1145    {
1146        match self {
1147            FeatureSet::All => {}
1148            FeatureSet::Set(feature_set) => {
1149                feature_set.extend(tags);
1150            }
1151        }
1152    }
1153}
1154
1155/// Stores a collection of ranges across zero or more axes.
1156#[derive(Debug, Clone, PartialEq)]
1157pub enum DesignSpace {
1158    Ranges(HashMap<Tag, RangeSet<Fixed>>),
1159    All,
1160}
1161
1162impl Default for DesignSpace {
1163    fn default() -> Self {
1164        Self::Ranges(Default::default())
1165    }
1166}
1167
1168impl DesignSpace {
1169    fn is_empty(&self) -> bool {
1170        match self {
1171            Self::All => false,
1172            Self::Ranges(ranges) => ranges.is_empty(),
1173        }
1174    }
1175}
1176
1177/// Stores a description of a font subset over codepoints, feature tags, and design space.
1178#[derive(Debug, Clone, PartialEq, Default)]
1179pub struct SubsetDefinition {
1180    pub codepoints: IntSet<u32>,
1181    pub feature_tags: FeatureSet,
1182    pub design_space: DesignSpace,
1183}
1184
1185impl SubsetDefinition {
1186    pub fn new(
1187        codepoints: IntSet<u32>,
1188        feature_tags: FeatureSet,
1189        design_space: DesignSpace,
1190    ) -> SubsetDefinition {
1191        SubsetDefinition {
1192            codepoints,
1193            feature_tags,
1194            design_space,
1195        }
1196    }
1197
1198    pub fn codepoints(codepoints: IntSet<u32>) -> SubsetDefinition {
1199        SubsetDefinition {
1200            codepoints,
1201            feature_tags: FeatureSet::Set(Default::default()),
1202            design_space: Default::default(),
1203        }
1204    }
1205
1206    /// Returns a SubsetDefinition which includes all things.
1207    pub fn all() -> SubsetDefinition {
1208        SubsetDefinition {
1209            codepoints: IntSet::all(),
1210            feature_tags: FeatureSet::All,
1211            design_space: DesignSpace::All,
1212        }
1213    }
1214
1215    pub fn union(&mut self, other: &SubsetDefinition) {
1216        self.codepoints.union(&other.codepoints);
1217
1218        match &other.feature_tags {
1219            FeatureSet::All => self.feature_tags = FeatureSet::All,
1220            FeatureSet::Set(set) => self.feature_tags.extend(set.iter().copied()),
1221        };
1222
1223        match (&other.design_space, &mut self.design_space) {
1224            (_, DesignSpace::All) | (DesignSpace::All, _) => self.design_space = DesignSpace::All,
1225            (DesignSpace::Ranges(other_ranges), DesignSpace::Ranges(self_ranges)) => {
1226                for (tag, segments) in other_ranges.iter() {
1227                    self_ranges.entry(*tag).or_default().extend(segments.iter());
1228                }
1229            }
1230        };
1231    }
1232
1233    fn intersection(&self, other: &Self) -> Self {
1234        let mut result: SubsetDefinition = self.clone();
1235
1236        result.codepoints.intersect(&other.codepoints);
1237
1238        match (&self.feature_tags, &other.feature_tags) {
1239            (FeatureSet::All, FeatureSet::Set(tags)) => {
1240                result.feature_tags = FeatureSet::Set(tags.clone());
1241            }
1242            (FeatureSet::Set(a), FeatureSet::Set(b)) => {
1243                result.feature_tags = FeatureSet::Set(a.intersection(b).copied().collect());
1244            }
1245            // In these cases result already has the correct intersection
1246            (FeatureSet::All, FeatureSet::All) => {}
1247            (FeatureSet::Set(_), FeatureSet::All) => {}
1248        };
1249
1250        result.design_space = self.design_space_intersection(&other.design_space);
1251
1252        result
1253    }
1254
1255    fn design_space_intersection(&self, other_design_space: &DesignSpace) -> DesignSpace {
1256        match (&self.design_space, other_design_space) {
1257            (DesignSpace::All, DesignSpace::All) => DesignSpace::All,
1258            (DesignSpace::All, DesignSpace::Ranges(ranges)) => DesignSpace::Ranges(ranges.clone()),
1259            (DesignSpace::Ranges(ranges), DesignSpace::All) => DesignSpace::Ranges(ranges.clone()),
1260            (DesignSpace::Ranges(self_ranges), DesignSpace::Ranges(other_ranges)) => {
1261                let mut result: HashMap<Tag, RangeSet<Fixed>> = Default::default();
1262                for (tag, input_segments) in other_ranges {
1263                    let Some(entry_segments) = self_ranges.get(tag) else {
1264                        continue;
1265                    };
1266
1267                    let ranges: RangeSet<Fixed> =
1268                        input_segments.intersection(entry_segments).collect();
1269                    if !ranges.is_empty() {
1270                        result.insert(*tag, ranges);
1271                    }
1272                }
1273
1274                DesignSpace::Ranges(result)
1275            }
1276        }
1277    }
1278}
1279
1280/// Stores a materialized version of an IFT patchmap entry.
1281///
1282/// See: <https://w3c.github.io/IFT/Overview.html#patch-map-dfn>
1283#[derive(Debug, Clone, PartialEq)]
1284struct Format2Entry {
1285    // Key
1286    subset_definition: SubsetDefinition,
1287    child_indices: Vec<usize>,
1288    conjunctive_child_match: bool,
1289    ignored: bool,
1290
1291    // Value
1292    urls: Vec<PatchUrl>,
1293    format: PatchFormat,
1294    application_flag_bit_index: u32,
1295}
1296
1297impl Format2Entry {
1298    fn base_entry(default_format: PatchFormat, application_flag_bit_index: u32) -> Self {
1299        Format2Entry {
1300            subset_definition: Default::default(),
1301            child_indices: vec![],
1302            conjunctive_child_match: false,
1303            ignored: false,
1304            urls: vec![],
1305            format: default_format,
1306            application_flag_bit_index,
1307        }
1308    }
1309
1310    fn intersects(&self, subset_definition: &SubsetDefinition) -> bool {
1311        // Intersection defined here: https://w3c.github.io/IFT/Overview.html#abstract-opdef-check-entry-intersection
1312        let codepoints_intersects = self.subset_definition.codepoints.is_empty()
1313            || self
1314                .subset_definition
1315                .codepoints
1316                .intersects_set(&subset_definition.codepoints);
1317        if !codepoints_intersects {
1318            return false;
1319        }
1320
1321        let features_intersects = match &self.subset_definition.feature_tags {
1322            FeatureSet::All => subset_definition.feature_tags.len() > 0,
1323            FeatureSet::Set(set) => match &subset_definition.feature_tags {
1324                FeatureSet::All => true,
1325                FeatureSet::Set(other) => {
1326                    set.is_empty() || set.intersection(other).next().is_some()
1327                }
1328            },
1329        };
1330
1331        if !features_intersects {
1332            return false;
1333        }
1334
1335        match &self.subset_definition.design_space {
1336            DesignSpace::All => !subset_definition.design_space.is_empty(),
1337            DesignSpace::Ranges(entry_ranges) => match &subset_definition.design_space {
1338                DesignSpace::All => true,
1339                DesignSpace::Ranges(other_ranges) => {
1340                    entry_ranges.is_empty()
1341                        || Self::design_space_intersects(entry_ranges, other_ranges)
1342                }
1343            },
1344        }
1345    }
1346
1347    fn populate_urls(
1348        &mut self,
1349        url_template: &[u8],
1350        deltas: Vec<i32>,
1351        last_id: &mut PatchId,
1352        id_string_data: &mut Option<Cursor<&[u8]>>,
1353    ) -> Result<(), ReadError> {
1354        if deltas.is_empty() {
1355            let next_id = format2_new_entry_id(None, last_id, id_string_data)?;
1356            self.urls.push(
1357                PatchUrl::expand_template(url_template, &next_id).map_err(|_| {
1358                    ReadError::MalformedData("Failed to expand url template in format 2 table.")
1359                })?,
1360            );
1361            *last_id = next_id;
1362            return Ok(());
1363        }
1364
1365        for delta in deltas {
1366            let next_id = format2_new_entry_id(Some(delta), last_id, id_string_data)?;
1367            self.urls.push(
1368                PatchUrl::expand_template(url_template, &next_id).map_err(|_| {
1369                    ReadError::MalformedData("Failed to expand url template in format 2 table.")
1370                })?,
1371            );
1372            *last_id = next_id;
1373        }
1374
1375        Ok(())
1376    }
1377
1378    fn design_space_intersects(
1379        a: &HashMap<Tag, RangeSet<Fixed>>,
1380        b: &HashMap<Tag, RangeSet<Fixed>>,
1381    ) -> bool {
1382        for (tag, a_segments) in a {
1383            let Some(b_segments) = b.get(tag) else {
1384                continue;
1385            };
1386
1387            if a_segments.intersection(b_segments).next().is_some() {
1388                return true;
1389            }
1390        }
1391
1392        false
1393    }
1394}
1395
1396#[cfg(test)]
1397mod tests {
1398    use std::str::FromStr;
1399
1400    use super::*;
1401    use font_test_data as test_data;
1402    use font_test_data::ift::{
1403        child_indices_format2, codepoints_only_format2, custom_ids_format2, feature_map_format1,
1404        features_and_design_space_format2, format1_with_dup_urls, simple_format1,
1405        string_ids_format2, string_ids_format2_with_preloads,
1406        table_keyed_format2_with_preload_urls, u16_entries_format1, ABSOLUTE_URL_TEMPLATE,
1407        RELATIVE_URL_TEMPLATE,
1408    };
1409    use read_fonts::tables::ift::{IFTX_TAG, IFT_TAG};
1410    use read_fonts::types::Int24;
1411    use read_fonts::FontRef;
1412    use write_fonts::FontBuilder;
1413
1414    impl FeatureSet {
1415        fn from<const N: usize>(tags: [Tag; N]) -> FeatureSet {
1416            FeatureSet::Set(BTreeSet::<Tag>::from(tags))
1417        }
1418    }
1419
1420    impl DesignSpace {
1421        fn from<const N: usize>(design_space: [(Tag, RangeSet<Fixed>); N]) -> DesignSpace {
1422            DesignSpace::Ranges(design_space.into_iter().collect())
1423        }
1424    }
1425
1426    impl IntersectionInfo {
1427        pub(crate) fn new(codepoints: u64, features: usize, order: usize) -> Self {
1428            IntersectionInfo {
1429                intersecting_codepoints: codepoints,
1430                intersecting_layout_tags: features,
1431                intersecting_design_space: Default::default(),
1432                entry_order: order,
1433            }
1434        }
1435
1436        pub(crate) fn from_design_space<const N: usize>(
1437            codepoints: u64,
1438            features: usize,
1439            design_space: [(Tag, Fixed); N],
1440            order: usize,
1441        ) -> Self {
1442            IntersectionInfo {
1443                intersecting_codepoints: codepoints,
1444                intersecting_layout_tags: features,
1445                intersecting_design_space: BTreeMap::from(design_space),
1446                entry_order: order,
1447            }
1448        }
1449    }
1450
1451    fn compat_id() -> CompatibilityId {
1452        CompatibilityId::from_u32s([1, 2, 3, 4])
1453    }
1454
1455    fn create_ift_font(font: FontRef, ift: Option<&[u8]>, iftx: Option<&[u8]>) -> Vec<u8> {
1456        let mut builder = FontBuilder::default();
1457
1458        if let Some(bytes) = ift {
1459            builder.add_raw(IFT_TAG, bytes);
1460        }
1461
1462        if let Some(bytes) = iftx {
1463            builder.add_raw(IFTX_TAG, bytes);
1464        }
1465
1466        builder.copy_missing_tables(font);
1467        builder.build()
1468    }
1469
1470    // Format 1 tests:
1471    // TODO(garretrieger): test w/ multi codepoints mapping to the same glyph.
1472    // TODO(garretrieger): test w/ IFT + IFTX both populated tables.
1473    // TODO(garretrieger): test which has entry that has empty codepoint array.
1474    // TODO(garretrieger): test with format 1 that has max entry = 0.
1475    // TODO(garretrieger): font with no maxp.
1476    // TODO(garretrieger): font with MAXP and maxp.
1477    // TODO(garretrieger): test for design space union of SubsetDefinition.
1478    // TODO(garretrieger): fuzzer to check consistency vs intersecting "*" subset def.
1479
1480    #[derive(Clone)]
1481    struct ExpectedEntry {
1482        indices: Vec<u32>,
1483        application_bit_index: IntSet<u32>,
1484        order: usize,
1485    }
1486
1487    fn set(value: u32) -> IntSet<u32> {
1488        let mut set = IntSet::<u32>::empty();
1489        set.insert(value);
1490        set
1491    }
1492
1493    fn f1(index: u32) -> ExpectedEntry {
1494        ExpectedEntry {
1495            indices: vec![index],
1496            application_bit_index: set(index + 36 * 8),
1497            order: index as usize,
1498        }
1499    }
1500
1501    fn f2(index: u32, entry_start: usize, order: usize) -> ExpectedEntry {
1502        ExpectedEntry {
1503            indices: vec![index],
1504            application_bit_index: set(entry_start as u32 * 8 + 6),
1505            order,
1506        }
1507    }
1508
1509    fn f2p(indices: Vec<u32>, entry_start: usize, order: usize) -> ExpectedEntry {
1510        ExpectedEntry {
1511            indices,
1512            application_bit_index: set(entry_start as u32 * 8 + 6),
1513            order,
1514        }
1515    }
1516
1517    fn test_intersection<const M: usize, const N: usize, const O: usize>(
1518        font: &FontRef,
1519        codepoints: [u32; M],
1520        tags: [Tag; N],
1521        expected_entries: [ExpectedEntry; O],
1522    ) {
1523        test_design_space_intersection(
1524            font,
1525            codepoints,
1526            FeatureSet::Set(BTreeSet::<Tag>::from(tags)),
1527            Default::default(),
1528            expected_entries,
1529        )
1530    }
1531
1532    fn test_design_space_intersection<const M: usize, const P: usize>(
1533        font: &FontRef,
1534        codepoints: [u32; M],
1535        tags: FeatureSet,
1536        design_space: DesignSpace,
1537        expected_entries: [ExpectedEntry; P],
1538    ) {
1539        let patches = intersecting_patches(
1540            font,
1541            &SubsetDefinition::new(IntSet::from(codepoints), tags, design_space),
1542        )
1543        .unwrap();
1544
1545        let expected: Vec<_> = expected_entries
1546            .iter()
1547            .map(
1548                |ExpectedEntry {
1549                     indices,
1550                     application_bit_index,
1551                     order,
1552                 }| {
1553                    let mut it = indices.iter().map(|i| {
1554                        PatchUrl::expand_template(RELATIVE_URL_TEMPLATE, &PatchId::Numeric(*i))
1555                            .unwrap()
1556                    });
1557
1558                    let mut e = it.next().unwrap().into_format_2_entry(
1559                        it.collect(),
1560                        IftTableTag::Ift(compat_id()),
1561                        PatchFormat::GlyphKeyed,
1562                        IntersectionInfo::from_order(*order),
1563                    );
1564                    e.application_bit_indices.union(application_bit_index);
1565                    e
1566                },
1567            )
1568            .collect();
1569
1570        assert_eq!(patches, expected);
1571    }
1572
1573    fn test_intersection_with_all<const M: usize, const N: usize>(
1574        font: &FontRef,
1575        tags: [Tag; M],
1576        expected_entries: [ExpectedEntry; N],
1577    ) {
1578        test_intersection_with_all_and_template(font, tags, RELATIVE_URL_TEMPLATE, expected_entries)
1579    }
1580
1581    fn test_intersection_with_all_and_template<const M: usize, const N: usize>(
1582        font: &FontRef,
1583        tags: [Tag; M],
1584        url_template: &[u8],
1585        expected_entries: [ExpectedEntry; N],
1586    ) {
1587        let patches = intersecting_patches(
1588            font,
1589            &SubsetDefinition::new(
1590                IntSet::<u32>::all(),
1591                FeatureSet::from(tags),
1592                Default::default(),
1593            ),
1594        )
1595        .unwrap();
1596
1597        let expected: Vec<_> = expected_entries
1598            .iter()
1599            .map(
1600                |ExpectedEntry {
1601                     indices,
1602                     application_bit_index,
1603                     order,
1604                 }| {
1605                    let mut it = indices.iter().map(|i| {
1606                        PatchUrl::expand_template(url_template, &PatchId::Numeric(*i)).unwrap()
1607                    });
1608                    let mut e = it.next().unwrap().into_format_2_entry(
1609                        it.collect(),
1610                        IftTableTag::Ift(compat_id()),
1611                        PatchFormat::GlyphKeyed,
1612                        IntersectionInfo::from_order(*order),
1613                    );
1614                    e.application_bit_indices.union(application_bit_index);
1615                    e
1616                },
1617            )
1618            .collect();
1619
1620        assert_eq!(expected, patches);
1621    }
1622
1623    fn check_url_template_substitution(template: &[u8], value: u32, expected: &str) {
1624        assert_eq!(
1625            PatchUrl::expand_template(template, &PatchId::Numeric(value))
1626                .unwrap()
1627                .as_ref(),
1628            expected,
1629        );
1630    }
1631
1632    fn check_string_url_template_substitution(template: &[u8], value: &str, expected: &str) {
1633        assert_eq!(
1634            PatchUrl::expand_template(template, &PatchId::String(Vec::from(value.as_bytes())))
1635                .unwrap()
1636                .as_ref(),
1637            expected,
1638        );
1639    }
1640
1641    #[test]
1642    fn url_template_substitution() {
1643        // These test cases are used in other tests.
1644
1645        let foo_bar_id = ABSOLUTE_URL_TEMPLATE;
1646        let foo_bar_d1_d2_id = b"\x0a//foo.bar/\x81\x01/\x82\x01/\x80";
1647        let foo_bar_d1_d2_d3_id = b"\x0a//foo.bar/\x81\x01/\x82\x01/\x83\x01/\x80";
1648        let foo_bar_id64 = b"\x0a//foo.bar/\x85";
1649
1650        check_url_template_substitution(foo_bar_id, 1, "//foo.bar/04");
1651        check_url_template_substitution(foo_bar_id, 2, "//foo.bar/08");
1652        check_url_template_substitution(foo_bar_id, 3, "//foo.bar/0C");
1653        check_url_template_substitution(foo_bar_id, 4, "//foo.bar/0G");
1654        check_url_template_substitution(foo_bar_id, 5, "//foo.bar/0K");
1655
1656        // These test cases are from specification:
1657        // https://w3c.github.io/IFT/Overview.html#url-templates
1658        check_url_template_substitution(foo_bar_id, 0, "//foo.bar/00");
1659        check_url_template_substitution(foo_bar_id, 123, "//foo.bar/FC");
1660        check_url_template_substitution(foo_bar_d1_d2_id, 478, "//foo.bar/0/F/07F0");
1661        check_url_template_substitution(foo_bar_d1_d2_d3_id, 123, "//foo.bar/C/F/_/FC");
1662
1663        check_string_url_template_substitution(foo_bar_d1_d2_d3_id, "baz", "//foo.bar/K/N/G/C9GNK");
1664        check_string_url_template_substitution(foo_bar_d1_d2_d3_id, "z", "//foo.bar/8/F/_/F8");
1665        check_string_url_template_substitution(
1666            foo_bar_d1_d2_d3_id,
1667            "àbc",
1668            "//foo.bar/O/O/4/OEG64OO",
1669        );
1670
1671        check_url_template_substitution(foo_bar_id64, 0, "//foo.bar/AA%3D%3D");
1672        check_url_template_substitution(foo_bar_id64, 14_000_000, "//foo.bar/1Z-A");
1673        check_url_template_substitution(foo_bar_id64, 17_000_000, "//foo.bar/AQNmQA%3D%3D");
1674
1675        check_string_url_template_substitution(foo_bar_id64, "àbc", "//foo.bar/w6BiYw%3D%3D");
1676    }
1677
1678    #[test]
1679    fn rejects_invalid_format() {
1680        let mut bad_format = simple_format1();
1681        bad_format.write_at("format", 3u8);
1682
1683        let font_bytes = create_ift_font(
1684            FontRef::new(test_data::ift::IFT_BASE).unwrap(),
1685            Some(&bad_format),
1686            Some(&simple_format1()),
1687        );
1688        let font = FontRef::new(&font_bytes).unwrap();
1689        assert_eq!(
1690            intersecting_patches(
1691                &font,
1692                &SubsetDefinition::new(IntSet::all(), FeatureSet::from([]), Default::default()),
1693            )
1694            .unwrap_err(),
1695            ReadError::InvalidFormat(3)
1696        );
1697    }
1698
1699    #[test]
1700    fn format_1_patch_map_u8_entries() {
1701        let font_bytes = create_ift_font(
1702            FontRef::new(test_data::ift::IFT_BASE).unwrap(),
1703            Some(&simple_format1()),
1704            None,
1705        );
1706        let font = FontRef::new(&font_bytes).unwrap();
1707
1708        test_intersection(&font, [], [], []);
1709        test_intersection(&font, [0x123], [], []); // 0x123 is not in the mapping
1710        test_intersection(&font, [0x13], [], []); // 0x13 maps to entry 0
1711        test_intersection(&font, [0x12], [], []); // 0x12 maps to entry 1 which is applied
1712        test_intersection(&font, [0x11], [], [f1(2)]); // 0x11 maps to entry 2
1713        test_intersection(&font, [0x11, 0x12, 0x123], [], [f1(2)]);
1714
1715        test_intersection_with_all(&font, [], [f1(2)]);
1716    }
1717
1718    #[test]
1719    fn format_1_patch_map_with_duplicate_urls() {
1720        let font_bytes = create_ift_font(
1721            FontRef::new(test_data::ift::IFT_BASE).unwrap(),
1722            Some(&format1_with_dup_urls()),
1723            None,
1724        );
1725        let font = FontRef::new(&font_bytes).unwrap();
1726
1727        let mut e2 = f1(2);
1728        let mut e3 = f1(3);
1729        let mut e4 = f1(4);
1730        e2.application_bit_index.union(&e3.application_bit_index);
1731        e2.application_bit_index.union(&e4.application_bit_index);
1732        e3.application_bit_index.union(&e2.application_bit_index);
1733        e4.application_bit_index.union(&e2.application_bit_index);
1734
1735        test_intersection_with_all_and_template(&font, [], b"\x08foo/baar", [e2, e3, e4]);
1736    }
1737
1738    #[test]
1739    fn format_1_patch_map_bad_entry_index() {
1740        let mut data = simple_format1();
1741        data.write_at("entry_index[1]", 3u8);
1742
1743        let font_bytes = create_ift_font(
1744            FontRef::new(test_data::ift::IFT_BASE).unwrap(),
1745            Some(&data),
1746            None,
1747        );
1748        let font = FontRef::new(&font_bytes).unwrap();
1749
1750        test_intersection(&font, [0x11], [], []);
1751    }
1752
1753    #[test]
1754    fn format_1_patch_map_glyph_map_too_short() {
1755        let data: &[u8] = &simple_format1();
1756        let data = &data[..data.len() - 1];
1757
1758        let font_bytes = create_ift_font(
1759            FontRef::new(test_data::ift::IFT_BASE).unwrap(),
1760            Some(data),
1761            None,
1762        );
1763        let font = FontRef::new(&font_bytes).unwrap();
1764
1765        assert!(intersecting_patches(
1766            &font,
1767            &SubsetDefinition::new(
1768                IntSet::from([0x123]),
1769                FeatureSet::from([]),
1770                Default::default(),
1771            ),
1772        )
1773        .is_err());
1774    }
1775
1776    #[test]
1777    fn format_1_patch_map_bad_glyph_count() {
1778        let font_bytes = create_ift_font(
1779            FontRef::new(test_data::CMAP12_FONT1).unwrap(),
1780            Some(&simple_format1()),
1781            None,
1782        );
1783        let font = FontRef::new(&font_bytes).unwrap();
1784
1785        assert!(intersecting_patches(
1786            &font,
1787            &SubsetDefinition::new(
1788                IntSet::from([0x123]),
1789                FeatureSet::from([]),
1790                Default::default(),
1791            ),
1792        )
1793        .is_err());
1794    }
1795
1796    #[test]
1797    fn format_1_patch_map_bad_max_entry() {
1798        let mut data = simple_format1();
1799        data.write_at("max_glyph_map_entry_id", 3u16);
1800
1801        let font_bytes = create_ift_font(
1802            FontRef::new(test_data::ift::IFT_BASE).unwrap(),
1803            Some(&data),
1804            None,
1805        );
1806        let font = FontRef::new(&font_bytes).unwrap();
1807
1808        assert!(intersecting_patches(
1809            &font,
1810            &SubsetDefinition::new(
1811                IntSet::from([0x123]),
1812                FeatureSet::from([]),
1813                Default::default(),
1814            ),
1815        )
1816        .is_err());
1817    }
1818
1819    #[test]
1820    fn format_1_patch_map_bad_encoding_number() {
1821        let mut data = simple_format1();
1822        data.write_at("patch_format", 0x12u8);
1823
1824        let font_bytes = create_ift_font(
1825            FontRef::new(test_data::ift::IFT_BASE).unwrap(),
1826            Some(&data),
1827            None,
1828        );
1829        let font = FontRef::new(&font_bytes).unwrap();
1830
1831        assert!(intersecting_patches(
1832            &font,
1833            &SubsetDefinition::new(
1834                IntSet::from([0x123]),
1835                FeatureSet::from([]),
1836                Default::default()
1837            )
1838        )
1839        .is_err());
1840    }
1841
1842    #[test]
1843    fn format_1_patch_map_u16_entries() {
1844        let font_bytes = create_ift_font(
1845            FontRef::new(test_data::ift::IFT_BASE).unwrap(),
1846            Some(&u16_entries_format1()),
1847            None,
1848        );
1849        let font = FontRef::new(&font_bytes).unwrap();
1850
1851        test_intersection(&font, [], [], []);
1852        test_intersection(&font, [0x11], [], []);
1853        test_intersection(&font, [0x12], [], [f1(0x50)]);
1854        test_intersection(&font, [0x13, 0x15], [], [f1(0x51), f1(0x12c)]);
1855
1856        test_intersection_with_all(&font, [], [f1(0x50), f1(0x51), f1(0x12c)]);
1857    }
1858
1859    #[test]
1860    fn format_1_patch_map_u16_entries_with_feature_mapping() {
1861        let font_bytes = create_ift_font(
1862            FontRef::new(test_data::ift::IFT_BASE).unwrap(),
1863            Some(&feature_map_format1()),
1864            None,
1865        );
1866        let font = FontRef::new(&font_bytes).unwrap();
1867
1868        test_intersection(&font, [], [], []);
1869        test_intersection(
1870            &font,
1871            [],
1872            [Tag::new(b"liga"), Tag::new(b"dlig"), Tag::new(b"null")],
1873            [],
1874        );
1875        test_intersection(&font, [0x12], [], [f1(0x50)]);
1876        test_intersection(&font, [0x12], [Tag::new(b"liga")], [f1(0x50), f1(0x180)]);
1877        test_intersection(
1878            &font,
1879            [0x13, 0x14],
1880            [Tag::new(b"liga")],
1881            [f1(0x51), f1(0x12c), f1(0x180), f1(0x181)],
1882        );
1883        test_intersection(
1884            &font,
1885            [0x13, 0x14],
1886            [Tag::new(b"dlig")],
1887            [f1(0x51), f1(0x12c), f1(0x190)],
1888        );
1889        test_intersection(
1890            &font,
1891            [0x13, 0x14],
1892            [Tag::new(b"dlig"), Tag::new(b"liga")],
1893            [f1(0x51), f1(0x12c), f1(0x180), f1(0x181), f1(0x190)],
1894        );
1895        test_intersection(&font, [0x11], [Tag::new(b"null")], [f1(0x12D)]);
1896        test_intersection(&font, [0x15], [Tag::new(b"liga")], [f1(0x181)]);
1897
1898        test_intersection_with_all(&font, [], [f1(0x50), f1(0x51), f1(0x12c)]);
1899        test_intersection_with_all(
1900            &font,
1901            [Tag::new(b"liga")],
1902            [f1(0x50), f1(0x51), f1(0x12c), f1(0x180), f1(0x181)],
1903        );
1904        test_intersection_with_all(
1905            &font,
1906            [Tag::new(b"dlig")],
1907            [f1(0x50), f1(0x51), f1(0x12c), f1(0x190)],
1908        );
1909    }
1910
1911    #[test]
1912    fn format_1_patch_map_all_features() {
1913        let font_bytes = create_ift_font(
1914            FontRef::new(test_data::ift::IFT_BASE).unwrap(),
1915            Some(&feature_map_format1()),
1916            None,
1917        );
1918        let font = FontRef::new(&font_bytes).unwrap();
1919
1920        test_design_space_intersection(
1921            &font,
1922            [0x13],
1923            FeatureSet::from([Tag::new(b"dlig"), Tag::new(b"liga")]),
1924            Default::default(),
1925            [f1(0x51), f1(0x180), f1(0x190)],
1926        );
1927
1928        test_design_space_intersection(
1929            &font,
1930            [0x13],
1931            FeatureSet::All,
1932            Default::default(),
1933            [f1(0x51), f1(0x180), f1(0x190)],
1934        );
1935    }
1936
1937    #[test]
1938    fn format_1_patch_map_all_features_skips_unsorted() {
1939        let mut data = feature_map_format1();
1940        data.write_at("FeatureRecord[0]", Tag::new(b"liga"));
1941        data.write_at("FeatureRecord[1]", Tag::new(b"dlig"));
1942
1943        let font_bytes = create_ift_font(
1944            FontRef::new(test_data::ift::IFT_BASE).unwrap(),
1945            Some(&data),
1946            None,
1947        );
1948        let font = FontRef::new(&font_bytes).unwrap();
1949
1950        test_design_space_intersection(
1951            &font,
1952            [0x13, 0x14],
1953            FeatureSet::from([Tag::new(b"dlig"), Tag::new(b"liga"), Tag::new(b"null")]),
1954            Default::default(),
1955            [f1(0x51), f1(0x12c), f1(0x190)],
1956        );
1957
1958        test_design_space_intersection(
1959            &font,
1960            [0x13, 0x14],
1961            FeatureSet::All,
1962            Default::default(),
1963            [f1(0x51), f1(0x12c), f1(0x190)],
1964        );
1965    }
1966
1967    fn patch_with_intersection(
1968        applied_entries_start: usize,
1969        index: u32,
1970        intersection_info: IntersectionInfo,
1971    ) -> PatchMapEntry {
1972        let url =
1973            PatchUrl::expand_template(RELATIVE_URL_TEMPLATE, &PatchId::Numeric(index)).unwrap();
1974        let mut e = url.into_format_1_entry(
1975            IftTableTag::Ift(compat_id()),
1976            PatchFormat::TableKeyed {
1977                fully_invalidating: true,
1978            },
1979            intersection_info,
1980        );
1981        e.application_bit_indices
1982            .insert(applied_entries_start as u32 + index);
1983        e
1984    }
1985
1986    #[test]
1987    fn format_1_patch_map_intersection_info() {
1988        let mut map = feature_map_format1();
1989        map.write_at("patch_format", 1u8);
1990        map.write_at("gid5_entry", 299u16);
1991        map.write_at("gid6_entry", 300u16);
1992        map.write_at("applied_entries_296", 0u8);
1993        let applied_entries_start = map.offset_for("applied_entries") * 8;
1994        let font_bytes = create_ift_font(
1995            FontRef::new(test_data::ift::IFT_BASE).unwrap(),
1996            Some(&map),
1997            None,
1998        );
1999        let font = FontRef::new(&font_bytes).unwrap();
2000
2001        // case 1 - only codepoints
2002        let patches = intersecting_patches(
2003            &font,
2004            &SubsetDefinition::new(IntSet::from([0x14]), Default::default(), Default::default()),
2005        )
2006        .unwrap();
2007        assert_eq!(
2008            patches,
2009            vec![patch_with_intersection(
2010                applied_entries_start,
2011                300,
2012                IntersectionInfo::new(1, 0, 300),
2013            ),]
2014        );
2015
2016        // case 2 - only codepoints
2017        let patches = intersecting_patches(
2018            &font,
2019            &SubsetDefinition::new(
2020                IntSet::from([0x14, 0x15, 0x16]),
2021                Default::default(),
2022                Default::default(),
2023            ),
2024        )
2025        .unwrap();
2026        assert_eq!(
2027            patches,
2028            vec![
2029                patch_with_intersection(
2030                    applied_entries_start,
2031                    299,
2032                    IntersectionInfo::new(1, 0, 299),
2033                ),
2034                patch_with_intersection(
2035                    applied_entries_start,
2036                    300,
2037                    IntersectionInfo::new(2, 0, 300),
2038                ),
2039            ]
2040        );
2041
2042        // case 3 - features (w/ intersection)
2043        let patches = intersecting_patches(
2044            &font,
2045            &SubsetDefinition::new(
2046                IntSet::from([0x14, 0x15, 0x16]),
2047                FeatureSet::from([Tag::new(b"dlig"), Tag::new(b"liga")]),
2048                Default::default(),
2049            ),
2050        )
2051        .unwrap();
2052        assert_eq!(
2053            patches,
2054            vec![
2055                patch_with_intersection(
2056                    applied_entries_start,
2057                    299,
2058                    IntersectionInfo::new(1, 0, 299),
2059                ),
2060                patch_with_intersection(
2061                    applied_entries_start,
2062                    300,
2063                    IntersectionInfo::new(2, 0, 300),
2064                ),
2065                patch_with_intersection(
2066                    applied_entries_start,
2067                    385,
2068                    IntersectionInfo::new(3, 1, 385),
2069                ),
2070            ]
2071        );
2072
2073        // case 4 - features (w/o intersection)
2074        let patches = intersecting_patches(
2075            &font,
2076            &SubsetDefinition::new(
2077                IntSet::from([0x14, 0x15, 0x16]),
2078                FeatureSet::from([Tag::new(b"dlig")]),
2079                Default::default(),
2080            ),
2081        )
2082        .unwrap();
2083        assert_eq!(
2084            patches,
2085            vec![
2086                patch_with_intersection(
2087                    applied_entries_start,
2088                    299,
2089                    IntersectionInfo::new(1, 0, 299),
2090                ),
2091                patch_with_intersection(
2092                    applied_entries_start,
2093                    300,
2094                    IntersectionInfo::new(2, 0, 300),
2095                ),
2096            ]
2097        );
2098    }
2099
2100    #[test]
2101    fn format_1_patch_map_u16_entries_with_out_of_order_feature_mapping() {
2102        let mut data = feature_map_format1();
2103        data.write_at("FeatureRecord[0]", Tag::new(b"liga"));
2104        data.write_at("FeatureRecord[1]", Tag::new(b"dlig"));
2105
2106        let font_bytes = create_ift_font(
2107            FontRef::new(test_data::ift::IFT_BASE).unwrap(),
2108            Some(&data),
2109            None,
2110        );
2111        let font = FontRef::new(&font_bytes).unwrap();
2112
2113        test_intersection(
2114            &font,
2115            [0x13, 0x14],
2116            [Tag::new(b"liga")],
2117            [f1(0x51), f1(0x12c), f1(0x190)],
2118        );
2119        test_intersection(
2120            &font,
2121            [0x13, 0x14],
2122            [Tag::new(b"dlig")],
2123            [f1(0x51), f1(0x12c)], // dlig is ignored since it's out of order.
2124        );
2125        test_intersection(&font, [0x11], [Tag::new(b"null")], [f1(0x12D)]);
2126    }
2127
2128    #[test]
2129    fn format_1_patch_map_u16_entries_with_duplicate_feature_mapping() {
2130        let mut data = feature_map_format1();
2131        data.write_at("FeatureRecord[0]", Tag::new(b"liga"));
2132        data.write_at("FeatureRecord[1]", Tag::new(b"liga"));
2133
2134        let font_bytes = create_ift_font(
2135            FontRef::new(test_data::ift::IFT_BASE).unwrap(),
2136            Some(&data),
2137            None,
2138        );
2139        let font = FontRef::new(&font_bytes).unwrap();
2140
2141        test_intersection(
2142            &font,
2143            [0x13, 0x14],
2144            [Tag::new(b"liga")],
2145            [f1(0x51), f1(0x12c), f1(0x190)],
2146        );
2147        test_intersection(&font, [0x11], [Tag::new(b"null")], [f1(0x12D)]);
2148    }
2149
2150    #[test]
2151    fn format_1_patch_map_feature_map_entry_record_too_short() {
2152        let font_bytes = create_ift_font(
2153            FontRef::new(test_data::ift::IFT_BASE).unwrap(),
2154            Some(&feature_map_format1()[..feature_map_format1().len() - 1]),
2155            None,
2156        );
2157        let font = FontRef::new(&font_bytes).unwrap();
2158
2159        assert!(intersecting_patches(
2160            &font,
2161            &SubsetDefinition::new(
2162                IntSet::from([0x12]),
2163                FeatureSet::from([]),
2164                Default::default(),
2165            ),
2166        )
2167        .is_err());
2168        assert!(intersecting_patches(
2169            &font,
2170            &SubsetDefinition::new(
2171                IntSet::from([0x12]),
2172                FeatureSet::from([Tag::new(b"liga")]),
2173                Default::default(),
2174            )
2175        )
2176        .is_err());
2177        assert!(intersecting_patches(
2178            &font,
2179            &SubsetDefinition::new(
2180                IntSet::from([0x12]),
2181                FeatureSet::from([]),
2182                Default::default(),
2183            )
2184        )
2185        .is_err());
2186    }
2187
2188    #[test]
2189    fn format_1_patch_map_feature_record_too_short() {
2190        let font_bytes = create_ift_font(
2191            FontRef::new(test_data::ift::IFT_BASE).unwrap(),
2192            Some(&feature_map_format1()[..123]),
2193            None,
2194        );
2195        let font = FontRef::new(&font_bytes).unwrap();
2196
2197        assert!(intersecting_patches(
2198            &font,
2199            &SubsetDefinition::new(
2200                IntSet::from([0x12]),
2201                FeatureSet::from([]),
2202                Default::default(),
2203            ),
2204        )
2205        .is_err());
2206        assert!(intersecting_patches(
2207            &font,
2208            &SubsetDefinition::new(
2209                IntSet::from([0x12]),
2210                FeatureSet::from([Tag::new(b"liga")]),
2211                Default::default(),
2212            )
2213        )
2214        .is_err());
2215        assert!(intersecting_patches(
2216            &font,
2217            &SubsetDefinition::new(
2218                IntSet::from([0x12]),
2219                FeatureSet::from([]),
2220                Default::default(),
2221            )
2222        )
2223        .is_err());
2224    }
2225
2226    #[test]
2227    fn format_2_patch_map_codepoints_only() {
2228        let font_bytes = create_ift_font(
2229            FontRef::new(test_data::ift::IFT_BASE).unwrap(),
2230            Some(&codepoints_only_format2()),
2231            None,
2232        );
2233        let font = FontRef::new(&font_bytes).unwrap();
2234
2235        let e1 = f2(1, codepoints_only_format2().offset_for("entries[0]"), 0);
2236        let e3 = f2(3, codepoints_only_format2().offset_for("entries[2]"), 2);
2237        let e4 = f2(4, codepoints_only_format2().offset_for("entries[3]"), 3);
2238        test_intersection(&font, [], [], []);
2239        test_intersection(&font, [0x02], [], [e1.clone()]);
2240        test_intersection(&font, [0x15], [], [e3.clone()]);
2241        test_intersection(&font, [0x07], [], [e1.clone(), e3.clone()]);
2242        test_intersection(&font, [80_007], [], [e4.clone()]);
2243
2244        test_intersection_with_all(&font, [], [e1.clone(), e3.clone(), e4.clone()]);
2245    }
2246
2247    #[test]
2248    fn format_2_patch_map_features_and_design_space() {
2249        let font_bytes = create_ift_font(
2250            FontRef::new(test_data::ift::IFT_BASE).unwrap(),
2251            Some(&features_and_design_space_format2()),
2252            None,
2253        );
2254        let font = FontRef::new(&font_bytes).unwrap();
2255
2256        let e1 = f2(
2257            1,
2258            features_and_design_space_format2().offset_for("entries[0]"),
2259            0,
2260        );
2261        let e2 = f2(
2262            2,
2263            features_and_design_space_format2().offset_for("entries[1]"),
2264            1,
2265        );
2266        let e3 = f2(
2267            3,
2268            features_and_design_space_format2().offset_for("entries[2]"),
2269            2,
2270        );
2271
2272        test_intersection(&font, [], [], []);
2273        test_intersection(&font, [0x02], [], []);
2274        test_intersection(&font, [0x50], [Tag::new(b"rlig")], []);
2275        test_intersection(&font, [0x02], [Tag::new(b"rlig")], [e2.clone()]);
2276
2277        test_design_space_intersection(
2278            &font,
2279            [0x02],
2280            FeatureSet::from([Tag::new(b"rlig")]),
2281            DesignSpace::from([(
2282                Tag::new(b"wdth"),
2283                [Fixed::from_f64(0.7)..=Fixed::from_f64(0.8)]
2284                    .into_iter()
2285                    .collect(),
2286            )]),
2287            [e2],
2288        );
2289
2290        test_design_space_intersection(
2291            &font,
2292            [0x05],
2293            FeatureSet::from([Tag::new(b"smcp")]),
2294            DesignSpace::from([(
2295                Tag::new(b"wdth"),
2296                [Fixed::from_f64(0.7)..=Fixed::from_f64(0.8)]
2297                    .into_iter()
2298                    .collect(),
2299            )]),
2300            [e1.clone()],
2301        );
2302        test_design_space_intersection(
2303            &font,
2304            [0x05],
2305            FeatureSet::from([Tag::new(b"smcp")]),
2306            DesignSpace::from([(
2307                Tag::new(b"wdth"),
2308                [Fixed::from_f64(0.2)..=Fixed::from_f64(0.3)]
2309                    .into_iter()
2310                    .collect(),
2311            )]),
2312            [e3.clone()],
2313        );
2314        test_design_space_intersection(
2315            &font,
2316            [0x55],
2317            FeatureSet::from([Tag::new(b"smcp")]),
2318            DesignSpace::from([(
2319                Tag::new(b"wdth"),
2320                [Fixed::from_f64(0.2)..=Fixed::from_f64(0.3)]
2321                    .into_iter()
2322                    .collect(),
2323            )]),
2324            [],
2325        );
2326        test_design_space_intersection(
2327            &font,
2328            [0x05],
2329            FeatureSet::from([Tag::new(b"smcp")]),
2330            DesignSpace::from([(
2331                Tag::new(b"wdth"),
2332                [Fixed::from_f64(1.2)..=Fixed::from_f64(1.3)]
2333                    .into_iter()
2334                    .collect(),
2335            )]),
2336            [],
2337        );
2338
2339        test_design_space_intersection(
2340            &font,
2341            [0x05],
2342            FeatureSet::from([Tag::new(b"smcp")]),
2343            DesignSpace::from([(
2344                Tag::new(b"wdth"),
2345                [
2346                    Fixed::from_f64(0.2)..=Fixed::from_f64(0.3),
2347                    Fixed::from_f64(0.7)..=Fixed::from_f64(0.8),
2348                ]
2349                .into_iter()
2350                .collect(),
2351            )]),
2352            [e1.clone(), e3.clone()],
2353        );
2354        test_design_space_intersection(
2355            &font,
2356            [0x05],
2357            FeatureSet::from([Tag::new(b"smcp")]),
2358            DesignSpace::from([(
2359                Tag::new(b"wdth"),
2360                [Fixed::from_f64(2.2)..=Fixed::from_f64(2.3)]
2361                    .into_iter()
2362                    .collect(),
2363            )]),
2364            [e3.clone()],
2365        );
2366        test_design_space_intersection(
2367            &font,
2368            [0x05],
2369            FeatureSet::from([Tag::new(b"smcp")]),
2370            DesignSpace::from([(
2371                Tag::new(b"wdth"),
2372                [
2373                    Fixed::from_f64(2.2)..=Fixed::from_f64(2.3),
2374                    Fixed::from_f64(1.2)..=Fixed::from_f64(1.3),
2375                ]
2376                .into_iter()
2377                .collect(),
2378            )]),
2379            [e3],
2380        );
2381    }
2382
2383    #[test]
2384    fn format_2_patch_map_all_features() {
2385        let font_bytes = create_ift_font(
2386            FontRef::new(test_data::ift::IFT_BASE).unwrap(),
2387            Some(&features_and_design_space_format2()),
2388            None,
2389        );
2390        let font = FontRef::new(&font_bytes).unwrap();
2391
2392        let e1 = f2(
2393            1,
2394            features_and_design_space_format2().offset_for("entries[0]"),
2395            0,
2396        );
2397        let e2 = f2(
2398            2,
2399            features_and_design_space_format2().offset_for("entries[1]"),
2400            1,
2401        );
2402        let e3 = f2(
2403            3,
2404            features_and_design_space_format2().offset_for("entries[2]"),
2405            2,
2406        );
2407
2408        test_design_space_intersection(
2409            &font,
2410            [0x06],
2411            FeatureSet::All,
2412            DesignSpace::from([(
2413                Tag::new(b"wdth"),
2414                [Fixed::from_f64(0.7)..=Fixed::from_f64(2.2)]
2415                    .into_iter()
2416                    .collect(),
2417            )]),
2418            [e1, e2, e3],
2419        );
2420    }
2421
2422    #[test]
2423    fn format_2_patch_map_all_design_space() {
2424        let font_bytes = create_ift_font(
2425            FontRef::new(test_data::ift::IFT_BASE).unwrap(),
2426            Some(&features_and_design_space_format2()),
2427            None,
2428        );
2429        let font = FontRef::new(&font_bytes).unwrap();
2430
2431        let e1 = f2(
2432            1,
2433            features_and_design_space_format2().offset_for("entries[0]"),
2434            0,
2435        );
2436        let e2 = f2(
2437            2,
2438            features_and_design_space_format2().offset_for("entries[1]"),
2439            1,
2440        );
2441        let e3 = f2(
2442            3,
2443            features_and_design_space_format2().offset_for("entries[2]"),
2444            2,
2445        );
2446
2447        test_design_space_intersection(
2448            &font,
2449            [0x05],
2450            FeatureSet::from([Tag::new(b"smcp")]),
2451            DesignSpace::All,
2452            [e1.clone(), e3.clone()],
2453        );
2454
2455        test_design_space_intersection(
2456            &font,
2457            [0x05],
2458            FeatureSet::All,
2459            DesignSpace::All,
2460            [e1, e2, e3],
2461        );
2462    }
2463
2464    #[test]
2465    fn format_2_patch_map_with_duplicate_urls() {
2466        // The mapping is set up to contain multiple entries that have the same url.
2467        // Checks that application bit indices get correctly recorded.
2468        let mut buffer = codepoints_only_format2();
2469        buffer.write_at("entry_count", Uint24::new(5));
2470        let buffer = buffer
2471            .push(0b00010100u8) // DELTA | CODEPOINT 1
2472            .push(Int24::new(-8)) // entry delta -4
2473            .extend([0b00001101, 0b00000011, 0b00110001u8]); // {0..17}
2474
2475        let font_bytes = create_ift_font(
2476            FontRef::new(test_data::ift::IFT_BASE).unwrap(),
2477            Some(&buffer),
2478            None,
2479        );
2480        let font = FontRef::new(&font_bytes).unwrap();
2481
2482        let mut e1 = f2(1, buffer.offset_for("entries[0]"), 0);
2483        let mut e5 = f2(1, buffer.offset_for("entries[3]") + 7, 4);
2484
2485        e1.application_bit_index.union(&e5.application_bit_index);
2486        e5.application_bit_index.union(&e1.application_bit_index);
2487
2488        test_intersection(&font, [], [], []);
2489        test_intersection(&font, [0x02], [], [e1, e5]);
2490    }
2491
2492    #[test]
2493    fn format_2_patch_map_intersection_info() {
2494        let mut map = features_and_design_space_format2();
2495        map.write_at("patch_format", 1u8);
2496
2497        let font_bytes = create_ift_font(
2498            FontRef::new(test_data::ift::IFT_BASE).unwrap(),
2499            Some(&map),
2500            None,
2501        );
2502        let font = FontRef::new(&font_bytes).unwrap();
2503
2504        // Case 1
2505        let patches = intersecting_patches(
2506            &font,
2507            &SubsetDefinition::new(
2508                IntSet::from([10, 15, 22]),
2509                FeatureSet::from([Tag::new(b"rlig"), Tag::new(b"liga")]),
2510                Default::default(),
2511            ),
2512        )
2513        .unwrap();
2514        assert_eq!(
2515            patches,
2516            vec![patch_with_intersection(
2517                map.offset_for("entries[1]") * 8 + 4,
2518                2,
2519                IntersectionInfo::new(2, 1, 1),
2520            ),]
2521        );
2522
2523        // Case 2
2524        let patches = intersecting_patches(
2525            &font,
2526            &SubsetDefinition::new(
2527                IntSet::from([10, 15, 22]),
2528                FeatureSet::from([Tag::new(b"rlig"), Tag::new(b"liga"), Tag::new(b"smcp")]),
2529                DesignSpace::from([(
2530                    Tag::new(b"wght"),
2531                    [Fixed::from_i32(505)..=Fixed::from_i32(800)]
2532                        .into_iter()
2533                        .collect(),
2534                )]),
2535            ),
2536        )
2537        .unwrap();
2538        assert_eq!(
2539            patches,
2540            vec![
2541                patch_with_intersection(
2542                    map.offset_for("entries[1]") * 8 + 4,
2543                    2,
2544                    IntersectionInfo::new(2, 1, 1),
2545                ),
2546                patch_with_intersection(
2547                    map.offset_for("entries[2]") * 8 + 3,
2548                    3,
2549                    IntersectionInfo::from_design_space(
2550                        3,
2551                        1,
2552                        [(Tag::new(b"wght"), Fixed::from_i32(195))],
2553                        2
2554                    ),
2555                ),
2556            ]
2557        );
2558    }
2559
2560    #[test]
2561    fn format_2_patch_map_invalid_child_indices() {
2562        let mut builder = child_indices_format2();
2563        builder.write_at("entries[6]_child", Uint24::new(6));
2564
2565        let font_bytes = create_ift_font(
2566            FontRef::new(test_data::ift::IFT_BASE).unwrap(),
2567            Some(&builder),
2568            None,
2569        );
2570        let font = FontRef::new(&font_bytes).unwrap();
2571
2572        assert_eq!(
2573            intersecting_patches(
2574                &font,
2575                &SubsetDefinition::new(IntSet::all(), FeatureSet::from([]), Default::default()),
2576            )
2577            .unwrap_err(),
2578            ReadError::MalformedData("Child index must refer to only prior entries.")
2579        );
2580    }
2581
2582    #[test]
2583    fn format_2_patch_map_disjunctive_child_indices() {
2584        let font_bytes = create_ift_font(
2585            FontRef::new(test_data::ift::IFT_BASE).unwrap(),
2586            Some(&child_indices_format2()),
2587            None,
2588        );
2589        let font = FontRef::new(&font_bytes).unwrap();
2590
2591        let e3 = f2(3, child_indices_format2().offset_for("entries[2]"), 2);
2592        let e5 = f2(5, child_indices_format2().offset_for("entries[4]"), 4);
2593        let e6 = f2(6, child_indices_format2().offset_for("entries[5]"), 5);
2594        let e7 = f2(7, child_indices_format2().offset_for("entries[6]"), 6);
2595        let e8 = f2(8, child_indices_format2().offset_for("entries[7]"), 7);
2596        let e9 = f2(9, child_indices_format2().offset_for("entries[8]"), 8);
2597        test_intersection(&font, [], [], []);
2598        test_intersection(&font, [0x05], [], [e5.clone(), e7.clone(), e8.clone()]);
2599        test_intersection(&font, [0x65], [], []);
2600        test_intersection(
2601            &font,
2602            [0x05, 0x65],
2603            [],
2604            [e5.clone(), e7.clone(), e8.clone(), e9],
2605        );
2606
2607        test_design_space_intersection(
2608            &font,
2609            [],
2610            FeatureSet::from([Tag::new(b"rlig")]),
2611            DesignSpace::from([(
2612                Tag::new(b"wght"),
2613                [Fixed::from_i32(500)..=Fixed::from_i32(500)]
2614                    .into_iter()
2615                    .collect(),
2616            )]),
2617            [e3.clone(), e6.clone(), e7.clone(), e8.clone()],
2618        );
2619
2620        test_design_space_intersection(
2621            &font,
2622            [0x05],
2623            FeatureSet::from([Tag::new(b"rlig")]),
2624            DesignSpace::from([(
2625                Tag::new(b"wght"),
2626                [Fixed::from_i32(500)..=Fixed::from_i32(500)]
2627                    .into_iter()
2628                    .collect(),
2629            )]),
2630            [e3, e5, e6, e7, e8],
2631        );
2632    }
2633
2634    #[test]
2635    fn format_2_patch_map_conjunctive_child_indices() {
2636        let mut builder = child_indices_format2();
2637        builder.write_at("entries[6]_child_count", 0b10000000u8 | 4u8);
2638
2639        let font_bytes = create_ift_font(
2640            FontRef::new(test_data::ift::IFT_BASE).unwrap(),
2641            Some(&builder),
2642            None,
2643        );
2644        let font = FontRef::new(&font_bytes).unwrap();
2645
2646        let e2 = f2(2, child_indices_format2().offset_for("entries[1]"), 1);
2647        let e3 = f2(3, child_indices_format2().offset_for("entries[2]"), 2);
2648        let e4 = f2(4, child_indices_format2().offset_for("entries[3]"), 3);
2649        let e5 = f2(5, child_indices_format2().offset_for("entries[4]"), 4);
2650        let e6 = f2(6, child_indices_format2().offset_for("entries[5]"), 5);
2651        let e7 = f2(7, child_indices_format2().offset_for("entries[6]"), 6);
2652        let e8 = f2(8, child_indices_format2().offset_for("entries[7]"), 7);
2653        test_intersection(&font, [0x05], [], [e5.clone(), e8.clone()]);
2654        test_design_space_intersection(
2655            &font,
2656            [0x05, 51],
2657            FeatureSet::from([Tag::new(b"liga"), Tag::new(b"rlig")]),
2658            DesignSpace::from([(
2659                Tag::new(b"wght"),
2660                [
2661                    Fixed::from_i32(75)..=Fixed::from_i32(75),
2662                    Fixed::from_i32(500)..=Fixed::from_i32(500),
2663                ]
2664                .into_iter()
2665                .collect(),
2666            )]),
2667            [e2, e3, e4, e5, e6, e7, e8],
2668        );
2669    }
2670
2671    #[test]
2672    fn format_2_patch_map_conjunctive_child_indices_intersection_info() {
2673        let mut builder = child_indices_format2();
2674        builder.write_at("entries[6]_child_count", 0b10000000u8 | 4u8);
2675        builder.write_at("encoding", 1u8);
2676
2677        let font_bytes = create_ift_font(
2678            FontRef::new(test_data::ift::IFT_BASE).unwrap(),
2679            Some(&builder),
2680            None,
2681        );
2682        let font = FontRef::new(&font_bytes).unwrap();
2683
2684        let patches = intersecting_patches(
2685            &font,
2686            &SubsetDefinition::new(
2687                IntSet::from([6, 51, 22]),
2688                FeatureSet::from([Tag::new(b"rlig"), Tag::new(b"liga")]),
2689                DesignSpace::from([(
2690                    Tag::new(b"wght"),
2691                    [Fixed::from_i32(75)..=Fixed::from_i32(300)]
2692                        .into_iter()
2693                        .collect(),
2694                )]),
2695            ),
2696        )
2697        .unwrap();
2698
2699        let e = patches
2700            .into_iter()
2701            .find(|p| &p.url().0 == "foo/0S")
2702            .unwrap();
2703
2704        let mut expected_info = IntersectionInfo::new(3, 2, 6);
2705        expected_info
2706            .intersecting_design_space
2707            .insert(Tag::new(b"wght"), Fixed::from_i32(125)); // [75..100] + [200..300]
2708
2709        assert_eq!(
2710            e,
2711            patch_with_intersection(
2712                builder.offset_for("entries[6]") * 8 + 6 - 7,
2713                7,
2714                expected_info,
2715            ),
2716        );
2717    }
2718
2719    #[test]
2720    fn format_2_patch_map_custom_ids() {
2721        let font_bytes = create_ift_font(
2722            FontRef::new(test_data::ift::IFT_BASE).unwrap(),
2723            Some(&custom_ids_format2()),
2724            None,
2725        );
2726        let font = FontRef::new(&font_bytes).unwrap();
2727
2728        let e0 = f2(0, custom_ids_format2().offset_for("entries[0]"), 0);
2729        let e6 = f2(6, custom_ids_format2().offset_for("entries[1]"), 1);
2730        let e15 = f2(15, custom_ids_format2().offset_for("entries[3]"), 3);
2731
2732        test_intersection_with_all(&font, [], [e0, e6, e15]);
2733    }
2734
2735    #[test]
2736    fn format_2_patch_map_custom_preload_ids() {
2737        let font_bytes = create_ift_font(
2738            FontRef::new(test_data::ift::IFT_BASE).unwrap(),
2739            Some(&table_keyed_format2_with_preload_urls()),
2740            None,
2741        );
2742        let font = FontRef::new(&font_bytes).unwrap();
2743
2744        let e0 = f2(
2745            1,
2746            table_keyed_format2_with_preload_urls().offset_for("entries[0]"),
2747            0,
2748        );
2749        let e1 = f2p(
2750            vec![9, 10, 6],
2751            table_keyed_format2_with_preload_urls().offset_for("entries[1]"),
2752            1,
2753        );
2754        let e2 = f2p(
2755            vec![2, 3],
2756            table_keyed_format2_with_preload_urls().offset_for("entries[2]"),
2757            2,
2758        );
2759        let e3 = f2(
2760            4,
2761            table_keyed_format2_with_preload_urls().offset_for("entries[3]"),
2762            3,
2763        );
2764
2765        test_intersection_with_all(&font, [], [e0, e1, e2, e3]);
2766    }
2767
2768    #[test]
2769    fn format_2_patch_map_custom_encoding() {
2770        let mut data = custom_ids_format2();
2771        data.write_at("entry[4] encoding", 1u8); // Tabled Keyed Full Invalidation.
2772
2773        let font_bytes = create_ift_font(
2774            FontRef::new(test_data::ift::IFT_BASE).unwrap(),
2775            Some(&data),
2776            None,
2777        );
2778        let font = FontRef::new(&font_bytes).unwrap();
2779
2780        let patches = intersecting_patches(
2781            &font,
2782            &SubsetDefinition::new(IntSet::all(), FeatureSet::from([]), Default::default()),
2783        )
2784        .unwrap();
2785
2786        let encodings: Vec<PatchFormat> = patches.into_iter().map(|e| e.format).collect();
2787        assert_eq!(
2788            encodings,
2789            vec![
2790                PatchFormat::GlyphKeyed,
2791                PatchFormat::GlyphKeyed,
2792                PatchFormat::TableKeyed {
2793                    fully_invalidating: true,
2794                },
2795            ]
2796        );
2797    }
2798
2799    #[test]
2800    fn format_2_patch_map_id_strings() {
2801        let font_bytes = create_ift_font(
2802            FontRef::new(test_data::ift::IFT_BASE).unwrap(),
2803            Some(&string_ids_format2()),
2804            None,
2805        );
2806        let font = FontRef::new(&font_bytes).unwrap();
2807
2808        let patches = intersecting_patches(
2809            &font,
2810            &SubsetDefinition::new(IntSet::all(), FeatureSet::from([]), Default::default()),
2811        )
2812        .unwrap();
2813
2814        let urls: Vec<PatchUrl> = patches.into_iter().map(|e| e.url).collect();
2815        let expected_urls: Vec<_> = ["", "abc", "defg", "defg", "hij", ""]
2816            .iter()
2817            .map(|id| PatchId::String(Vec::from(id.as_bytes())))
2818            .map(|id| PatchUrl::expand_template(RELATIVE_URL_TEMPLATE, &id).unwrap())
2819            .collect();
2820        assert_eq!(urls, expected_urls);
2821    }
2822
2823    #[test]
2824    fn format_2_patch_map_id_strings_with_preloads() {
2825        let font_bytes = create_ift_font(
2826            FontRef::new(test_data::ift::IFT_BASE).unwrap(),
2827            Some(&string_ids_format2_with_preloads()),
2828            None,
2829        );
2830        let font = FontRef::new(&font_bytes).unwrap();
2831
2832        let patches = intersecting_patches(
2833            &font,
2834            &SubsetDefinition::new(IntSet::all(), FeatureSet::from([]), Default::default()),
2835        )
2836        .unwrap();
2837
2838        let urls: Vec<Vec<PatchUrl>> = patches
2839            .into_iter()
2840            .map(|e| {
2841                let mut ids = vec![e.url];
2842                ids.extend(e.preload_urls.clone());
2843                ids
2844            })
2845            .collect();
2846
2847        let expected_urls = vec![
2848            vec![""],
2849            vec!["abc", "", "defg"],
2850            vec!["defg"],
2851            vec!["hij"],
2852            vec![""],
2853        ];
2854        let expected_urls = expected_urls
2855            .into_iter()
2856            .map(|group| {
2857                group
2858                    .iter()
2859                    .map(|id| PatchId::String(Vec::from(id.as_bytes())))
2860                    .map(|id| PatchUrl::expand_template(RELATIVE_URL_TEMPLATE, &id).unwrap())
2861                    .collect::<Vec<PatchUrl>>()
2862            })
2863            .collect::<Vec<Vec<PatchUrl>>>();
2864
2865        assert_eq!(urls, expected_urls);
2866    }
2867
2868    #[test]
2869    fn format_2_patch_map_id_strings_too_short() {
2870        let mut data = string_ids_format2();
2871        data.write_at("entry[4] id length", Uint24::new(4));
2872
2873        let font_bytes = create_ift_font(
2874            FontRef::new(test_data::ift::IFT_BASE).unwrap(),
2875            Some(&data),
2876            None,
2877        );
2878        let font = FontRef::new(&font_bytes).unwrap();
2879
2880        assert!(intersecting_patches(
2881            &font,
2882            &SubsetDefinition::new(IntSet::all(), FeatureSet::from([]), Default::default()),
2883        )
2884        .is_err());
2885    }
2886
2887    #[test]
2888    fn format_2_patch_map_invalid_design_space() {
2889        let mut data = features_and_design_space_format2();
2890        data.write_at("wdth start", 0x20000u32);
2891
2892        let font_bytes = create_ift_font(
2893            FontRef::new(test_data::ift::IFT_BASE).unwrap(),
2894            Some(&data),
2895            None,
2896        );
2897        let font = FontRef::new(&font_bytes).unwrap();
2898
2899        assert!(intersecting_patches(
2900            &font,
2901            &SubsetDefinition::new(IntSet::all(), FeatureSet::from([]), Default::default()),
2902        )
2903        .is_err());
2904    }
2905
2906    #[test]
2907    fn format_2_patch_map_invalid_sparse_bit_set() {
2908        let data = codepoints_only_format2();
2909        let font_bytes = create_ift_font(
2910            FontRef::new(test_data::ift::IFT_BASE).unwrap(),
2911            Some(&data[..(data.len() - 1)]),
2912            None,
2913        );
2914        let font = FontRef::new(&font_bytes).unwrap();
2915
2916        assert!(intersecting_patches(
2917            &font,
2918            &SubsetDefinition::new(IntSet::all(), FeatureSet::from([]), Default::default()),
2919        )
2920        .is_err());
2921    }
2922
2923    #[test]
2924    fn format_2_patch_map_negative_entry_id() {
2925        let mut data = custom_ids_format2();
2926        data.write_at("entries[1].id_delta", Int24::new(-4));
2927
2928        let font_bytes = create_ift_font(
2929            FontRef::new(test_data::ift::IFT_BASE).unwrap(),
2930            Some(&data),
2931            None,
2932        );
2933        let font = FontRef::new(&font_bytes).unwrap();
2934
2935        assert!(intersecting_patches(
2936            &font,
2937            &SubsetDefinition::new(IntSet::all(), FeatureSet::from([]), Default::default()),
2938        )
2939        .is_err());
2940    }
2941
2942    #[test]
2943    fn format_2_patch_map_negative_entry_id_on_ignored() {
2944        let mut data = custom_ids_format2();
2945        data.write_at("id delta - ignored entry", Int24::new(-20));
2946
2947        let font_bytes = create_ift_font(
2948            FontRef::new(test_data::ift::IFT_BASE).unwrap(),
2949            Some(&data),
2950            None,
2951        );
2952        let font = FontRef::new(&font_bytes).unwrap();
2953
2954        assert!(intersecting_patches(
2955            &font,
2956            &SubsetDefinition::new(IntSet::all(), FeatureSet::from([]), Default::default()),
2957        )
2958        .is_err());
2959    }
2960
2961    #[test]
2962    fn format_2_patch_map_entry_id_overflow() {
2963        let count = 1023;
2964        let mut data = custom_ids_format2();
2965        data.write_at("entry_count", Uint24::new(count + 5));
2966
2967        for _ in 0..count {
2968            data = data
2969                .push(0b01000100u8) // format = ID_DELTA | IGNORED
2970                .push(Int24::new(0x7FFFFE)); // delta = max(i24) / 2
2971        }
2972
2973        // at this point the second last entry id is:
2974        // 15 +                   # last entry id from the first 4 entries
2975        // count * (7FFFFE/2 + 1) # sum of added deltas
2976        //
2977        // So the max delta without overflow on the last entry is:
2978        //
2979        // u32::MAX - second last entry id - 1
2980        //
2981        // The -1 is needed because the last entry implicitly includes a + 1
2982        let max_delta_without_overflow =
2983            (u32::MAX - ((15 + count * ((0x7FFFFE / 2) + 1)) + 1)) as i32;
2984        data = data
2985            .push(0b01000100u8) // format = ID_DELTA | IGNORED
2986            .push_with_tag(Int24::new(max_delta_without_overflow * 2), "last delta"); // delta
2987
2988        // Check one less than max doesn't overflow
2989        let font_bytes = create_ift_font(
2990            FontRef::new(test_data::ift::IFT_BASE).unwrap(),
2991            Some(&data),
2992            None,
2993        );
2994        let font = FontRef::new(&font_bytes).unwrap();
2995
2996        assert!(intersecting_patches(
2997            &font,
2998            &SubsetDefinition::new(IntSet::all(), FeatureSet::from([]), Default::default()),
2999        )
3000        .is_ok());
3001
3002        // Check one more does overflow
3003        data.write_at("last delta", Int24::new(max_delta_without_overflow + 2));
3004
3005        let font_bytes = create_ift_font(
3006            FontRef::new(test_data::ift::IFT_BASE).unwrap(),
3007            Some(&data),
3008            None,
3009        );
3010        let font = FontRef::new(&font_bytes).unwrap();
3011
3012        assert!(intersecting_patches(
3013            &font,
3014            &SubsetDefinition::new(IntSet::all(), FeatureSet::from([]), Default::default()),
3015        )
3016        .is_err());
3017    }
3018
3019    #[test]
3020    fn intersection_info_ordering() {
3021        // these are in the correct order
3022        let v1 = IntersectionInfo::new(5, 9, 1);
3023        let v2 = IntersectionInfo::new(5, 10, 2);
3024        let v3 = IntersectionInfo::new(5, 10, 1);
3025        let v4 = IntersectionInfo::new(6, 1, 10);
3026        let v5 = IntersectionInfo::new(6, 1, 9);
3027
3028        assert_eq!(v1.cmp(&v1), Ordering::Equal);
3029
3030        assert_eq!(v1.cmp(&v2), Ordering::Less);
3031        assert_eq!(v2.cmp(&v1), Ordering::Greater);
3032
3033        assert_eq!(v2.cmp(&v3), Ordering::Less);
3034        assert_eq!(v3.cmp(&v2), Ordering::Greater);
3035
3036        assert_eq!(v3.cmp(&v4), Ordering::Less);
3037        assert_eq!(v4.cmp(&v3), Ordering::Greater);
3038
3039        assert_eq!(v4.cmp(&v5), Ordering::Less);
3040        assert_eq!(v5.cmp(&v4), Ordering::Greater);
3041
3042        assert_eq!(v3.cmp(&v5), Ordering::Less);
3043        assert_eq!(v5.cmp(&v3), Ordering::Greater);
3044    }
3045
3046    #[test]
3047    fn intersection_info_ordering_with_design_space() {
3048        let aaaa = Tag::new(b"aaaa");
3049        let bbbb = Tag::new(b"bbbb");
3050
3051        // these are in the correct order
3052        let v1 = IntersectionInfo::from_design_space(1, 0, [(aaaa, 4i32.into())], 0);
3053        let v2 = IntersectionInfo::from_design_space(
3054            1,
3055            0,
3056            [(aaaa, 5i32.into()), (bbbb, 2i32.into())],
3057            0,
3058        );
3059        let v3 = IntersectionInfo::from_design_space(1, 0, [(aaaa, 6i32.into())], 0);
3060        let v4 = IntersectionInfo::from_design_space(
3061            1,
3062            0,
3063            [(aaaa, 6i32.into()), (bbbb, 2i32.into())],
3064            0,
3065        );
3066        let v5 = IntersectionInfo::from_design_space(1, 0, [(bbbb, 1i32.into())], 0);
3067        let v6 = IntersectionInfo::from_design_space(2, 0, [(aaaa, 1i32.into())], 0);
3068
3069        assert_eq!(v1.cmp(&v1), Ordering::Equal);
3070
3071        assert_eq!(v1.cmp(&v2), Ordering::Less);
3072        assert_eq!(v2.cmp(&v1), Ordering::Greater);
3073
3074        assert_eq!(v2.cmp(&v3), Ordering::Less);
3075        assert_eq!(v3.cmp(&v2), Ordering::Greater);
3076
3077        assert_eq!(v3.cmp(&v4), Ordering::Less);
3078        assert_eq!(v4.cmp(&v3), Ordering::Greater);
3079
3080        assert_eq!(v4.cmp(&v5), Ordering::Less);
3081        assert_eq!(v5.cmp(&v4), Ordering::Greater);
3082
3083        assert_eq!(v5.cmp(&v6), Ordering::Less);
3084        assert_eq!(v6.cmp(&v5), Ordering::Greater);
3085
3086        assert_eq!(v3.cmp(&v5), Ordering::Less);
3087        assert_eq!(v5.cmp(&v3), Ordering::Greater);
3088    }
3089
3090    #[test]
3091    fn entry_design_codepoints_intersection() {
3092        let url = PatchUrl::expand_template(RELATIVE_URL_TEMPLATE, &PatchId::Numeric(0)).unwrap();
3093
3094        let s1 = SubsetDefinition::codepoints([3, 5, 7].into_iter().collect());
3095        let s2 = SubsetDefinition::codepoints([13, 15, 17].into_iter().collect());
3096        let s3 = SubsetDefinition::codepoints([7, 13].into_iter().collect());
3097
3098        let e1 = Format2Entry {
3099            subset_definition: s1.clone(),
3100            child_indices: Default::default(),
3101            conjunctive_child_match: Default::default(),
3102            ignored: false,
3103
3104            urls: vec![url.clone()],
3105            format: PatchFormat::GlyphKeyed,
3106            application_flag_bit_index: 0,
3107        };
3108        let e2 = Format2Entry {
3109            subset_definition: Default::default(),
3110            child_indices: Default::default(),
3111            conjunctive_child_match: Default::default(),
3112            ignored: false,
3113
3114            urls: vec![url.clone()],
3115            format: PatchFormat::GlyphKeyed,
3116            application_flag_bit_index: 0,
3117        };
3118
3119        assert!(e1.intersects(&s1));
3120        assert_eq!(s1.intersection(&s1), s1);
3121
3122        assert!(!e1.intersects(&s2));
3123        assert_eq!(s1.intersection(&s2), Default::default());
3124
3125        assert!(e1.intersects(&s3));
3126        assert_eq!(
3127            s1.intersection(&s3),
3128            SubsetDefinition::codepoints([7].into_iter().collect())
3129        );
3130
3131        assert!(e2.intersects(&s1));
3132        assert_eq!(
3133            SubsetDefinition::default().intersection(&s1),
3134            Default::default()
3135        );
3136    }
3137
3138    #[test]
3139    fn entry_design_space_intersection() {
3140        let url = PatchUrl::expand_template(RELATIVE_URL_TEMPLATE, &PatchId::Numeric(0)).unwrap();
3141
3142        let s1 = SubsetDefinition::new(
3143            Default::default(),
3144            Default::default(),
3145            DesignSpace::from([
3146                (
3147                    Tag::new(b"aaaa"),
3148                    [Fixed::from_i32(100)..=Fixed::from_i32(200)]
3149                        .into_iter()
3150                        .collect(),
3151                ),
3152                (
3153                    Tag::new(b"bbbb"),
3154                    [
3155                        Fixed::from_i32(300)..=Fixed::from_i32(600),
3156                        Fixed::from_i32(700)..=Fixed::from_i32(900),
3157                    ]
3158                    .into_iter()
3159                    .collect(),
3160                ),
3161            ]),
3162        );
3163        let s2 = SubsetDefinition::new(
3164            Default::default(),
3165            Default::default(),
3166            DesignSpace::from([
3167                (
3168                    Tag::new(b"bbbb"),
3169                    [Fixed::from_i32(100)..=Fixed::from_i32(200)]
3170                        .into_iter()
3171                        .collect(),
3172                ),
3173                (
3174                    Tag::new(b"cccc"),
3175                    [Fixed::from_i32(300)..=Fixed::from_i32(600)]
3176                        .into_iter()
3177                        .collect(),
3178                ),
3179            ]),
3180        );
3181        let s3 = SubsetDefinition::new(
3182            Default::default(),
3183            Default::default(),
3184            DesignSpace::from([
3185                (
3186                    Tag::new(b"bbbb"),
3187                    [Fixed::from_i32(500)..=Fixed::from_i32(800)]
3188                        .into_iter()
3189                        .collect(),
3190                ),
3191                (
3192                    Tag::new(b"cccc"),
3193                    [Fixed::from_i32(300)..=Fixed::from_i32(600)]
3194                        .into_iter()
3195                        .collect(),
3196                ),
3197            ]),
3198        );
3199        let s4 = SubsetDefinition::new(
3200            Default::default(),
3201            Default::default(),
3202            DesignSpace::from([(
3203                Tag::new(b"bbbb"),
3204                [
3205                    Fixed::from_i32(500)..=Fixed::from_i32(600),
3206                    Fixed::from_i32(700)..=Fixed::from_i32(800),
3207                ]
3208                .into_iter()
3209                .collect(),
3210            )]),
3211        );
3212
3213        let e1 = Format2Entry {
3214            subset_definition: s1.clone(),
3215            child_indices: Default::default(),
3216            conjunctive_child_match: Default::default(),
3217            ignored: false,
3218
3219            urls: vec![url.clone()],
3220            format: PatchFormat::GlyphKeyed,
3221            application_flag_bit_index: 0,
3222        };
3223
3224        let e2 = Format2Entry {
3225            subset_definition: Default::default(),
3226            child_indices: Default::default(),
3227            conjunctive_child_match: Default::default(),
3228            ignored: false,
3229
3230            urls: vec![url.clone()],
3231            format: PatchFormat::GlyphKeyed,
3232            application_flag_bit_index: 0,
3233        };
3234
3235        assert!(e1.intersects(&s1));
3236        assert_eq!(s1.intersection(&s1), s1.clone());
3237
3238        assert!(!e1.intersects(&s2));
3239        assert_eq!(s1.intersection(&s2), Default::default());
3240
3241        assert!(e1.intersects(&s3));
3242        assert_eq!(s1.intersection(&s3), s4.clone());
3243
3244        assert!(e2.intersects(&s1));
3245        assert_eq!(
3246            SubsetDefinition::default().intersection(&s1),
3247            SubsetDefinition::default()
3248        );
3249    }
3250
3251    #[test]
3252    fn feature_set_extend_insert() {
3253        let mut features: FeatureSet = Default::default();
3254
3255        let foo = Tag::from_str("fooo").unwrap();
3256        let bar = Tag::from_str("baar").unwrap();
3257        let baz = Tag::from_str("baaz").unwrap();
3258
3259        features.extend([foo, bar].into_iter());
3260        features.insert(baz);
3261        features.insert(foo);
3262
3263        assert_eq!(features, FeatureSet::Set(BTreeSet::from([foo, bar, baz])));
3264
3265        let mut features: FeatureSet = FeatureSet::All;
3266
3267        features.extend([foo, bar].into_iter());
3268        features.insert(baz);
3269        features.insert(foo);
3270
3271        assert_eq!(features, FeatureSet::All);
3272    }
3273}