1include!("../../generated/generated_cmap.rs");
6
7use std::collections::HashMap;
8
9use crate::util::SearchRange;
10
11const WINDOWS_BMP_ENCODING: u16 = 1;
13const WINDOWS_FULL_REPERTOIRE_ENCODING: u16 = 10;
14
15const UNICODE_BMP_ENCODING: u16 = 3;
17const UNICODE_FULL_REPERTOIRE_ENCODING: u16 = 4;
18
19impl CmapSubtable {
20 fn create_format_4(mappings: &[(char, GlyphId)]) -> Option<Self> {
30 let mut end_code = Vec::with_capacity(mappings.len() + 1);
31 let mut start_code = Vec::with_capacity(mappings.len() + 1);
32 let mut id_deltas = Vec::with_capacity(mappings.len() + 1);
33 let mut id_range_offsets = Vec::with_capacity(mappings.len() + 1);
34 let mut glyph_ids = Vec::new();
35
36 let segments = Format4SegmentComputer::new(mappings).compute();
37 assert!(mappings.iter().all(|(_, g)| g.to_u32() <= 0xFFFF));
38 if segments.is_empty() {
39 return None;
41 }
42 let n_segments = segments.len() + 1;
43 for (i, segment) in segments.into_iter().enumerate() {
44 let start = mappings[segment.start_ix].0;
45 let end = mappings[segment.end_ix].0;
46 start_code.push(start as u32 as u16);
47 end_code.push(end as u32 as u16);
48 if let Some(delta) = segment.id_delta {
49 let delta = i16::try_from(delta)
51 .unwrap_or_else(|_| delta.rem_euclid(0x10000).try_into().unwrap());
52 id_deltas.push(delta);
53 id_range_offsets.push(0u16);
54 } else {
55 let current_n_ids = glyph_ids.len();
62 let n_following_segments = n_segments - i;
63 let id_range_offset = (n_following_segments + current_n_ids) * u16::RAW_BYTE_LEN;
66 id_deltas.push(0);
67 id_range_offsets.push(id_range_offset.try_into().unwrap());
68 glyph_ids.extend(
69 mappings[segment.start_ix..=segment.end_ix]
70 .iter()
71 .map(|(_, gid)| u16::try_from(gid.to_u32()).expect("checked before now")),
72 )
73 }
74 }
75
76 end_code.push(0xFFFF);
78 start_code.push(0xFFFF);
79 id_deltas.push(1);
80 id_range_offsets.push(0);
81
82 Some(Self::format_4(
83 0,
84 end_code,
85 start_code,
86 id_deltas,
87 id_range_offsets,
88 glyph_ids,
89 ))
90 }
91
92 fn create_format_12(mappings: &[(char, GlyphId)]) -> Self {
97 let (mut char_codes, gids): (Vec<u32>, Vec<u32>) = mappings
98 .iter()
99 .map(|(cp, gid)| (*cp as u32, gid.to_u32()))
100 .unzip();
101 let cmap: HashMap<_, _> = char_codes.iter().cloned().zip(gids).collect();
102 char_codes.dedup();
103
104 let mut start_char_code = *char_codes.first().unwrap();
106 let mut start_glyph_id = cmap[&start_char_code];
107 let mut last_glyph_id = start_glyph_id.wrapping_sub(1);
108 let mut last_char_code = start_char_code.wrapping_sub(1);
109 let mut groups = Vec::new();
110 for char_code in char_codes {
111 let glyph_id = cmap[&char_code];
112 if glyph_id != last_glyph_id.wrapping_add(1)
113 || char_code != last_char_code.wrapping_add(1)
114 {
115 groups.push((start_char_code, last_char_code, start_glyph_id));
116 start_char_code = char_code;
117 start_glyph_id = glyph_id;
118 }
119 last_glyph_id = glyph_id;
120 last_char_code = char_code;
121 }
122 groups.push((start_char_code, last_char_code, start_glyph_id));
123
124 let seq_map_groups = groups
125 .into_iter()
126 .map(|(start_char, end_char, gid)| SequentialMapGroup::new(start_char, end_char, gid))
127 .collect::<Vec<_>>();
128 CmapSubtable::format_12(
129 0, seq_map_groups,
131 )
132 }
133}
134
135#[derive(Debug, Clone, PartialEq, Eq)]
140pub struct CmapConflict {
141 ch: char,
142 gid1: GlyphId,
143 gid2: GlyphId,
144}
145
146impl std::fmt::Display for CmapConflict {
147 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
148 let ch32 = self.ch as u32;
149 write!(
150 f,
151 "Cannot map {:?} (U+{ch32:04X}) to two different glyph ids: {} and {}",
152 self.ch, self.gid1, self.gid2
153 )
154 }
155}
156
157impl std::error::Error for CmapConflict {}
158
159impl Cmap {
160 pub fn from_mappings(
173 mappings: impl IntoIterator<Item = (char, GlyphId)>,
174 ) -> Result<Cmap, CmapConflict> {
175 let mut mappings: Vec<_> = mappings.into_iter().collect();
176 mappings.sort();
177 mappings.dedup();
178 if let Some((ch, gid1, gid2)) =
179 mappings
180 .iter()
181 .zip(mappings.iter().skip(1))
182 .find_map(|((c1, g1), (c2, g2))| {
183 (c1 == c2 && g1 != g2).then(|| (*c1, *g1.min(g2), *g1.max(g2)))
184 })
185 {
186 return Err(CmapConflict { ch, gid1, gid2 });
187 }
188
189 let mut uni_records = Vec::new(); let mut win_records = Vec::new(); let bmp_subtable = CmapSubtable::create_format_4(&mappings);
195 if let Some(bmp_subtable) = bmp_subtable {
196 uni_records.push(EncodingRecord::new(
201 PlatformId::Unicode,
202 UNICODE_BMP_ENCODING,
203 bmp_subtable.clone(),
204 ));
205 win_records.push(EncodingRecord::new(
206 PlatformId::Windows,
207 WINDOWS_BMP_ENCODING,
208 bmp_subtable,
209 ));
210 }
211
212 if mappings.iter().any(|(cp, _)| *cp > '\u{FFFF}') {
215 let full_repertoire_subtable = CmapSubtable::create_format_12(&mappings);
216 uni_records.push(EncodingRecord::new(
218 PlatformId::Unicode,
219 UNICODE_FULL_REPERTOIRE_ENCODING,
220 full_repertoire_subtable.clone(),
221 ));
222 win_records.push(EncodingRecord::new(
223 PlatformId::Windows,
224 WINDOWS_FULL_REPERTOIRE_ENCODING,
225 full_repertoire_subtable,
226 ));
227 }
228
229 Ok(Cmap::new(
235 uni_records.into_iter().chain(win_records).collect(),
236 ))
237 }
238}
239
240struct Format4SegmentComputer<'a> {
242 mappings: &'a [(char, GlyphId)],
243 seg_start: usize,
245 gids_in_order: bool,
247}
248
249#[derive(Clone, Copy, Debug)]
250struct Format4Segment {
251 start_ix: usize,
253 end_ix: usize,
254 start_char: char,
255 end_char: char,
256 id_delta: Option<i32>,
257}
258
259impl Format4Segment {
260 fn len(&self) -> usize {
261 self.end_ix - self.start_ix + 1
262 }
263
264 fn cost(&self) -> usize {
266 const BASE_COST: usize = 4 * u16::RAW_BYTE_LEN;
268
269 if self.id_delta.is_some() {
270 BASE_COST
271 } else {
272 BASE_COST + self.len() * u16::RAW_BYTE_LEN
275 }
276 }
277
278 fn can_combine(&self, next: &Self) -> bool {
280 self.end_char as u32 + 1 == next.start_char as u32
281 }
282
283 fn should_combine(&self, prev: &Self, next: Option<&Self>) -> bool {
289 if !prev.can_combine(self) {
290 return false;
291 }
292
293 let combined_cost = prev.combine(self).cost();
296 let separate_cost = prev.cost() + self.cost();
297
298 if combined_cost < separate_cost {
299 return true;
300 }
301
302 if let Some(next) = next.filter(|next| self.can_combine(next)) {
342 let combined_cost = prev.combine(self).combine(next).cost();
343 let separate_cost = separate_cost + next.cost();
344 return combined_cost < separate_cost;
345 }
346
347 false
348 }
349
350 fn combine(&self, next: &Format4Segment) -> Format4Segment {
354 assert_eq!(next.start_ix, self.end_ix + 1,);
355 Format4Segment {
356 start_ix: self.start_ix,
357 start_char: self.start_char,
358 end_char: next.end_char,
359 end_ix: next.end_ix,
360 id_delta: None,
361 }
362 }
363}
364
365impl<'a> Format4SegmentComputer<'a> {
366 fn new(mappings: &'a [(char, GlyphId)]) -> Self {
367 let mappings = mappings
369 .iter()
370 .position(|(c, _)| u16::try_from(*c as u32).is_err())
371 .map(|bad_idx| &mappings[..bad_idx])
372 .unwrap_or(mappings);
373 Self {
374 mappings,
375 seg_start: 0,
376 gids_in_order: false,
377 }
378 }
379
380 fn make_segment(&mut self, seg_len: usize) -> Format4Segment {
385 let use_delta = self.gids_in_order || seg_len == 0;
387 let start_ix = self.seg_start;
388 let end_ix = self.seg_start + seg_len;
389 let start_char = self.mappings[start_ix].0;
390 let end_char = self.mappings[end_ix].0;
391 let result = Format4Segment {
392 start_ix,
393 end_ix,
394 start_char,
395 end_char,
396 id_delta: self
397 .mappings
398 .get(self.seg_start)
399 .map(|(cp, gid)| gid.to_u32() as i32 - *cp as u32 as i32)
400 .filter(|_| use_delta),
401 };
402 self.seg_start += seg_len + 1;
403 self.gids_in_order = false;
404 result
405 }
406
407 fn next_possible_segment(&mut self) -> Option<Format4Segment> {
413 if self.seg_start == self.mappings.len() {
414 return None;
415 }
416
417 let Some(((mut prev_cp, mut prev_gid), rest)) =
418 self.mappings[self.seg_start..].split_first()
419 else {
420 return Some(self.make_segment(0));
422 };
423
424 for (i, (cp, gid)) in rest.iter().enumerate() {
425 if *cp as u32 != prev_cp as u32 + 1 {
427 return Some(self.make_segment(i));
428 }
429 let next_gid_is_in_order = prev_gid.to_u32() + 1 == gid.to_u32();
430 if !next_gid_is_in_order {
431 if self.gids_in_order {
433 return Some(self.make_segment(i));
434 }
435 } else if !self.gids_in_order {
441 if i == 0 {
442 self.gids_in_order = true;
443 } else {
444 return Some(self.make_segment(i - 1));
445 }
446 }
447 prev_cp = *cp;
448 prev_gid = *gid;
449 }
450
451 let last_idx = self.mappings.len() - 1;
453 Some(self.make_segment(last_idx - self.seg_start))
454 }
455
456 fn compute(mut self) -> Vec<Format4Segment> {
481 let Some(first) = self.next_possible_segment() else {
482 return Default::default();
483 };
484
485 let mut result = vec![first];
486
487 let mut next = self.next_possible_segment();
490
491 while let Some(current) = next.take() {
492 next = self.next_possible_segment();
493 let prev = result.last_mut().unwrap();
494 if current.should_combine(prev, next.as_ref()) {
495 *prev = prev.combine(¤t);
496 continue;
497 }
498
499 result.push(current);
500 }
501 result
502 }
503}
504
505impl Cmap4 {
506 fn compute_length(&self) -> u16 {
507 const FIXED_SIZE: usize = 8 * u16::RAW_BYTE_LEN;
510 const PER_SEGMENT_LEN: usize = 4 * u16::RAW_BYTE_LEN;
511
512 let segment_len = self.end_code.len() * PER_SEGMENT_LEN;
513 let gid_len = self.glyph_id_array.len() * u16::RAW_BYTE_LEN;
514
515 (FIXED_SIZE + segment_len + gid_len)
516 .try_into()
517 .expect("cmap4 overflow")
518 }
519
520 fn compute_search_range(&self) -> u16 {
521 SearchRange::compute(self.end_code.len(), u16::RAW_BYTE_LEN).search_range
522 }
523
524 fn compute_entry_selector(&self) -> u16 {
525 SearchRange::compute(self.end_code.len(), u16::RAW_BYTE_LEN).entry_selector
526 }
527
528 fn compute_range_shift(&self) -> u16 {
529 SearchRange::compute(self.end_code.len(), u16::RAW_BYTE_LEN).range_shift
530 }
531}
532
533impl Cmap12 {
534 fn compute_length(&self) -> u32 {
535 const FIXED_SIZE: usize = 2 * u16::RAW_BYTE_LEN + 3 * u32::RAW_BYTE_LEN;
537 const PER_SEGMENT_LEN: usize = 3 * u32::RAW_BYTE_LEN;
538
539 (FIXED_SIZE + PER_SEGMENT_LEN * self.groups.len())
540 .try_into()
541 .unwrap()
542 }
543}
544
545#[cfg(test)]
546mod tests {
547 use std::ops::RangeInclusive;
548
549 use font_types::GlyphId;
550 use read_fonts::{
551 tables::cmap::{Cmap, CmapSubtable, PlatformId},
552 FontData, FontRead,
553 };
554
555 use crate::{
556 dump_table,
557 tables::cmap::{
558 self as write, CmapConflict, UNICODE_BMP_ENCODING, UNICODE_FULL_REPERTOIRE_ENCODING,
559 WINDOWS_BMP_ENCODING, WINDOWS_FULL_REPERTOIRE_ENCODING,
560 },
561 };
562
563 use super::{Cmap12, SequentialMapGroup};
564
565 fn assert_generates_simple_cmap(mappings: Vec<(char, GlyphId)>) {
566 let cmap = write::Cmap::from_mappings(mappings).unwrap();
567
568 let bytes = dump_table(&cmap).unwrap();
569 let font_data = FontData::new(&bytes);
570 let cmap = Cmap::read(font_data).unwrap();
571
572 assert_eq!(2, cmap.encoding_records().len(), "{cmap:?}");
573 assert_eq!(
574 vec![
575 (PlatformId::Unicode, UNICODE_BMP_ENCODING),
576 (PlatformId::Windows, WINDOWS_BMP_ENCODING)
577 ],
578 cmap.encoding_records()
579 .iter()
580 .map(|er| (er.platform_id(), er.encoding_id()))
581 .collect::<Vec<_>>(),
582 "{cmap:?}"
583 );
584
585 for encoding_record in cmap.encoding_records() {
586 let CmapSubtable::Format4(cmap4) = encoding_record.subtable(font_data).unwrap() else {
587 panic!("Expected a cmap4 in {encoding_record:?}");
588 };
589
590 assert_eq!(
592 (8, 8, 2, 0),
593 (
594 cmap4.seg_count_x2(),
595 cmap4.search_range(),
596 cmap4.entry_selector(),
597 cmap4.range_shift()
598 )
599 );
600 assert_eq!(cmap4.start_code(), &[10u16, 30u16, 153u16, 0xffffu16]);
601 assert_eq!(cmap4.end_code(), &[20u16, 90u16, 480u16, 0xffffu16]);
602 assert_eq!(cmap4.id_delta(), &[-10i16, -19i16, -81i16, 1i16]);
604 assert_eq!(cmap4.id_range_offsets(), &[0u16, 0u16, 0u16, 0u16]);
605 }
606 }
607
608 fn simple_cmap_mappings() -> Vec<(char, GlyphId)> {
609 (10..=20)
610 .chain(30..=90)
611 .chain(153..=480)
612 .enumerate()
613 .map(|(idx, codepoint)| (char::from_u32(codepoint).unwrap(), GlyphId::new(idx as u32)))
614 .collect()
615 }
616
617 #[test]
620 fn generate_simple_cmap4() {
621 let mappings = simple_cmap_mappings();
622 assert_generates_simple_cmap(mappings);
623 }
624
625 #[test]
626 fn generate_cmap4_out_of_order_input() {
627 let mut ordered = simple_cmap_mappings();
628 let mut disordered = Vec::new();
629 while !ordered.is_empty() {
630 if ordered.len() % 2 == 0 {
631 disordered.insert(0, ordered.remove(0));
632 } else {
633 disordered.push(ordered.remove(0));
634 }
635 }
636 assert_ne!(ordered, disordered);
637 assert_generates_simple_cmap(disordered);
638 }
639
640 #[test]
641 fn generate_cmap4_large_values() {
642 let mut mappings = simple_cmap_mappings();
643 let codepoint = char::from_u32(0xa78b).unwrap();
645 let gid = GlyphId::new(153);
646 mappings.push((codepoint, gid));
647
648 let cmap = write::Cmap::from_mappings(mappings).unwrap();
649
650 let bytes = dump_table(&cmap).unwrap();
651 let font_data = FontData::new(&bytes);
652 let cmap = Cmap::read(font_data).unwrap();
653 assert_eq!(cmap.map_codepoint(codepoint), Some(gid));
654 }
655
656 #[test]
657 fn bytes_are_reused() {
658 let mappings = simple_cmap_mappings();
660 let cmap_both = write::Cmap::from_mappings(mappings).unwrap();
661 assert_eq!(2, cmap_both.encoding_records.len(), "{cmap_both:?}");
662
663 let bytes_for_both = dump_table(&cmap_both).unwrap().len();
664
665 for i in 0..cmap_both.encoding_records.len() {
666 let mut cmap = cmap_both.clone();
667 cmap.encoding_records.remove(i);
668 let bytes_for_one = dump_table(&cmap).unwrap().len();
669 assert_eq!(bytes_for_one + 8, bytes_for_both);
670 }
671 }
672
673 fn non_bmp_cmap_mappings() -> Vec<(char, GlyphId)> {
674 vec![
676 ('\u{1f12f}', GlyphId::new(481)),
678 ('\u{1f130}', GlyphId::new(482)),
679 ('\u{1f132}', GlyphId::new(483)),
681 ('\u{1f133}', GlyphId::new(484)),
682 ('\u{1f134}', GlyphId::new(486)),
684 ('\u{1f136}', GlyphId::new(488)),
686 ('\u{1f136}', GlyphId::new(488)),
687 ]
688 }
689
690 fn bmp_and_non_bmp_cmap_mappings() -> Vec<(char, GlyphId)> {
691 let mut mappings = simple_cmap_mappings();
692 mappings.extend(non_bmp_cmap_mappings());
693 mappings
694 }
695
696 fn assert_cmap12_groups(
697 font_data: FontData,
698 cmap: &Cmap,
699 record_index: usize,
700 expected: &[(u32, u32, u32)],
701 ) {
702 let rec = &cmap.encoding_records()[record_index];
703 let CmapSubtable::Format12(subtable) = rec.subtable(font_data).unwrap() else {
704 panic!("Expected a cmap12 in {rec:?}");
705 };
706 let groups = subtable
707 .groups()
708 .iter()
709 .map(|g| (g.start_char_code(), g.end_char_code(), g.start_glyph_id()))
710 .collect::<Vec<_>>();
711 assert_eq!(groups.len(), expected.len());
712 assert_eq!(groups, expected);
713 }
714
715 #[test]
716 fn generate_cmap4_and_12() {
717 let mappings = bmp_and_non_bmp_cmap_mappings();
718
719 let cmap = write::Cmap::from_mappings(mappings).unwrap();
720
721 let bytes = dump_table(&cmap).unwrap();
722 let font_data = FontData::new(&bytes);
723 let cmap = Cmap::read(font_data).unwrap();
724
725 assert_eq!(4, cmap.encoding_records().len(), "{cmap:?}");
726 assert_eq!(
727 vec![
728 (PlatformId::Unicode, UNICODE_BMP_ENCODING),
729 (PlatformId::Unicode, UNICODE_FULL_REPERTOIRE_ENCODING),
730 (PlatformId::Windows, WINDOWS_BMP_ENCODING),
731 (PlatformId::Windows, WINDOWS_FULL_REPERTOIRE_ENCODING)
732 ],
733 cmap.encoding_records()
734 .iter()
735 .map(|er| (er.platform_id(), er.encoding_id()))
736 .collect::<Vec<_>>(),
737 "{cmap:?}"
738 );
739
740 let encoding_records = cmap.encoding_records();
741 let first_rec = &encoding_records[0];
742 assert!(
743 matches!(
744 first_rec.subtable(font_data).unwrap(),
745 CmapSubtable::Format4(_)
746 ),
747 "Expected a cmap4 in {first_rec:?}"
748 );
749
750 let expected_groups = vec![
752 (10, 20, 0),
753 (30, 90, 11),
754 (153, 480, 72),
755 (0x1f12f, 0x1f130, 481),
756 (0x1f132, 0x1f133, 483),
757 (0x1f134, 0x1f134, 486),
758 (0x1f136, 0x1f136, 488),
759 ];
760 assert_cmap12_groups(font_data, &cmap, 1, &expected_groups);
761 assert_cmap12_groups(font_data, &cmap, 3, &expected_groups);
762 }
763
764 #[test]
765 fn generate_cmap12_only() {
766 let mappings = non_bmp_cmap_mappings();
767
768 let cmap = write::Cmap::from_mappings(mappings).unwrap();
769
770 let bytes = dump_table(&cmap).unwrap();
771 let font_data = FontData::new(&bytes);
772 let cmap = Cmap::read(font_data).unwrap();
773
774 assert_eq!(2, cmap.encoding_records().len(), "{cmap:?}");
775 assert_eq!(
776 vec![
777 (PlatformId::Unicode, UNICODE_FULL_REPERTOIRE_ENCODING),
778 (PlatformId::Windows, WINDOWS_FULL_REPERTOIRE_ENCODING)
779 ],
780 cmap.encoding_records()
781 .iter()
782 .map(|er| (er.platform_id(), er.encoding_id()))
783 .collect::<Vec<_>>(),
784 "{cmap:?}"
785 );
786
787 let expected_groups = vec![
789 (0x1f12f, 0x1f130, 481),
790 (0x1f132, 0x1f133, 483),
791 (0x1f134, 0x1f134, 486),
792 (0x1f136, 0x1f136, 488),
793 ];
794 assert_cmap12_groups(font_data, &cmap, 0, &expected_groups);
795 assert_cmap12_groups(font_data, &cmap, 1, &expected_groups);
796 }
797
798 #[test]
799 fn multiple_mappings_fails() {
800 let mut mappings = non_bmp_cmap_mappings();
801 let (ch, gid1) = mappings[0];
803 let gid2 = GlyphId::new(gid1.to_u32() + 1);
804 mappings.push((ch, gid2));
805
806 let result = write::Cmap::from_mappings(mappings);
807
808 assert_eq!(result, Err(CmapConflict { ch, gid1, gid2 }))
809 }
810
811 struct MappingBuilder {
812 mappings: Vec<(char, GlyphId)>,
813 next_gid: u16,
814 }
815
816 impl Default for MappingBuilder {
817 fn default() -> Self {
818 Self {
819 mappings: Default::default(),
820 next_gid: 1,
821 }
822 }
823 }
824
825 impl MappingBuilder {
826 fn extend(mut self, range: impl IntoIterator<Item = char>) -> Self {
827 for c in range {
828 let gid = GlyphId::new(self.next_gid as _);
829 self.mappings.push((c, gid));
830 self.next_gid += 1;
831 }
832 self
833 }
834
835 fn compute(&mut self) -> Vec<RangeInclusive<char>> {
837 self.mappings.sort();
838 super::Format4SegmentComputer::new(&self.mappings)
839 .compute()
840 .into_iter()
841 .map(|seg| self.mappings[seg.start_ix].0..=self.mappings[seg.end_ix].0)
842 .collect()
843 }
844
845 fn build(mut self) -> Vec<(char, GlyphId)> {
846 self.mappings.sort();
847 self.mappings
848 }
849 }
850
851 #[test]
852 fn f4_segments_simple() {
853 let mut one_big_discontiguous_mapping = MappingBuilder::default().extend(('a'..='z').rev());
854 assert_eq!(one_big_discontiguous_mapping.compute(), ['a'..='z']);
855 }
856
857 #[test]
858 fn f4_segments_combine_small() {
859 let mut mapping = MappingBuilder::default()
860 .extend(['e', 'd', 'c', 'b', 'a'])
862 .extend('f'..='g')
865 .extend('m'..='n')
866 .extend(('o'..='z').rev());
867
868 assert_eq!(mapping.compute(), ['a'..='g', 'm'..='z']);
869 }
870
871 #[test]
872 fn f4_segments_keep() {
873 let mut mapping = MappingBuilder::default()
874 .extend('a'..='m')
875 .extend(['o', 'n']);
876
877 assert_eq!(mapping.compute(), ['a'..='m', 'n'..='o']);
878 }
879
880 fn expect_f4(mapping: &[(char, GlyphId)]) -> super::Cmap4 {
881 let format4 = super::CmapSubtable::create_format_4(mapping).unwrap();
882 let super::CmapSubtable::Format4(format4) = format4 else {
883 panic!("O_o")
884 };
885 format4
886 }
887
888 fn get_read_mapping(table: &super::Cmap4) -> Vec<(char, GlyphId)> {
890 let bytes = dump_table(table).unwrap();
891 let readcmap = read_fonts::tables::cmap::Cmap4::read(bytes.as_slice().into()).unwrap();
892
893 let mut mapping = readcmap
894 .iter()
895 .map(|(c, gid)| (char::from_u32(c).unwrap(), gid))
896 .collect::<Vec<_>>();
897 assert_eq!(mapping.pop(), Some(('\u{FFFF}', GlyphId::NOTDEF)));
900 mapping
901 }
902
903 #[test]
904 fn f4_segment_len_one_uses_delta() {
905 let mapping = MappingBuilder::default()
907 .extend(['a', 'z', '1', '9'])
908 .build();
909
910 let format4 = expect_f4(&mapping);
911 assert_eq!(format4.end_code.len(), 5); assert!(format4.glyph_id_array.is_empty());
913 assert!(format4.id_delta.iter().all(|d| *d != 0));
914 }
915
916 #[test]
917 fn f4_efficiency() {
918 let mapping = MappingBuilder::default()
920 .extend('A'..='Z')
921 .extend(('a'..='z').rev())
922 .build();
923
924 let format4 = expect_f4(&mapping);
925
926 assert_eq!(
927 format4.start_code,
928 ['A' as u32 as u16, 'a' as u32 as u16, 0xffff]
929 );
930
931 assert_eq!(
932 format4.end_code,
933 ['Z' as u32 as u16, 'z' as u32 as u16, 0xffff]
934 );
935
936 assert_eq!(format4.id_delta, [-64, 0, 1]);
937 assert_eq!(format4.id_range_offsets, [0, 4, 0]);
938
939 let read_mapping = get_read_mapping(&format4);
940 assert_eq!(mapping.len(), read_mapping.len());
941 assert!(mapping == read_mapping);
942 }
943
944 #[test]
945 fn f4_kinda_real_world() {
946 let mapping = MappingBuilder::default()
948 .extend(['\r']) .extend('\x20'..='\x7e') .extend('\u{a0}'..='\u{ac}') .extend('\u{ae}'..='\u{17f}') .extend(['\u{18f}', '\u{192}'])
953 .build();
954
955 let format4 = expect_f4(&mapping);
956 assert_eq!(format4.end_code.len(), 7);
958 let read_mapping = get_read_mapping(&format4);
959
960 assert_eq!(mapping.len(), read_mapping.len());
961 assert!(mapping == read_mapping);
962 }
963
964 #[test]
965 fn f4_sandwich_segment() {
968 let mapping = MappingBuilder::default()
969 .extend(['\r'])
970 .extend(('\x20'..='\x27').rev()) .extend('\x28'..='\x2c') .extend(('\x2d'..='\x34').rev()) .extend('\x35'..='\x3e')
976 .build();
977
978 let format4 = expect_f4(&mapping);
979 assert_eq!(format4.end_code.len(), 4);
980 }
981
982 #[test]
984 fn cmap12_length_calculation() {
985 let more_than_16_bits = u16::MAX as u32 + 5;
986 let groups = (0..more_than_16_bits)
987 .map(|i| SequentialMapGroup::new(i, i, i))
988 .collect();
989 let cmap12 = Cmap12::new(0, groups);
990 let bytes = crate::dump_table(&cmap12).unwrap();
991 let read_it_back = Cmap12::read(bytes.as_slice().into()).unwrap();
992 assert_eq!(read_it_back.groups.len() as u32, more_than_16_bits);
993 }
994}