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
550impl FontWrite for Cmap14 {
562 fn write_into(&self, writer: &mut TableWriter) {
563 14u16.write_into(writer);
564 self.compute_length().write_into(writer);
565 u32::try_from(self.var_selector.len())
566 .unwrap()
567 .write_into(writer);
568
569 let mut next_offset = Self::HEADER_LEN + self.var_selector.len() * Self::RECORD_LEN;
572 let mut write_offset = |writer: &mut TableWriter, len: Option<usize>| match len {
573 Some(len) => {
574 (next_offset as u32).write_into(writer);
575 next_offset += len;
576 }
577 None => 0u32.write_into(writer),
578 };
579 for record in &self.var_selector {
580 record.var_selector.write_into(writer);
581 write_offset(
582 writer,
583 record.default_uvs.as_ref().map(DefaultUvs::compute_len),
584 );
585 write_offset(
586 writer,
587 record
588 .non_default_uvs
589 .as_ref()
590 .map(NonDefaultUvs::compute_len),
591 );
592 }
593 for record in &self.var_selector {
594 if let Some(uvs) = record.default_uvs.as_ref() {
595 uvs.write_into(writer);
596 }
597 if let Some(uvs) = record.non_default_uvs.as_ref() {
598 uvs.write_into(writer);
599 }
600 }
601 }
602
603 fn table_type(&self) -> TableType {
604 TableType::Named("Cmap14")
605 }
606}
607
608impl Cmap14 {
609 const HEADER_LEN: usize = u16::RAW_BYTE_LEN + 2 * u32::RAW_BYTE_LEN;
611 const RECORD_LEN: usize = Uint24::RAW_BYTE_LEN + 2 * u32::RAW_BYTE_LEN;
613
614 fn compute_length(&self) -> u32 {
615 let uvs_len: usize = self
617 .var_selector
618 .iter()
619 .map(|record| {
620 record
621 .default_uvs
622 .as_ref()
623 .map(DefaultUvs::compute_len)
624 .unwrap_or(0)
625 + record
626 .non_default_uvs
627 .as_ref()
628 .map(NonDefaultUvs::compute_len)
629 .unwrap_or(0)
630 })
631 .sum();
632
633 (Self::HEADER_LEN + self.var_selector.len() * Self::RECORD_LEN + uvs_len)
634 .try_into()
635 .expect("cmap14 overflow")
636 }
637}
638
639impl DefaultUvs {
640 fn compute_len(&self) -> usize {
641 u32::RAW_BYTE_LEN + self.ranges.len() * (Uint24::RAW_BYTE_LEN + u8::RAW_BYTE_LEN)
643 }
644}
645
646impl NonDefaultUvs {
647 fn compute_len(&self) -> usize {
648 u32::RAW_BYTE_LEN + self.uvs_mapping.len() * (Uint24::RAW_BYTE_LEN + u16::RAW_BYTE_LEN)
650 }
651}
652
653#[cfg(test)]
654mod tests {
655 use std::ops::RangeInclusive;
656
657 use font_types::{GlyphId, Uint24};
658 use read_fonts::{
659 tables::cmap::{Cmap, CmapSubtable, PlatformId},
660 FontData, FontRead, TableProvider,
661 };
662
663 use crate::{
664 dump_table,
665 from_obj::ToOwnedTable,
666 tables::cmap::{
667 self as write, CmapConflict, UNICODE_BMP_ENCODING, UNICODE_FULL_REPERTOIRE_ENCODING,
668 WINDOWS_BMP_ENCODING, WINDOWS_FULL_REPERTOIRE_ENCODING,
669 },
670 };
671
672 use super::{Cmap12, SequentialMapGroup};
673
674 fn assert_generates_simple_cmap(mappings: Vec<(char, GlyphId)>) {
675 let cmap = write::Cmap::from_mappings(mappings).unwrap();
676
677 let bytes = dump_table(&cmap).unwrap();
678 let font_data = FontData::new(&bytes);
679 let cmap = Cmap::read(font_data).unwrap();
680
681 assert_eq!(
682 2,
683 cmap.encoding_records().len(),
684 "{:?}",
685 cmap.encoding_records()
686 );
687 assert_eq!(
688 vec![
689 (PlatformId::Unicode, UNICODE_BMP_ENCODING),
690 (PlatformId::Windows, WINDOWS_BMP_ENCODING)
691 ],
692 cmap.encoding_records()
693 .iter()
694 .map(|er| (er.platform_id(), er.encoding_id()))
695 .collect::<Vec<_>>()
696 );
697
698 for encoding_record in cmap.encoding_records() {
699 let CmapSubtable::Format4(cmap4) = encoding_record.subtable(font_data).unwrap() else {
700 panic!("Expected a cmap4 in {encoding_record:?}");
701 };
702
703 assert_eq!(
705 (8, 8, 2, 0),
706 (
707 cmap4.seg_count_x2(),
708 cmap4.search_range(),
709 cmap4.entry_selector(),
710 cmap4.range_shift()
711 )
712 );
713 assert_eq!(cmap4.start_code(), &[10u16, 30u16, 153u16, 0xffffu16]);
714 assert_eq!(cmap4.end_code(), &[20u16, 90u16, 480u16, 0xffffu16]);
715 assert_eq!(cmap4.id_delta(), &[-10i16, -19i16, -81i16, 1i16]);
717 assert_eq!(cmap4.id_range_offsets(), &[0u16, 0u16, 0u16, 0u16]);
718 }
719 }
720
721 fn simple_cmap_mappings() -> Vec<(char, GlyphId)> {
722 (10..=20)
723 .chain(30..=90)
724 .chain(153..=480)
725 .enumerate()
726 .map(|(idx, codepoint)| (char::from_u32(codepoint).unwrap(), GlyphId::new(idx as u32)))
727 .collect()
728 }
729
730 #[test]
733 fn generate_simple_cmap4() {
734 let mappings = simple_cmap_mappings();
735 assert_generates_simple_cmap(mappings);
736 }
737
738 #[test]
739 fn generate_cmap4_out_of_order_input() {
740 let mut ordered = simple_cmap_mappings();
741 let mut disordered = Vec::new();
742 while !ordered.is_empty() {
743 if ordered.len() % 2 == 0 {
744 disordered.insert(0, ordered.remove(0));
745 } else {
746 disordered.push(ordered.remove(0));
747 }
748 }
749 assert_ne!(ordered, disordered);
750 assert_generates_simple_cmap(disordered);
751 }
752
753 #[test]
754 fn generate_cmap4_large_values() {
755 let mut mappings = simple_cmap_mappings();
756 let codepoint = char::from_u32(0xa78b).unwrap();
758 let gid = GlyphId::new(153);
759 mappings.push((codepoint, gid));
760
761 let cmap = write::Cmap::from_mappings(mappings).unwrap();
762
763 let bytes = dump_table(&cmap).unwrap();
764 let font_data = FontData::new(&bytes);
765 let cmap = Cmap::read(font_data).unwrap();
766 assert_eq!(cmap.map_codepoint(codepoint), Some(gid));
767 }
768
769 #[test]
770 fn bytes_are_reused() {
771 let mappings = simple_cmap_mappings();
773 let cmap_both = write::Cmap::from_mappings(mappings).unwrap();
774 assert_eq!(2, cmap_both.encoding_records.len(), "{cmap_both:?}");
775
776 let bytes_for_both = dump_table(&cmap_both).unwrap().len();
777
778 for i in 0..cmap_both.encoding_records.len() {
779 let mut cmap = cmap_both.clone();
780 cmap.encoding_records.remove(i);
781 let bytes_for_one = dump_table(&cmap).unwrap().len();
782 assert_eq!(bytes_for_one + 8, bytes_for_both);
783 }
784 }
785
786 fn non_bmp_cmap_mappings() -> Vec<(char, GlyphId)> {
787 vec![
789 ('\u{1f12f}', GlyphId::new(481)),
791 ('\u{1f130}', GlyphId::new(482)),
792 ('\u{1f132}', GlyphId::new(483)),
794 ('\u{1f133}', GlyphId::new(484)),
795 ('\u{1f134}', GlyphId::new(486)),
797 ('\u{1f136}', GlyphId::new(488)),
799 ('\u{1f136}', GlyphId::new(488)),
800 ]
801 }
802
803 fn bmp_and_non_bmp_cmap_mappings() -> Vec<(char, GlyphId)> {
804 let mut mappings = simple_cmap_mappings();
805 mappings.extend(non_bmp_cmap_mappings());
806 mappings
807 }
808
809 fn assert_cmap12_groups(
810 font_data: FontData,
811 cmap: &Cmap,
812 record_index: usize,
813 expected: &[(u32, u32, u32)],
814 ) {
815 let rec = &cmap.encoding_records()[record_index];
816 let CmapSubtable::Format12(subtable) = rec.subtable(font_data).unwrap() else {
817 panic!("Expected a cmap12 in {rec:?}");
818 };
819 let groups = subtable
820 .groups()
821 .iter()
822 .map(|g| (g.start_char_code(), g.end_char_code(), g.start_glyph_id()))
823 .collect::<Vec<_>>();
824 assert_eq!(groups.len(), expected.len());
825 assert_eq!(groups, expected);
826 }
827
828 #[test]
829 fn generate_cmap4_and_12() {
830 let mappings = bmp_and_non_bmp_cmap_mappings();
831
832 let cmap = write::Cmap::from_mappings(mappings).unwrap();
833
834 let bytes = dump_table(&cmap).unwrap();
835 let font_data = FontData::new(&bytes);
836 let cmap = Cmap::read(font_data).unwrap();
837
838 assert_eq!(
839 4,
840 cmap.encoding_records().len(),
841 "{:?}",
842 cmap.encoding_records()
843 );
844 assert_eq!(
845 vec![
846 (PlatformId::Unicode, UNICODE_BMP_ENCODING),
847 (PlatformId::Unicode, UNICODE_FULL_REPERTOIRE_ENCODING),
848 (PlatformId::Windows, WINDOWS_BMP_ENCODING),
849 (PlatformId::Windows, WINDOWS_FULL_REPERTOIRE_ENCODING)
850 ],
851 cmap.encoding_records()
852 .iter()
853 .map(|er| (er.platform_id(), er.encoding_id()))
854 .collect::<Vec<_>>()
855 );
856
857 let encoding_records = cmap.encoding_records();
858 let first_rec = &encoding_records[0];
859 assert!(
860 matches!(
861 first_rec.subtable(font_data).unwrap(),
862 CmapSubtable::Format4(_)
863 ),
864 "Expected a cmap4 in {first_rec:?}"
865 );
866
867 let expected_groups = vec![
869 (10, 20, 0),
870 (30, 90, 11),
871 (153, 480, 72),
872 (0x1f12f, 0x1f130, 481),
873 (0x1f132, 0x1f133, 483),
874 (0x1f134, 0x1f134, 486),
875 (0x1f136, 0x1f136, 488),
876 ];
877 assert_cmap12_groups(font_data, &cmap, 1, &expected_groups);
878 assert_cmap12_groups(font_data, &cmap, 3, &expected_groups);
879 }
880
881 #[test]
882 fn generate_cmap12_only() {
883 let mappings = non_bmp_cmap_mappings();
884
885 let cmap = write::Cmap::from_mappings(mappings).unwrap();
886
887 let bytes = dump_table(&cmap).unwrap();
888 let font_data = FontData::new(&bytes);
889 let cmap = Cmap::read(font_data).unwrap();
890
891 assert_eq!(
892 2,
893 cmap.encoding_records().len(),
894 "{:?}",
895 cmap.encoding_records()
896 );
897 assert_eq!(
898 vec![
899 (PlatformId::Unicode, UNICODE_FULL_REPERTOIRE_ENCODING),
900 (PlatformId::Windows, WINDOWS_FULL_REPERTOIRE_ENCODING)
901 ],
902 cmap.encoding_records()
903 .iter()
904 .map(|er| (er.platform_id(), er.encoding_id()))
905 .collect::<Vec<_>>()
906 );
907
908 let expected_groups = vec![
910 (0x1f12f, 0x1f130, 481),
911 (0x1f132, 0x1f133, 483),
912 (0x1f134, 0x1f134, 486),
913 (0x1f136, 0x1f136, 488),
914 ];
915 assert_cmap12_groups(font_data, &cmap, 0, &expected_groups);
916 assert_cmap12_groups(font_data, &cmap, 1, &expected_groups);
917 }
918
919 #[test]
920 fn multiple_mappings_fails() {
921 let mut mappings = non_bmp_cmap_mappings();
922 let (ch, gid1) = mappings[0];
924 let gid2 = GlyphId::new(gid1.to_u32() + 1);
925 mappings.push((ch, gid2));
926
927 let result = write::Cmap::from_mappings(mappings);
928
929 assert_eq!(result, Err(CmapConflict { ch, gid1, gid2 }))
930 }
931
932 struct MappingBuilder {
933 mappings: Vec<(char, GlyphId)>,
934 next_gid: u16,
935 }
936
937 impl Default for MappingBuilder {
938 fn default() -> Self {
939 Self {
940 mappings: Default::default(),
941 next_gid: 1,
942 }
943 }
944 }
945
946 impl MappingBuilder {
947 fn extend(mut self, range: impl IntoIterator<Item = char>) -> Self {
948 for c in range {
949 let gid = GlyphId::new(self.next_gid as _);
950 self.mappings.push((c, gid));
951 self.next_gid += 1;
952 }
953 self
954 }
955
956 fn compute(&mut self) -> Vec<RangeInclusive<char>> {
958 self.mappings.sort();
959 super::Format4SegmentComputer::new(&self.mappings)
960 .compute()
961 .into_iter()
962 .map(|seg| self.mappings[seg.start_ix].0..=self.mappings[seg.end_ix].0)
963 .collect()
964 }
965
966 fn build(mut self) -> Vec<(char, GlyphId)> {
967 self.mappings.sort();
968 self.mappings
969 }
970 }
971
972 #[test]
973 fn f4_segments_simple() {
974 let mut one_big_discontiguous_mapping = MappingBuilder::default().extend(('a'..='z').rev());
975 assert_eq!(one_big_discontiguous_mapping.compute(), ['a'..='z']);
976 }
977
978 #[test]
979 fn f4_segments_combine_small() {
980 let mut mapping = MappingBuilder::default()
981 .extend(['e', 'd', 'c', 'b', 'a'])
983 .extend('f'..='g')
986 .extend('m'..='n')
987 .extend(('o'..='z').rev());
988
989 assert_eq!(mapping.compute(), ['a'..='g', 'm'..='z']);
990 }
991
992 #[test]
993 fn f4_segments_keep() {
994 let mut mapping = MappingBuilder::default()
995 .extend('a'..='m')
996 .extend(['o', 'n']);
997
998 assert_eq!(mapping.compute(), ['a'..='m', 'n'..='o']);
999 }
1000
1001 fn expect_f4(mapping: &[(char, GlyphId)]) -> super::Cmap4 {
1002 let format4 = super::CmapSubtable::create_format_4(mapping).unwrap();
1003 let super::CmapSubtable::Format4(format4) = format4 else {
1004 panic!("O_o")
1005 };
1006 format4
1007 }
1008
1009 fn get_read_mapping(table: &super::Cmap4) -> Vec<(char, GlyphId)> {
1011 let bytes = dump_table(table).unwrap();
1012 let readcmap = read_fonts::tables::cmap::Cmap4::read(bytes.as_slice().into()).unwrap();
1013
1014 let mut mapping = readcmap
1015 .iter()
1016 .map(|(c, gid)| (char::from_u32(c).unwrap(), gid))
1017 .collect::<Vec<_>>();
1018 assert_eq!(mapping.pop(), Some(('\u{FFFF}', GlyphId::NOTDEF)));
1021 mapping
1022 }
1023
1024 #[test]
1025 fn f4_segment_len_one_uses_delta() {
1026 let mapping = MappingBuilder::default()
1028 .extend(['a', 'z', '1', '9'])
1029 .build();
1030
1031 let format4 = expect_f4(&mapping);
1032 assert_eq!(format4.end_code.len(), 5); assert!(format4.glyph_id_array.is_empty());
1034 assert!(format4.id_delta.iter().all(|d| *d != 0));
1035 }
1036
1037 #[test]
1038 fn f4_efficiency() {
1039 let mapping = MappingBuilder::default()
1041 .extend('A'..='Z')
1042 .extend(('a'..='z').rev())
1043 .build();
1044
1045 let format4 = expect_f4(&mapping);
1046
1047 assert_eq!(
1048 format4.start_code,
1049 ['A' as u32 as u16, 'a' as u32 as u16, 0xffff]
1050 );
1051
1052 assert_eq!(
1053 format4.end_code,
1054 ['Z' as u32 as u16, 'z' as u32 as u16, 0xffff]
1055 );
1056
1057 assert_eq!(format4.id_delta, [-64, 0, 1]);
1058 assert_eq!(format4.id_range_offsets, [0, 4, 0]);
1059
1060 let read_mapping = get_read_mapping(&format4);
1061 assert_eq!(mapping.len(), read_mapping.len());
1062 assert!(mapping == read_mapping);
1063 }
1064
1065 #[test]
1066 fn f4_kinda_real_world() {
1067 let mapping = MappingBuilder::default()
1069 .extend(['\r']) .extend('\x20'..='\x7e') .extend('\u{a0}'..='\u{ac}') .extend('\u{ae}'..='\u{17f}') .extend(['\u{18f}', '\u{192}'])
1074 .build();
1075
1076 let format4 = expect_f4(&mapping);
1077 assert_eq!(format4.end_code.len(), 7);
1079 let read_mapping = get_read_mapping(&format4);
1080
1081 assert_eq!(mapping.len(), read_mapping.len());
1082 assert!(mapping == read_mapping);
1083 }
1084
1085 #[test]
1086 fn f4_sandwich_segment() {
1089 let mapping = MappingBuilder::default()
1090 .extend(['\r'])
1091 .extend(('\x20'..='\x27').rev()) .extend('\x28'..='\x2c') .extend(('\x2d'..='\x34').rev()) .extend('\x35'..='\x3e')
1097 .build();
1098
1099 let format4 = expect_f4(&mapping);
1100 assert_eq!(format4.end_code.len(), 4);
1101 }
1102
1103 #[test]
1105 fn cmap12_length_calculation() {
1106 let more_than_16_bits = u16::MAX as u32 + 5;
1107 let groups = (0..more_than_16_bits)
1108 .map(|i| SequentialMapGroup::new(i, i, i))
1109 .collect();
1110 let cmap12 = Cmap12::new(0, groups);
1111 let bytes = crate::dump_table(&cmap12).unwrap();
1112 let read_it_back = Cmap12::read(bytes.as_slice().into()).unwrap();
1113 assert_eq!(read_it_back.groups.len() as u32, more_than_16_bits);
1114 }
1115
1116 #[test]
1117 fn cmap14_round_trip() {
1118 let font = read_fonts::FontRef::new(font_test_data::CMAP14_FONT1).unwrap();
1119 let (_, cmap14) = font.cmap().unwrap().uvs_subtable().unwrap();
1120 let owned: write::Cmap14 = cmap14.to_owned_table();
1121 let bytes = dump_table(&owned).unwrap();
1122 assert_eq!(
1123 bytes,
1124 &cmap14.offset_data().as_bytes()[..cmap14.length() as usize]
1125 );
1126 }
1127
1128 fn default_uvs() -> write::DefaultUvs {
1129 write::DefaultUvs::new(1, vec![write::UnicodeRange::new(Uint24::new(0x4E00), 2)])
1130 }
1131
1132 fn non_default_uvs() -> write::NonDefaultUvs {
1133 write::NonDefaultUvs::new(1, vec![write::UvsMapping::new(Uint24::new(0x4E08), 25)])
1134 }
1135
1136 fn dump_cmap14(cmap14: &write::Cmap14) -> (Vec<u8>, Vec<u32>) {
1139 let bytes = dump_table(cmap14).unwrap();
1140 let read = read_fonts::tables::cmap::Cmap14::read(bytes.as_slice().into()).unwrap();
1141 assert_eq!(read.length() as usize, bytes.len());
1142 let offsets = read
1143 .var_selector()
1144 .iter()
1145 .flat_map(|rec| [rec.default_uvs_offset(), rec.non_default_uvs_offset()])
1146 .map(|off| off.offset().to_u32())
1147 .collect();
1148 (bytes, offsets)
1149 }
1150
1151 #[test]
1153 fn cmap14_identical_uvs_tables_are_not_shared() {
1154 let cmap14 = write::Cmap14::new(vec![
1155 write::VariationSelector::new(
1156 Uint24::new(0xE0100),
1157 Some(default_uvs()),
1158 Some(non_default_uvs()),
1159 ),
1160 write::VariationSelector::new(
1161 Uint24::new(0xE0101),
1162 Some(default_uvs()),
1163 Some(non_default_uvs()),
1164 ),
1165 ]);
1166 let (bytes, offsets) = dump_cmap14(&cmap14);
1167 assert_eq!(offsets, [32, 40, 49, 57]);
1170 assert_eq!(bytes.len(), 66);
1171 }
1172
1173 #[test]
1174 fn cmap14_null_offsets() {
1175 let cmap14 = write::Cmap14::new(vec![
1176 write::VariationSelector::new(Uint24::new(0xE0100), None, Some(non_default_uvs())),
1177 write::VariationSelector::new(Uint24::new(0xE0101), Some(default_uvs()), None),
1178 ]);
1179 let (bytes, offsets) = dump_cmap14(&cmap14);
1180 assert_eq!(offsets, [0, 32, 41, 0]);
1182 let tail = [
1183 dump_table(&non_default_uvs()).unwrap(),
1184 dump_table(&default_uvs()).unwrap(),
1185 ]
1186 .concat();
1187 assert_eq!(&bytes[32..], tail);
1188 }
1189
1190 fn cmap4_has_a_unique_final_segment<I>(mappings: I)
1191 where
1192 I: IntoIterator<Item = (char, GlyphId)>,
1193 {
1194 let cmap = crate::tables::cmap::Cmap::from_mappings(mappings).unwrap();
1195 for record in &cmap.encoding_records {
1196 let crate::tables::cmap::CmapSubtable::Format4(cmap4) = &*record.subtable else {
1197 continue;
1198 };
1199 if !matches!((cmap4.start_code.as_slice(), cmap4.end_code.as_slice()),
1200 (&[.., before_last_start, 0xFFFF], &[.., before_last_end, 0xFFFF])
1201 if before_last_start != 0xFFFF || before_last_end != 0xFFFF)
1202 {
1203 panic!(
1204 "Expected cmap4 to end with a single (0xFFFF, 0xFFFF) segment, but found {:?}",
1205 (&cmap4.start_code, &cmap4.end_code)
1206 );
1207 }
1208 }
1209 }
1210
1211 #[test]
1212 fn cmap4_final_segment_is_not_duplicated() {
1213 cmap4_has_a_unique_final_segment([('a', GlyphId::new(1)), ('\u{FFFF}', GlyphId::new(0))]);
1214 }
1215
1216 #[test]
1217 fn cmap4_final_segment_is_added_even_if_last_generated_segment_ends_with_0xffff() {
1218 cmap4_has_a_unique_final_segment([
1219 ('a', GlyphId::new(1)),
1220 ('\u{FFFE}', GlyphId::new(0)),
1221 ('\u{FFFF}', GlyphId::new(0)),
1222 ]);
1223 }
1224}