1use 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
33pub fn intersecting_patches(
37 font: &FontRef,
38 subset_definition: &SubsetDefinition,
39) -> Result<Vec<PatchMapEntry>, ReadError> {
40 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 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 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 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 .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 index.into(),
130 )
131 } else {
132 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 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 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 .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 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 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 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 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 continue;
338 }
339
340 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 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 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 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 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 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 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 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 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 (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 let mut entry: Format2Entry =
615 Format2Entry::base_entry(*default_format, (data_start_index as u32 * 8) + 6);
616
617 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 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 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 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 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 entry.populate_urls(url_template, entry_deltas, last_entry_id, id_string_data)?;
676
677 let (codepoints, trailing_data) =
679 decode_format2_codepoints(entry_data.format_flags(), trailing_data)?;
680 if entry.subset_definition.codepoints.is_empty() {
681 entry.subset_definition.codepoints = codepoints;
683 } else {
684 entry.subset_definition.codepoints.union(&codepoints);
685 }
686
687 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 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 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 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 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#[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 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#[derive(Debug, Clone, Eq, PartialEq, Hash)]
886pub enum PatchId {
887 Numeric(u32),
888 String(Vec<u8>), }
890
891#[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#[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#[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,
992 source_table,
993 application_bit_indices: IntSet::<u32>::empty(), 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(), intersection_info,
1012 }
1013 }
1014}
1015
1016impl AsRef<str> for PatchUrl {
1017 fn as_ref(&self) -> &str {
1018 &self.0
1019 }
1020}
1021
1022#[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 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 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#[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 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#[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#[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 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 (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#[derive(Debug, Clone, PartialEq)]
1284struct Format2Entry {
1285 subset_definition: SubsetDefinition,
1287 child_indices: Vec<usize>,
1288 conjunctive_child_match: bool,
1289 ignored: bool,
1290
1291 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 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 #[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 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 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], [], []); test_intersection(&font, [0x13], [], []); test_intersection(&font, [0x12], [], []); test_intersection(&font, [0x11], [], [f1(2)]); 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 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 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 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 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)], );
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 let mut buffer = codepoints_only_format2();
2469 buffer.write_at("entry_count", Uint24::new(5));
2470 let buffer = buffer
2471 .push(0b00010100u8) .push(Int24::new(-8)) .extend([0b00001101, 0b00000011, 0b00110001u8]); 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 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 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)); 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); 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) .push(Int24::new(0x7FFFFE)); }
2972
2973 let max_delta_without_overflow =
2983 (u32::MAX - ((15 + count * ((0x7FFFFE / 2) + 1)) + 1)) as i32;
2984 data = data
2985 .push(0b01000100u8) .push_with_tag(Int24::new(max_delta_without_overflow * 2), "last delta"); 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 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 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 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}