1include!("../../generated/generated_cmap.rs");
6
7use std::collections::HashMap;
8
9use crate::search_range::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 add_final_segment = segments.last().is_none_or(|seg| {
43 (mappings[seg.start_ix].0, mappings[seg.end_ix].0) != ('\u{FFFF}', '\u{FFFF}')
44 });
45 let n_segments = segments.len() + add_final_segment as usize;
46 for (i, segment) in segments.into_iter().enumerate() {
47 let start = mappings[segment.start_ix].0;
48 let end = mappings[segment.end_ix].0;
49 start_code.push(start as u32 as u16);
50 end_code.push(end as u32 as u16);
51 if let Some(delta) = segment.id_delta {
52 let delta = i16::try_from(delta)
54 .unwrap_or_else(|_| delta.rem_euclid(0x10000).try_into().unwrap());
55 id_deltas.push(delta);
56 id_range_offsets.push(0u16);
57 } else {
58 let current_n_ids = glyph_ids.len();
65 let n_following_segments = n_segments - i;
66 let id_range_offset = (n_following_segments + current_n_ids) * u16::RAW_BYTE_LEN;
69 id_deltas.push(0);
70 id_range_offsets.push(id_range_offset.try_into().unwrap());
71 glyph_ids.extend(
72 mappings[segment.start_ix..=segment.end_ix]
73 .iter()
74 .map(|(_, gid)| u16::try_from(gid.to_u32()).expect("checked before now")),
75 )
76 }
77 }
78
79 if add_final_segment {
81 end_code.push(0xFFFF);
82 start_code.push(0xFFFF);
83 id_deltas.push(1);
84 id_range_offsets.push(0);
85 }
86
87 Some(Self::format_4(
88 0,
89 end_code,
90 start_code,
91 id_deltas,
92 id_range_offsets,
93 glyph_ids,
94 ))
95 }
96
97 fn create_format_12(mappings: &[(char, GlyphId)]) -> Self {
102 let (mut char_codes, gids): (Vec<u32>, Vec<u32>) = mappings
103 .iter()
104 .map(|(cp, gid)| (*cp as u32, gid.to_u32()))
105 .unzip();
106 let cmap: HashMap<_, _> = char_codes.iter().cloned().zip(gids).collect();
107 char_codes.dedup();
108
109 let mut start_char_code = *char_codes.first().unwrap();
111 let mut start_glyph_id = cmap[&start_char_code];
112 let mut last_glyph_id = start_glyph_id.wrapping_sub(1);
113 let mut last_char_code = start_char_code.wrapping_sub(1);
114 let mut groups = Vec::new();
115 for char_code in char_codes {
116 let glyph_id = cmap[&char_code];
117 if glyph_id != last_glyph_id.wrapping_add(1)
118 || char_code != last_char_code.wrapping_add(1)
119 {
120 groups.push((start_char_code, last_char_code, start_glyph_id));
121 start_char_code = char_code;
122 start_glyph_id = glyph_id;
123 }
124 last_glyph_id = glyph_id;
125 last_char_code = char_code;
126 }
127 groups.push((start_char_code, last_char_code, start_glyph_id));
128
129 let seq_map_groups = groups
130 .into_iter()
131 .map(|(start_char, end_char, gid)| SequentialMapGroup::new(start_char, end_char, gid))
132 .collect::<Vec<_>>();
133 CmapSubtable::format_12(
134 0, seq_map_groups,
136 )
137 }
138}
139
140#[derive(Debug, Clone, PartialEq, Eq)]
145pub struct CmapConflict {
146 ch: char,
147 gid1: GlyphId,
148 gid2: GlyphId,
149}
150
151impl std::fmt::Display for CmapConflict {
152 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
153 let ch32 = self.ch as u32;
154 write!(
155 f,
156 "Cannot map {:?} (U+{ch32:04X}) to two different glyph ids: {} and {}",
157 self.ch, self.gid1, self.gid2
158 )
159 }
160}
161
162impl std::error::Error for CmapConflict {}
163
164impl Cmap {
165 pub fn from_mappings(
178 mappings: impl IntoIterator<Item = (char, GlyphId)>,
179 ) -> Result<Cmap, CmapConflict> {
180 let mut mappings: Vec<_> = mappings.into_iter().collect();
181 mappings.sort();
182 mappings.dedup();
183 if let Some((ch, gid1, gid2)) =
184 mappings
185 .iter()
186 .zip(mappings.iter().skip(1))
187 .find_map(|((c1, g1), (c2, g2))| {
188 (c1 == c2 && g1 != g2).then(|| (*c1, *g1.min(g2), *g1.max(g2)))
189 })
190 {
191 return Err(CmapConflict { ch, gid1, gid2 });
192 }
193
194 let mut uni_records = Vec::new(); let mut win_records = Vec::new(); let bmp_subtable = CmapSubtable::create_format_4(&mappings);
200 if let Some(bmp_subtable) = bmp_subtable {
201 uni_records.push(EncodingRecord::new(
206 PlatformId::Unicode,
207 UNICODE_BMP_ENCODING,
208 bmp_subtable.clone(),
209 ));
210 win_records.push(EncodingRecord::new(
211 PlatformId::Windows,
212 WINDOWS_BMP_ENCODING,
213 bmp_subtable,
214 ));
215 }
216
217 if mappings.iter().any(|(cp, _)| *cp > '\u{FFFF}') {
220 let full_repertoire_subtable = CmapSubtable::create_format_12(&mappings);
221 uni_records.push(EncodingRecord::new(
223 PlatformId::Unicode,
224 UNICODE_FULL_REPERTOIRE_ENCODING,
225 full_repertoire_subtable.clone(),
226 ));
227 win_records.push(EncodingRecord::new(
228 PlatformId::Windows,
229 WINDOWS_FULL_REPERTOIRE_ENCODING,
230 full_repertoire_subtable,
231 ));
232 }
233
234 Ok(Cmap::new(
240 uni_records.into_iter().chain(win_records).collect(),
241 ))
242 }
243}
244
245struct Format4SegmentComputer<'a> {
247 mappings: &'a [(char, GlyphId)],
248 seg_start: usize,
250 gids_in_order: bool,
252}
253
254#[derive(Clone, Copy, Debug)]
255struct Format4Segment {
256 start_ix: usize,
258 end_ix: usize,
259 start_char: char,
260 end_char: char,
261 id_delta: Option<i32>,
262}
263
264impl Format4Segment {
265 fn len(&self) -> usize {
266 self.end_ix - self.start_ix + 1
267 }
268
269 fn cost(&self) -> usize {
271 const BASE_COST: usize = 4 * u16::RAW_BYTE_LEN;
273
274 if self.id_delta.is_some() {
275 BASE_COST
276 } else {
277 BASE_COST + self.len() * u16::RAW_BYTE_LEN
280 }
281 }
282
283 fn can_combine(&self, next: &Self) -> bool {
285 self.end_char as u32 + 1 == next.start_char as u32
286 }
287
288 fn should_combine(&self, prev: &Self, next: Option<&Self>) -> bool {
294 if !prev.can_combine(self) {
295 return false;
296 }
297
298 let combined_cost = prev.combine(self).cost();
301 let separate_cost = prev.cost() + self.cost();
302
303 if combined_cost < separate_cost {
304 return true;
305 }
306
307 if let Some(next) = next.filter(|next| self.can_combine(next)) {
347 let combined_cost = prev.combine(self).combine(next).cost();
348 let separate_cost = separate_cost + next.cost();
349 return combined_cost < separate_cost;
350 }
351
352 false
353 }
354
355 fn combine(&self, next: &Format4Segment) -> Format4Segment {
359 assert_eq!(next.start_ix, self.end_ix + 1,);
360 Format4Segment {
361 start_ix: self.start_ix,
362 start_char: self.start_char,
363 end_char: next.end_char,
364 end_ix: next.end_ix,
365 id_delta: None,
366 }
367 }
368}
369
370impl<'a> Format4SegmentComputer<'a> {
371 fn new(mappings: &'a [(char, GlyphId)]) -> Self {
372 let mappings = mappings
374 .iter()
375 .position(|(c, _)| u16::try_from(*c as u32).is_err())
376 .map(|bad_idx| &mappings[..bad_idx])
377 .unwrap_or(mappings);
378 Self {
379 mappings,
380 seg_start: 0,
381 gids_in_order: false,
382 }
383 }
384
385 fn make_segment(&mut self, seg_len: usize) -> Format4Segment {
390 let use_delta = self.gids_in_order || seg_len == 0;
392 let start_ix = self.seg_start;
393 let end_ix = self.seg_start + seg_len;
394 let start_char = self.mappings[start_ix].0;
395 let end_char = self.mappings[end_ix].0;
396 let result = Format4Segment {
397 start_ix,
398 end_ix,
399 start_char,
400 end_char,
401 id_delta: self
402 .mappings
403 .get(self.seg_start)
404 .map(|(cp, gid)| gid.to_u32() as i32 - *cp as u32 as i32)
405 .filter(|_| use_delta),
406 };
407 self.seg_start += seg_len + 1;
408 self.gids_in_order = false;
409 result
410 }
411
412 fn next_possible_segment(&mut self) -> Option<Format4Segment> {
418 if self.seg_start == self.mappings.len() {
419 return None;
420 }
421
422 let Some(((mut prev_cp, mut prev_gid), rest)) =
423 self.mappings[self.seg_start..].split_first()
424 else {
425 return Some(self.make_segment(0));
427 };
428
429 for (i, (cp, gid)) in rest.iter().enumerate() {
430 if *cp as u32 != prev_cp as u32 + 1 {
432 return Some(self.make_segment(i));
433 }
434 let next_gid_is_in_order = prev_gid.to_u32() + 1 == gid.to_u32();
435 if !next_gid_is_in_order {
436 if self.gids_in_order {
438 return Some(self.make_segment(i));
439 }
440 } else if !self.gids_in_order {
446 if i == 0 {
447 self.gids_in_order = true;
448 } else {
449 return Some(self.make_segment(i - 1));
450 }
451 }
452 prev_cp = *cp;
453 prev_gid = *gid;
454 }
455
456 let last_idx = self.mappings.len() - 1;
458 Some(self.make_segment(last_idx - self.seg_start))
459 }
460
461 fn compute(mut self) -> Vec<Format4Segment> {
486 let Some(first) = self.next_possible_segment() else {
487 return Default::default();
488 };
489
490 let mut result = vec![first];
491
492 let mut next = self.next_possible_segment();
495
496 while let Some(current) = next.take() {
497 next = self.next_possible_segment();
498 let prev = result.last_mut().unwrap();
499 if current.should_combine(prev, next.as_ref()) {
500 *prev = prev.combine(¤t);
501 continue;
502 }
503
504 result.push(current);
505 }
506 result
507 }
508}
509
510impl Cmap4 {
511 fn compute_length(&self) -> u16 {
512 const FIXED_SIZE: usize = 8 * u16::RAW_BYTE_LEN;
515 const PER_SEGMENT_LEN: usize = 4 * u16::RAW_BYTE_LEN;
516
517 let segment_len = self.end_code.len() * PER_SEGMENT_LEN;
518 let gid_len = self.glyph_id_array.len() * u16::RAW_BYTE_LEN;
519
520 (FIXED_SIZE + segment_len + gid_len)
521 .try_into()
522 .expect("cmap4 overflow")
523 }
524
525 fn compute_search_range(&self) -> u16 {
526 SearchRange::compute(self.end_code.len(), u16::RAW_BYTE_LEN).search_range
527 }
528
529 fn compute_entry_selector(&self) -> u16 {
530 SearchRange::compute(self.end_code.len(), u16::RAW_BYTE_LEN).entry_selector
531 }
532
533 fn compute_range_shift(&self) -> u16 {
534 SearchRange::compute(self.end_code.len(), u16::RAW_BYTE_LEN).range_shift
535 }
536}
537
538impl Cmap12 {
539 fn compute_length(&self) -> u32 {
540 const FIXED_SIZE: usize = 2 * u16::RAW_BYTE_LEN + 3 * u32::RAW_BYTE_LEN;
542 const PER_SEGMENT_LEN: usize = 3 * u32::RAW_BYTE_LEN;
543
544 (FIXED_SIZE + PER_SEGMENT_LEN * self.groups.len())
545 .try_into()
546 .unwrap()
547 }
548}
549
550#[cfg(test)]
551mod tests {
552 use std::ops::RangeInclusive;
553
554 use font_types::GlyphId;
555 use read_fonts::{
556 tables::cmap::{Cmap, CmapSubtable, PlatformId},
557 FontData, FontRead,
558 };
559
560 use crate::{
561 dump_table,
562 tables::cmap::{
563 self as write, CmapConflict, UNICODE_BMP_ENCODING, UNICODE_FULL_REPERTOIRE_ENCODING,
564 WINDOWS_BMP_ENCODING, WINDOWS_FULL_REPERTOIRE_ENCODING,
565 },
566 };
567
568 use super::{Cmap12, SequentialMapGroup};
569
570 fn assert_generates_simple_cmap(mappings: Vec<(char, GlyphId)>) {
571 let cmap = write::Cmap::from_mappings(mappings).unwrap();
572
573 let bytes = dump_table(&cmap).unwrap();
574 let font_data = FontData::new(&bytes);
575 let cmap = Cmap::read(font_data).unwrap();
576
577 assert_eq!(
578 2,
579 cmap.encoding_records().len(),
580 "{:?}",
581 cmap.encoding_records()
582 );
583 assert_eq!(
584 vec![
585 (PlatformId::Unicode, UNICODE_BMP_ENCODING),
586 (PlatformId::Windows, WINDOWS_BMP_ENCODING)
587 ],
588 cmap.encoding_records()
589 .iter()
590 .map(|er| (er.platform_id(), er.encoding_id()))
591 .collect::<Vec<_>>()
592 );
593
594 for encoding_record in cmap.encoding_records() {
595 let CmapSubtable::Format4(cmap4) = encoding_record.subtable(font_data).unwrap() else {
596 panic!("Expected a cmap4 in {encoding_record:?}");
597 };
598
599 assert_eq!(
601 (8, 8, 2, 0),
602 (
603 cmap4.seg_count_x2(),
604 cmap4.search_range(),
605 cmap4.entry_selector(),
606 cmap4.range_shift()
607 )
608 );
609 assert_eq!(cmap4.start_code(), &[10u16, 30u16, 153u16, 0xffffu16]);
610 assert_eq!(cmap4.end_code(), &[20u16, 90u16, 480u16, 0xffffu16]);
611 assert_eq!(cmap4.id_delta(), &[-10i16, -19i16, -81i16, 1i16]);
613 assert_eq!(cmap4.id_range_offsets(), &[0u16, 0u16, 0u16, 0u16]);
614 }
615 }
616
617 fn simple_cmap_mappings() -> Vec<(char, GlyphId)> {
618 (10..=20)
619 .chain(30..=90)
620 .chain(153..=480)
621 .enumerate()
622 .map(|(idx, codepoint)| (char::from_u32(codepoint).unwrap(), GlyphId::new(idx as u32)))
623 .collect()
624 }
625
626 #[test]
629 fn generate_simple_cmap4() {
630 let mappings = simple_cmap_mappings();
631 assert_generates_simple_cmap(mappings);
632 }
633
634 #[test]
635 fn generate_cmap4_out_of_order_input() {
636 let mut ordered = simple_cmap_mappings();
637 let mut disordered = Vec::new();
638 while !ordered.is_empty() {
639 if ordered.len() % 2 == 0 {
640 disordered.insert(0, ordered.remove(0));
641 } else {
642 disordered.push(ordered.remove(0));
643 }
644 }
645 assert_ne!(ordered, disordered);
646 assert_generates_simple_cmap(disordered);
647 }
648
649 #[test]
650 fn generate_cmap4_large_values() {
651 let mut mappings = simple_cmap_mappings();
652 let codepoint = char::from_u32(0xa78b).unwrap();
654 let gid = GlyphId::new(153);
655 mappings.push((codepoint, gid));
656
657 let cmap = write::Cmap::from_mappings(mappings).unwrap();
658
659 let bytes = dump_table(&cmap).unwrap();
660 let font_data = FontData::new(&bytes);
661 let cmap = Cmap::read(font_data).unwrap();
662 assert_eq!(cmap.map_codepoint(codepoint), Some(gid));
663 }
664
665 #[test]
666 fn bytes_are_reused() {
667 let mappings = simple_cmap_mappings();
669 let cmap_both = write::Cmap::from_mappings(mappings).unwrap();
670 assert_eq!(2, cmap_both.encoding_records.len(), "{cmap_both:?}");
671
672 let bytes_for_both = dump_table(&cmap_both).unwrap().len();
673
674 for i in 0..cmap_both.encoding_records.len() {
675 let mut cmap = cmap_both.clone();
676 cmap.encoding_records.remove(i);
677 let bytes_for_one = dump_table(&cmap).unwrap().len();
678 assert_eq!(bytes_for_one + 8, bytes_for_both);
679 }
680 }
681
682 fn non_bmp_cmap_mappings() -> Vec<(char, GlyphId)> {
683 vec![
685 ('\u{1f12f}', GlyphId::new(481)),
687 ('\u{1f130}', GlyphId::new(482)),
688 ('\u{1f132}', GlyphId::new(483)),
690 ('\u{1f133}', GlyphId::new(484)),
691 ('\u{1f134}', GlyphId::new(486)),
693 ('\u{1f136}', GlyphId::new(488)),
695 ('\u{1f136}', GlyphId::new(488)),
696 ]
697 }
698
699 fn bmp_and_non_bmp_cmap_mappings() -> Vec<(char, GlyphId)> {
700 let mut mappings = simple_cmap_mappings();
701 mappings.extend(non_bmp_cmap_mappings());
702 mappings
703 }
704
705 fn assert_cmap12_groups(
706 font_data: FontData,
707 cmap: &Cmap,
708 record_index: usize,
709 expected: &[(u32, u32, u32)],
710 ) {
711 let rec = &cmap.encoding_records()[record_index];
712 let CmapSubtable::Format12(subtable) = rec.subtable(font_data).unwrap() else {
713 panic!("Expected a cmap12 in {rec:?}");
714 };
715 let groups = subtable
716 .groups()
717 .iter()
718 .map(|g| (g.start_char_code(), g.end_char_code(), g.start_glyph_id()))
719 .collect::<Vec<_>>();
720 assert_eq!(groups.len(), expected.len());
721 assert_eq!(groups, expected);
722 }
723
724 #[test]
725 fn generate_cmap4_and_12() {
726 let mappings = bmp_and_non_bmp_cmap_mappings();
727
728 let cmap = write::Cmap::from_mappings(mappings).unwrap();
729
730 let bytes = dump_table(&cmap).unwrap();
731 let font_data = FontData::new(&bytes);
732 let cmap = Cmap::read(font_data).unwrap();
733
734 assert_eq!(
735 4,
736 cmap.encoding_records().len(),
737 "{:?}",
738 cmap.encoding_records()
739 );
740 assert_eq!(
741 vec![
742 (PlatformId::Unicode, UNICODE_BMP_ENCODING),
743 (PlatformId::Unicode, UNICODE_FULL_REPERTOIRE_ENCODING),
744 (PlatformId::Windows, WINDOWS_BMP_ENCODING),
745 (PlatformId::Windows, WINDOWS_FULL_REPERTOIRE_ENCODING)
746 ],
747 cmap.encoding_records()
748 .iter()
749 .map(|er| (er.platform_id(), er.encoding_id()))
750 .collect::<Vec<_>>()
751 );
752
753 let encoding_records = cmap.encoding_records();
754 let first_rec = &encoding_records[0];
755 assert!(
756 matches!(
757 first_rec.subtable(font_data).unwrap(),
758 CmapSubtable::Format4(_)
759 ),
760 "Expected a cmap4 in {first_rec:?}"
761 );
762
763 let expected_groups = vec![
765 (10, 20, 0),
766 (30, 90, 11),
767 (153, 480, 72),
768 (0x1f12f, 0x1f130, 481),
769 (0x1f132, 0x1f133, 483),
770 (0x1f134, 0x1f134, 486),
771 (0x1f136, 0x1f136, 488),
772 ];
773 assert_cmap12_groups(font_data, &cmap, 1, &expected_groups);
774 assert_cmap12_groups(font_data, &cmap, 3, &expected_groups);
775 }
776
777 #[test]
778 fn generate_cmap12_only() {
779 let mappings = non_bmp_cmap_mappings();
780
781 let cmap = write::Cmap::from_mappings(mappings).unwrap();
782
783 let bytes = dump_table(&cmap).unwrap();
784 let font_data = FontData::new(&bytes);
785 let cmap = Cmap::read(font_data).unwrap();
786
787 assert_eq!(
788 2,
789 cmap.encoding_records().len(),
790 "{:?}",
791 cmap.encoding_records()
792 );
793 assert_eq!(
794 vec![
795 (PlatformId::Unicode, UNICODE_FULL_REPERTOIRE_ENCODING),
796 (PlatformId::Windows, WINDOWS_FULL_REPERTOIRE_ENCODING)
797 ],
798 cmap.encoding_records()
799 .iter()
800 .map(|er| (er.platform_id(), er.encoding_id()))
801 .collect::<Vec<_>>()
802 );
803
804 let expected_groups = vec![
806 (0x1f12f, 0x1f130, 481),
807 (0x1f132, 0x1f133, 483),
808 (0x1f134, 0x1f134, 486),
809 (0x1f136, 0x1f136, 488),
810 ];
811 assert_cmap12_groups(font_data, &cmap, 0, &expected_groups);
812 assert_cmap12_groups(font_data, &cmap, 1, &expected_groups);
813 }
814
815 #[test]
816 fn multiple_mappings_fails() {
817 let mut mappings = non_bmp_cmap_mappings();
818 let (ch, gid1) = mappings[0];
820 let gid2 = GlyphId::new(gid1.to_u32() + 1);
821 mappings.push((ch, gid2));
822
823 let result = write::Cmap::from_mappings(mappings);
824
825 assert_eq!(result, Err(CmapConflict { ch, gid1, gid2 }))
826 }
827
828 struct MappingBuilder {
829 mappings: Vec<(char, GlyphId)>,
830 next_gid: u16,
831 }
832
833 impl Default for MappingBuilder {
834 fn default() -> Self {
835 Self {
836 mappings: Default::default(),
837 next_gid: 1,
838 }
839 }
840 }
841
842 impl MappingBuilder {
843 fn extend(mut self, range: impl IntoIterator<Item = char>) -> Self {
844 for c in range {
845 let gid = GlyphId::new(self.next_gid as _);
846 self.mappings.push((c, gid));
847 self.next_gid += 1;
848 }
849 self
850 }
851
852 fn compute(&mut self) -> Vec<RangeInclusive<char>> {
854 self.mappings.sort();
855 super::Format4SegmentComputer::new(&self.mappings)
856 .compute()
857 .into_iter()
858 .map(|seg| self.mappings[seg.start_ix].0..=self.mappings[seg.end_ix].0)
859 .collect()
860 }
861
862 fn build(mut self) -> Vec<(char, GlyphId)> {
863 self.mappings.sort();
864 self.mappings
865 }
866 }
867
868 #[test]
869 fn f4_segments_simple() {
870 let mut one_big_discontiguous_mapping = MappingBuilder::default().extend(('a'..='z').rev());
871 assert_eq!(one_big_discontiguous_mapping.compute(), ['a'..='z']);
872 }
873
874 #[test]
875 fn f4_segments_combine_small() {
876 let mut mapping = MappingBuilder::default()
877 .extend(['e', 'd', 'c', 'b', 'a'])
879 .extend('f'..='g')
882 .extend('m'..='n')
883 .extend(('o'..='z').rev());
884
885 assert_eq!(mapping.compute(), ['a'..='g', 'm'..='z']);
886 }
887
888 #[test]
889 fn f4_segments_keep() {
890 let mut mapping = MappingBuilder::default()
891 .extend('a'..='m')
892 .extend(['o', 'n']);
893
894 assert_eq!(mapping.compute(), ['a'..='m', 'n'..='o']);
895 }
896
897 fn expect_f4(mapping: &[(char, GlyphId)]) -> super::Cmap4 {
898 let format4 = super::CmapSubtable::create_format_4(mapping).unwrap();
899 let super::CmapSubtable::Format4(format4) = format4 else {
900 panic!("O_o")
901 };
902 format4
903 }
904
905 fn get_read_mapping(table: &super::Cmap4) -> Vec<(char, GlyphId)> {
907 let bytes = dump_table(table).unwrap();
908 let readcmap = read_fonts::tables::cmap::Cmap4::read(bytes.as_slice().into()).unwrap();
909
910 let mut mapping = readcmap
911 .iter()
912 .map(|(c, gid)| (char::from_u32(c).unwrap(), gid))
913 .collect::<Vec<_>>();
914 assert_eq!(mapping.pop(), Some(('\u{FFFF}', GlyphId::NOTDEF)));
917 mapping
918 }
919
920 #[test]
921 fn f4_segment_len_one_uses_delta() {
922 let mapping = MappingBuilder::default()
924 .extend(['a', 'z', '1', '9'])
925 .build();
926
927 let format4 = expect_f4(&mapping);
928 assert_eq!(format4.end_code.len(), 5); assert!(format4.glyph_id_array.is_empty());
930 assert!(format4.id_delta.iter().all(|d| *d != 0));
931 }
932
933 #[test]
934 fn f4_efficiency() {
935 let mapping = MappingBuilder::default()
937 .extend('A'..='Z')
938 .extend(('a'..='z').rev())
939 .build();
940
941 let format4 = expect_f4(&mapping);
942
943 assert_eq!(
944 format4.start_code,
945 ['A' as u32 as u16, 'a' as u32 as u16, 0xffff]
946 );
947
948 assert_eq!(
949 format4.end_code,
950 ['Z' as u32 as u16, 'z' as u32 as u16, 0xffff]
951 );
952
953 assert_eq!(format4.id_delta, [-64, 0, 1]);
954 assert_eq!(format4.id_range_offsets, [0, 4, 0]);
955
956 let read_mapping = get_read_mapping(&format4);
957 assert_eq!(mapping.len(), read_mapping.len());
958 assert!(mapping == read_mapping);
959 }
960
961 #[test]
962 fn f4_kinda_real_world() {
963 let mapping = MappingBuilder::default()
965 .extend(['\r']) .extend('\x20'..='\x7e') .extend('\u{a0}'..='\u{ac}') .extend('\u{ae}'..='\u{17f}') .extend(['\u{18f}', '\u{192}'])
970 .build();
971
972 let format4 = expect_f4(&mapping);
973 assert_eq!(format4.end_code.len(), 7);
975 let read_mapping = get_read_mapping(&format4);
976
977 assert_eq!(mapping.len(), read_mapping.len());
978 assert!(mapping == read_mapping);
979 }
980
981 #[test]
982 fn f4_sandwich_segment() {
985 let mapping = MappingBuilder::default()
986 .extend(['\r'])
987 .extend(('\x20'..='\x27').rev()) .extend('\x28'..='\x2c') .extend(('\x2d'..='\x34').rev()) .extend('\x35'..='\x3e')
993 .build();
994
995 let format4 = expect_f4(&mapping);
996 assert_eq!(format4.end_code.len(), 4);
997 }
998
999 #[test]
1001 fn cmap12_length_calculation() {
1002 let more_than_16_bits = u16::MAX as u32 + 5;
1003 let groups = (0..more_than_16_bits)
1004 .map(|i| SequentialMapGroup::new(i, i, i))
1005 .collect();
1006 let cmap12 = Cmap12::new(0, groups);
1007 let bytes = crate::dump_table(&cmap12).unwrap();
1008 let read_it_back = Cmap12::read(bytes.as_slice().into()).unwrap();
1009 assert_eq!(read_it_back.groups.len() as u32, more_than_16_bits);
1010 }
1011
1012 fn cmap4_has_a_unique_final_segment<I>(mappings: I)
1013 where
1014 I: IntoIterator<Item = (char, GlyphId)>,
1015 {
1016 let cmap = crate::tables::cmap::Cmap::from_mappings(mappings).unwrap();
1017 for record in &cmap.encoding_records {
1018 let crate::tables::cmap::CmapSubtable::Format4(cmap4) = &*record.subtable else {
1019 continue;
1020 };
1021 if !matches!((cmap4.start_code.as_slice(), cmap4.end_code.as_slice()),
1022 (&[.., before_last_start, 0xFFFF], &[.., before_last_end, 0xFFFF])
1023 if before_last_start != 0xFFFF || before_last_end != 0xFFFF)
1024 {
1025 panic!(
1026 "Expected cmap4 to end with a single (0xFFFF, 0xFFFF) segment, but found {:?}",
1027 (&cmap4.start_code, &cmap4.end_code)
1028 );
1029 }
1030 }
1031 }
1032
1033 #[test]
1034 fn cmap4_final_segment_is_not_duplicated() {
1035 cmap4_has_a_unique_final_segment([('a', GlyphId::new(1)), ('\u{FFFF}', GlyphId::new(0))]);
1036 }
1037
1038 #[test]
1039 fn cmap4_final_segment_is_added_even_if_last_generated_segment_ends_with_0xffff() {
1040 cmap4_has_a_unique_final_segment([
1041 ('a', GlyphId::new(1)),
1042 ('\u{FFFE}', GlyphId::new(0)),
1043 ('\u{FFFF}', GlyphId::new(0)),
1044 ]);
1045 }
1046}