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
19#[derive(Clone, Copy, Debug, PartialEq, Eq)]
21enum Format4Error {
22 NoBmpMappings,
24 GlyphIdOverflow { ch: char, gid: GlyphId },
26 LengthOverflow(usize),
28}
29
30impl std::fmt::Display for Format4Error {
31 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
32 match self {
33 Self::NoBmpMappings => write!(f, "there are no chars in the BMP"),
34 Self::GlyphIdOverflow { ch, gid } => write!(
35 f,
36 "U+{:04X} is mapped to {gid}, which is above the format 4 maximum of 65535",
37 *ch as u32
38 ),
39 Self::LengthOverflow(len) => write!(
40 f,
41 "length of {len} bytes is above the format 4 maximum of 65535"
42 ),
43 }
44 }
45}
46
47impl CmapSubtable {
48 fn create_format_4(mappings: &[(char, GlyphId)]) -> Result<Self, Format4Error> {
58 let mut end_code = Vec::with_capacity(mappings.len() + 1);
59 let mut start_code = Vec::with_capacity(mappings.len() + 1);
60 let mut id_deltas = Vec::with_capacity(mappings.len() + 1);
61 let mut id_range_offsets = Vec::with_capacity(mappings.len() + 1);
62 let mut glyph_ids = Vec::new();
63
64 if let Some((ch, gid)) = mappings
66 .iter()
67 .take_while(|(ch, _)| *ch <= '\u{FFFF}')
68 .find(|(_, gid)| gid.to_u32() > 0xFFFF)
69 {
70 return Err(Format4Error::GlyphIdOverflow { ch: *ch, gid: *gid });
71 }
72 let segments = Format4SegmentComputer::new(mappings).compute();
73 if segments.is_empty() {
74 return Err(Format4Error::NoBmpMappings);
75 }
76 let add_final_segment = segments.last().is_none_or(|seg| {
77 (mappings[seg.start_ix].0, mappings[seg.end_ix].0) != ('\u{FFFF}', '\u{FFFF}')
78 });
79 let n_segments = segments.len() + add_final_segment as usize;
80 let n_glyph_ids = segments
81 .iter()
82 .filter(|seg| seg.id_delta.is_none())
83 .map(Format4Segment::len)
84 .sum();
85 Cmap4::checked_length(n_segments, n_glyph_ids)?;
86 for (i, segment) in segments.into_iter().enumerate() {
87 let start = mappings[segment.start_ix].0;
88 let end = mappings[segment.end_ix].0;
89 start_code.push(start as u32 as u16);
90 end_code.push(end as u32 as u16);
91 if let Some(delta) = segment.id_delta {
92 id_deltas.push(delta as i16);
94 id_range_offsets.push(0u16);
95 } else {
96 let current_n_ids = glyph_ids.len();
103 let n_following_segments = n_segments - i;
104 let id_range_offset = (n_following_segments + current_n_ids) * u16::RAW_BYTE_LEN;
107 id_deltas.push(0);
108 id_range_offsets.push(
111 id_range_offset
112 .try_into()
113 .expect("length checked before now"),
114 );
115 glyph_ids.extend(
116 mappings[segment.start_ix..=segment.end_ix]
117 .iter()
118 .map(|(_, gid)| u16::try_from(gid.to_u32()).expect("checked before now")),
119 )
120 }
121 }
122
123 if add_final_segment {
125 end_code.push(0xFFFF);
126 start_code.push(0xFFFF);
127 id_deltas.push(1);
128 id_range_offsets.push(0);
129 }
130
131 Ok(Self::format_4(
132 0,
133 end_code,
134 start_code,
135 id_deltas,
136 id_range_offsets,
137 glyph_ids,
138 ))
139 }
140
141 fn create_format_12(mappings: &[(char, GlyphId)]) -> Self {
146 let (mut char_codes, gids): (Vec<u32>, Vec<u32>) = mappings
147 .iter()
148 .map(|(cp, gid)| (*cp as u32, gid.to_u32()))
149 .unzip();
150 let cmap: HashMap<_, _> = char_codes.iter().cloned().zip(gids).collect();
151 char_codes.dedup();
152
153 let mut start_char_code = *char_codes.first().unwrap();
155 let mut start_glyph_id = cmap[&start_char_code];
156 let mut last_glyph_id = start_glyph_id.wrapping_sub(1);
157 let mut last_char_code = start_char_code.wrapping_sub(1);
158 let mut groups = Vec::new();
159 for char_code in char_codes {
160 let glyph_id = cmap[&char_code];
161 if glyph_id != last_glyph_id.wrapping_add(1)
162 || char_code != last_char_code.wrapping_add(1)
163 {
164 groups.push((start_char_code, last_char_code, start_glyph_id));
165 start_char_code = char_code;
166 start_glyph_id = glyph_id;
167 }
168 last_glyph_id = glyph_id;
169 last_char_code = char_code;
170 }
171 groups.push((start_char_code, last_char_code, start_glyph_id));
172
173 let seq_map_groups = groups
174 .into_iter()
175 .map(|(start_char, end_char, gid)| SequentialMapGroup::new(start_char, end_char, gid))
176 .collect::<Vec<_>>();
177 CmapSubtable::format_12(
178 0, seq_map_groups,
180 )
181 }
182}
183
184#[derive(Debug, Clone, PartialEq, Eq)]
189pub struct CmapConflict {
190 ch: char,
191 gid1: GlyphId,
192 gid2: GlyphId,
193}
194
195impl std::fmt::Display for CmapConflict {
196 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
197 let ch32 = self.ch as u32;
198 write!(
199 f,
200 "Cannot map {:?} (U+{ch32:04X}) to two different glyph ids: {} and {}",
201 self.ch, self.gid1, self.gid2
202 )
203 }
204}
205
206impl std::error::Error for CmapConflict {}
207
208impl Cmap {
209 pub fn from_mappings(
231 mappings: impl IntoIterator<Item = (char, GlyphId)>,
232 ) -> Result<Cmap, CmapConflict> {
233 let mut mappings: Vec<_> = mappings.into_iter().collect();
234 mappings.sort();
235 mappings.dedup();
236 if let Some((ch, gid1, gid2)) =
237 mappings
238 .iter()
239 .zip(mappings.iter().skip(1))
240 .find_map(|((c1, g1), (c2, g2))| {
241 (c1 == c2 && g1 != g2).then(|| (*c1, *g1.min(g2), *g1.max(g2)))
242 })
243 {
244 return Err(CmapConflict { ch, gid1, gid2 });
245 }
246
247 let mut uni_records = Vec::new(); let mut win_records = Vec::new(); let bmp_subtable = CmapSubtable::create_format_4(&mappings)
253 .inspect_err(|e| log::warn!("omitting cmap format 4 subtable: {e}"))
254 .ok();
255 let has_format_4 = bmp_subtable.is_some();
256 if let Some(bmp_subtable) = bmp_subtable {
257 uni_records.push(EncodingRecord::new(
262 PlatformId::Unicode,
263 UNICODE_BMP_ENCODING,
264 bmp_subtable.clone(),
265 ));
266 win_records.push(EncodingRecord::new(
267 PlatformId::Windows,
268 WINDOWS_BMP_ENCODING,
269 bmp_subtable,
270 ));
271 }
272
273 if !mappings.is_empty()
277 && (!has_format_4 || mappings.iter().any(|(cp, _)| *cp > '\u{FFFF}'))
278 {
279 let full_repertoire_subtable = CmapSubtable::create_format_12(&mappings);
280 uni_records.push(EncodingRecord::new(
282 PlatformId::Unicode,
283 UNICODE_FULL_REPERTOIRE_ENCODING,
284 full_repertoire_subtable.clone(),
285 ));
286 win_records.push(EncodingRecord::new(
287 PlatformId::Windows,
288 WINDOWS_FULL_REPERTOIRE_ENCODING,
289 full_repertoire_subtable,
290 ));
291 }
292
293 Ok(Cmap::new(
299 uni_records.into_iter().chain(win_records).collect(),
300 ))
301 }
302}
303
304struct Format4SegmentComputer<'a> {
306 mappings: &'a [(char, GlyphId)],
307 seg_start: usize,
309 gids_in_order: bool,
311}
312
313#[derive(Clone, Copy, Debug)]
314struct Format4Segment {
315 start_ix: usize,
317 end_ix: usize,
318 start_char: char,
319 end_char: char,
320 id_delta: Option<i32>,
321}
322
323impl Format4Segment {
324 fn len(&self) -> usize {
325 self.end_ix - self.start_ix + 1
326 }
327
328 fn cost(&self) -> usize {
330 const BASE_COST: usize = 4 * u16::RAW_BYTE_LEN;
332
333 if self.id_delta.is_some() {
334 BASE_COST
335 } else {
336 BASE_COST + self.len() * u16::RAW_BYTE_LEN
339 }
340 }
341
342 fn can_combine(&self, next: &Self) -> bool {
344 self.end_char as u32 + 1 == next.start_char as u32
345 }
346
347 fn should_combine(&self, prev: &Self, next: Option<&Self>) -> bool {
353 if !prev.can_combine(self) {
354 return false;
355 }
356
357 let combined_cost = prev.combine(self).cost();
360 let separate_cost = prev.cost() + self.cost();
361
362 if combined_cost < separate_cost {
363 return true;
364 }
365
366 if let Some(next) = next.filter(|next| self.can_combine(next)) {
406 let combined_cost = prev.combine(self).combine(next).cost();
407 let separate_cost = separate_cost + next.cost();
408 return combined_cost < separate_cost;
409 }
410
411 false
412 }
413
414 fn combine(&self, next: &Format4Segment) -> Format4Segment {
418 assert_eq!(next.start_ix, self.end_ix + 1,);
419 Format4Segment {
420 start_ix: self.start_ix,
421 start_char: self.start_char,
422 end_char: next.end_char,
423 end_ix: next.end_ix,
424 id_delta: None,
425 }
426 }
427}
428
429impl<'a> Format4SegmentComputer<'a> {
430 fn new(mappings: &'a [(char, GlyphId)]) -> Self {
431 let mappings = mappings
433 .iter()
434 .position(|(c, _)| u16::try_from(*c as u32).is_err())
435 .map(|bad_idx| &mappings[..bad_idx])
436 .unwrap_or(mappings);
437 Self {
438 mappings,
439 seg_start: 0,
440 gids_in_order: false,
441 }
442 }
443
444 fn make_segment(&mut self, seg_len: usize) -> Format4Segment {
449 let use_delta = self.gids_in_order || seg_len == 0;
451 let start_ix = self.seg_start;
452 let end_ix = self.seg_start + seg_len;
453 let start_char = self.mappings[start_ix].0;
454 let end_char = self.mappings[end_ix].0;
455 let result = Format4Segment {
456 start_ix,
457 end_ix,
458 start_char,
459 end_char,
460 id_delta: self
461 .mappings
462 .get(self.seg_start)
463 .map(|(cp, gid)| gid.to_u32() as i32 - *cp as u32 as i32)
464 .filter(|_| use_delta),
465 };
466 self.seg_start += seg_len + 1;
467 self.gids_in_order = false;
468 result
469 }
470
471 fn next_possible_segment(&mut self) -> Option<Format4Segment> {
477 if self.seg_start == self.mappings.len() {
478 return None;
479 }
480
481 let Some(((mut prev_cp, mut prev_gid), rest)) =
482 self.mappings[self.seg_start..].split_first()
483 else {
484 return Some(self.make_segment(0));
486 };
487
488 for (i, (cp, gid)) in rest.iter().enumerate() {
489 if *cp as u32 != prev_cp as u32 + 1 {
491 return Some(self.make_segment(i));
492 }
493 let next_gid_is_in_order = prev_gid.to_u32() + 1 == gid.to_u32();
494 if !next_gid_is_in_order {
495 if self.gids_in_order {
497 return Some(self.make_segment(i));
498 }
499 } else if !self.gids_in_order {
505 if i == 0 {
506 self.gids_in_order = true;
507 } else {
508 return Some(self.make_segment(i - 1));
509 }
510 }
511 prev_cp = *cp;
512 prev_gid = *gid;
513 }
514
515 let last_idx = self.mappings.len() - 1;
517 Some(self.make_segment(last_idx - self.seg_start))
518 }
519
520 fn compute(mut self) -> Vec<Format4Segment> {
545 let Some(first) = self.next_possible_segment() else {
546 return Default::default();
547 };
548
549 let mut result = vec![first];
550
551 let mut next = self.next_possible_segment();
554
555 while let Some(current) = next.take() {
556 next = self.next_possible_segment();
557 let prev = result.last_mut().unwrap();
558 if current.should_combine(prev, next.as_ref()) {
559 *prev = prev.combine(¤t);
560 continue;
561 }
562
563 result.push(current);
564 }
565 result
566 }
567}
568
569impl Cmap4 {
570 fn checked_length(n_segments: usize, n_glyph_ids: usize) -> Result<u16, Format4Error> {
574 const FIXED_SIZE: usize = 8 * u16::RAW_BYTE_LEN;
577 const PER_SEGMENT_LEN: usize = 4 * u16::RAW_BYTE_LEN;
578
579 let segment_len = n_segments * PER_SEGMENT_LEN;
580 let gid_len = n_glyph_ids * u16::RAW_BYTE_LEN;
581
582 let length = FIXED_SIZE + segment_len + gid_len;
583 length
584 .try_into()
585 .map_err(|_| Format4Error::LengthOverflow(length))
586 }
587
588 fn compute_length(&self) -> u16 {
589 Self::checked_length(self.end_code.len(), self.glyph_id_array.len())
591 .expect("cmap4 overflow")
592 }
593
594 fn check_length(&self, ctx: &mut ValidationCtx) {
595 if let Err(e) = Self::checked_length(self.end_code.len(), self.glyph_id_array.len()) {
596 ctx.report(e);
597 }
598 }
599
600 fn compute_search_range(&self) -> u16 {
601 SearchRange::compute(self.end_code.len(), u16::RAW_BYTE_LEN).search_range
602 }
603
604 fn compute_entry_selector(&self) -> u16 {
605 SearchRange::compute(self.end_code.len(), u16::RAW_BYTE_LEN).entry_selector
606 }
607
608 fn compute_range_shift(&self) -> u16 {
609 SearchRange::compute(self.end_code.len(), u16::RAW_BYTE_LEN).range_shift
610 }
611}
612
613impl Cmap12 {
614 fn compute_length(&self) -> u32 {
615 const FIXED_SIZE: usize = 2 * u16::RAW_BYTE_LEN + 3 * u32::RAW_BYTE_LEN;
617 const PER_SEGMENT_LEN: usize = 3 * u32::RAW_BYTE_LEN;
618
619 (FIXED_SIZE + PER_SEGMENT_LEN * self.groups.len())
620 .try_into()
621 .unwrap()
622 }
623}
624
625impl FontWrite for Cmap14 {
637 fn write_into(&self, writer: &mut TableWriter) {
638 14u16.write_into(writer);
639 self.compute_length().write_into(writer);
640 u32::try_from(self.var_selector.len())
641 .unwrap()
642 .write_into(writer);
643
644 let mut next_offset = Self::HEADER_LEN + self.var_selector.len() * Self::RECORD_LEN;
647 let mut write_offset = |writer: &mut TableWriter, len: Option<usize>| match len {
648 Some(len) => {
649 (next_offset as u32).write_into(writer);
650 next_offset += len;
651 }
652 None => 0u32.write_into(writer),
653 };
654 for record in &self.var_selector {
655 record.var_selector.write_into(writer);
656 write_offset(
657 writer,
658 record.default_uvs.as_ref().map(DefaultUvs::compute_len),
659 );
660 write_offset(
661 writer,
662 record
663 .non_default_uvs
664 .as_ref()
665 .map(NonDefaultUvs::compute_len),
666 );
667 }
668 for record in &self.var_selector {
669 if let Some(uvs) = record.default_uvs.as_ref() {
670 uvs.write_into(writer);
671 }
672 if let Some(uvs) = record.non_default_uvs.as_ref() {
673 uvs.write_into(writer);
674 }
675 }
676 }
677
678 fn table_type(&self) -> TableType {
679 TableType::Named("Cmap14")
680 }
681}
682
683impl Cmap14 {
684 const HEADER_LEN: usize = u16::RAW_BYTE_LEN + 2 * u32::RAW_BYTE_LEN;
686 const RECORD_LEN: usize = Uint24::RAW_BYTE_LEN + 2 * u32::RAW_BYTE_LEN;
688
689 fn compute_length(&self) -> u32 {
690 let uvs_len: usize = self
692 .var_selector
693 .iter()
694 .map(|record| {
695 record
696 .default_uvs
697 .as_ref()
698 .map(DefaultUvs::compute_len)
699 .unwrap_or(0)
700 + record
701 .non_default_uvs
702 .as_ref()
703 .map(NonDefaultUvs::compute_len)
704 .unwrap_or(0)
705 })
706 .sum();
707
708 (Self::HEADER_LEN + self.var_selector.len() * Self::RECORD_LEN + uvs_len)
709 .try_into()
710 .expect("cmap14 overflow")
711 }
712}
713
714impl DefaultUvs {
715 fn compute_len(&self) -> usize {
716 u32::RAW_BYTE_LEN + self.ranges.len() * (Uint24::RAW_BYTE_LEN + u8::RAW_BYTE_LEN)
718 }
719}
720
721impl NonDefaultUvs {
722 fn compute_len(&self) -> usize {
723 u32::RAW_BYTE_LEN + self.uvs_mapping.len() * (Uint24::RAW_BYTE_LEN + u16::RAW_BYTE_LEN)
725 }
726}
727
728#[cfg(test)]
729mod tests {
730 use std::ops::RangeInclusive;
731
732 use font_types::{GlyphId, Uint24};
733 use read_fonts::{
734 tables::cmap::{Cmap, CmapSubtable, PlatformId},
735 FontData, FontRead, TableProvider,
736 };
737
738 use crate::{
739 dump_table,
740 from_obj::ToOwnedTable,
741 tables::cmap::{
742 self as write, CmapConflict, UNICODE_BMP_ENCODING, UNICODE_FULL_REPERTOIRE_ENCODING,
743 WINDOWS_BMP_ENCODING, WINDOWS_FULL_REPERTOIRE_ENCODING,
744 },
745 };
746
747 use super::{Cmap12, SequentialMapGroup};
748
749 fn assert_generates_simple_cmap(mappings: Vec<(char, GlyphId)>) {
750 let cmap = write::Cmap::from_mappings(mappings).unwrap();
751
752 let bytes = dump_table(&cmap).unwrap();
753 let font_data = FontData::new(&bytes);
754 let cmap = Cmap::read(font_data).unwrap();
755
756 assert_eq!(
757 2,
758 cmap.encoding_records().len(),
759 "{:?}",
760 cmap.encoding_records()
761 );
762 assert_eq!(
763 vec![
764 (PlatformId::Unicode, UNICODE_BMP_ENCODING),
765 (PlatformId::Windows, WINDOWS_BMP_ENCODING)
766 ],
767 cmap.encoding_records()
768 .iter()
769 .map(|er| (er.platform_id(), er.encoding_id()))
770 .collect::<Vec<_>>()
771 );
772
773 for encoding_record in cmap.encoding_records() {
774 let CmapSubtable::Format4(cmap4) = encoding_record.subtable(font_data).unwrap() else {
775 panic!("Expected a cmap4 in {encoding_record:?}");
776 };
777
778 assert_eq!(
780 (8, 8, 2, 0),
781 (
782 cmap4.seg_count_x2(),
783 cmap4.search_range(),
784 cmap4.entry_selector(),
785 cmap4.range_shift()
786 )
787 );
788 assert_eq!(cmap4.start_code(), &[10u16, 30u16, 153u16, 0xffffu16]);
789 assert_eq!(cmap4.end_code(), &[20u16, 90u16, 480u16, 0xffffu16]);
790 assert_eq!(cmap4.id_delta(), &[-10i16, -19i16, -81i16, 1i16]);
792 assert_eq!(cmap4.id_range_offsets(), &[0u16, 0u16, 0u16, 0u16]);
793 }
794 }
795
796 fn simple_cmap_mappings() -> Vec<(char, GlyphId)> {
797 (10..=20)
798 .chain(30..=90)
799 .chain(153..=480)
800 .enumerate()
801 .map(|(idx, codepoint)| (char::from_u32(codepoint).unwrap(), GlyphId::new(idx as u32)))
802 .collect()
803 }
804
805 #[test]
808 fn generate_simple_cmap4() {
809 let mappings = simple_cmap_mappings();
810 assert_generates_simple_cmap(mappings);
811 }
812
813 #[test]
814 fn generate_cmap4_out_of_order_input() {
815 let mut ordered = simple_cmap_mappings();
816 let mut disordered = Vec::new();
817 while !ordered.is_empty() {
818 if ordered.len() % 2 == 0 {
819 disordered.insert(0, ordered.remove(0));
820 } else {
821 disordered.push(ordered.remove(0));
822 }
823 }
824 assert_ne!(ordered, disordered);
825 assert_generates_simple_cmap(disordered);
826 }
827
828 #[test]
829 fn generate_cmap4_large_values() {
830 let mut mappings = simple_cmap_mappings();
831 let codepoint = char::from_u32(0xa78b).unwrap();
833 let gid = GlyphId::new(153);
834 mappings.push((codepoint, gid));
835
836 let cmap = write::Cmap::from_mappings(mappings).unwrap();
837
838 let bytes = dump_table(&cmap).unwrap();
839 let font_data = FontData::new(&bytes);
840 let cmap = Cmap::read(font_data).unwrap();
841 assert_eq!(cmap.map_codepoint(codepoint), Some(gid));
842 }
843
844 #[test]
845 fn generate_cmap4_large_positive_delta() {
846 let mut mappings = simple_cmap_mappings();
847 let codepoint = '\u{2026}';
850 let gid = GlyphId::new(58562);
851 mappings.push((codepoint, gid));
852
853 let cmap = write::Cmap::from_mappings(mappings).unwrap();
854
855 let bytes = dump_table(&cmap).unwrap();
856 let font_data = FontData::new(&bytes);
857 let cmap = Cmap::read(font_data).unwrap();
858 assert_eq!(cmap.map_codepoint(codepoint), Some(gid));
859 }
860
861 #[test]
862 fn bytes_are_reused() {
863 let mappings = simple_cmap_mappings();
865 let cmap_both = write::Cmap::from_mappings(mappings).unwrap();
866 assert_eq!(2, cmap_both.encoding_records.len(), "{cmap_both:?}");
867
868 let bytes_for_both = dump_table(&cmap_both).unwrap().len();
869
870 for i in 0..cmap_both.encoding_records.len() {
871 let mut cmap = cmap_both.clone();
872 cmap.encoding_records.remove(i);
873 let bytes_for_one = dump_table(&cmap).unwrap().len();
874 assert_eq!(bytes_for_one + 8, bytes_for_both);
875 }
876 }
877
878 fn non_bmp_cmap_mappings() -> Vec<(char, GlyphId)> {
879 vec![
881 ('\u{1f12f}', GlyphId::new(481)),
883 ('\u{1f130}', GlyphId::new(482)),
884 ('\u{1f132}', GlyphId::new(483)),
886 ('\u{1f133}', GlyphId::new(484)),
887 ('\u{1f134}', GlyphId::new(486)),
889 ('\u{1f136}', GlyphId::new(488)),
891 ('\u{1f136}', GlyphId::new(488)),
892 ]
893 }
894
895 fn bmp_and_non_bmp_cmap_mappings() -> Vec<(char, GlyphId)> {
896 let mut mappings = simple_cmap_mappings();
897 mappings.extend(non_bmp_cmap_mappings());
898 mappings
899 }
900
901 fn assert_cmap12_groups(
902 font_data: FontData,
903 cmap: &Cmap,
904 record_index: usize,
905 expected: &[(u32, u32, u32)],
906 ) {
907 let rec = &cmap.encoding_records()[record_index];
908 let CmapSubtable::Format12(subtable) = rec.subtable(font_data).unwrap() else {
909 panic!("Expected a cmap12 in {rec:?}");
910 };
911 let groups = subtable
912 .groups()
913 .iter()
914 .map(|g| (g.start_char_code(), g.end_char_code(), g.start_glyph_id()))
915 .collect::<Vec<_>>();
916 assert_eq!(groups.len(), expected.len());
917 assert_eq!(groups, expected);
918 }
919
920 #[test]
921 fn generate_cmap4_and_12() {
922 let mappings = bmp_and_non_bmp_cmap_mappings();
923
924 let cmap = write::Cmap::from_mappings(mappings).unwrap();
925
926 let bytes = dump_table(&cmap).unwrap();
927 let font_data = FontData::new(&bytes);
928 let cmap = Cmap::read(font_data).unwrap();
929
930 assert_eq!(
931 4,
932 cmap.encoding_records().len(),
933 "{:?}",
934 cmap.encoding_records()
935 );
936 assert_eq!(
937 vec![
938 (PlatformId::Unicode, UNICODE_BMP_ENCODING),
939 (PlatformId::Unicode, UNICODE_FULL_REPERTOIRE_ENCODING),
940 (PlatformId::Windows, WINDOWS_BMP_ENCODING),
941 (PlatformId::Windows, WINDOWS_FULL_REPERTOIRE_ENCODING)
942 ],
943 cmap.encoding_records()
944 .iter()
945 .map(|er| (er.platform_id(), er.encoding_id()))
946 .collect::<Vec<_>>()
947 );
948
949 let encoding_records = cmap.encoding_records();
950 let first_rec = &encoding_records[0];
951 assert!(
952 matches!(
953 first_rec.subtable(font_data).unwrap(),
954 CmapSubtable::Format4(_)
955 ),
956 "Expected a cmap4 in {first_rec:?}"
957 );
958
959 let expected_groups = vec![
961 (10, 20, 0),
962 (30, 90, 11),
963 (153, 480, 72),
964 (0x1f12f, 0x1f130, 481),
965 (0x1f132, 0x1f133, 483),
966 (0x1f134, 0x1f134, 486),
967 (0x1f136, 0x1f136, 488),
968 ];
969 assert_cmap12_groups(font_data, &cmap, 1, &expected_groups);
970 assert_cmap12_groups(font_data, &cmap, 3, &expected_groups);
971 }
972
973 #[test]
974 fn generate_cmap12_only() {
975 let mappings = non_bmp_cmap_mappings();
976
977 let cmap = write::Cmap::from_mappings(mappings).unwrap();
978
979 let bytes = dump_table(&cmap).unwrap();
980 let font_data = FontData::new(&bytes);
981 let cmap = Cmap::read(font_data).unwrap();
982
983 assert_eq!(
984 2,
985 cmap.encoding_records().len(),
986 "{:?}",
987 cmap.encoding_records()
988 );
989 assert_eq!(
990 vec![
991 (PlatformId::Unicode, UNICODE_FULL_REPERTOIRE_ENCODING),
992 (PlatformId::Windows, WINDOWS_FULL_REPERTOIRE_ENCODING)
993 ],
994 cmap.encoding_records()
995 .iter()
996 .map(|er| (er.platform_id(), er.encoding_id()))
997 .collect::<Vec<_>>()
998 );
999
1000 let expected_groups = vec![
1002 (0x1f12f, 0x1f130, 481),
1003 (0x1f132, 0x1f133, 483),
1004 (0x1f134, 0x1f134, 486),
1005 (0x1f136, 0x1f136, 488),
1006 ];
1007 assert_cmap12_groups(font_data, &cmap, 0, &expected_groups);
1008 assert_cmap12_groups(font_data, &cmap, 1, &expected_groups);
1009 }
1010
1011 #[test]
1012 fn multiple_mappings_fails() {
1013 let mut mappings = non_bmp_cmap_mappings();
1014 let (ch, gid1) = mappings[0];
1016 let gid2 = GlyphId::new(gid1.to_u32() + 1);
1017 mappings.push((ch, gid2));
1018
1019 let result = write::Cmap::from_mappings(mappings);
1020
1021 assert_eq!(result, Err(CmapConflict { ch, gid1, gid2 }))
1022 }
1023
1024 struct MappingBuilder {
1025 mappings: Vec<(char, GlyphId)>,
1026 next_gid: u16,
1027 }
1028
1029 impl Default for MappingBuilder {
1030 fn default() -> Self {
1031 Self {
1032 mappings: Default::default(),
1033 next_gid: 1,
1034 }
1035 }
1036 }
1037
1038 impl MappingBuilder {
1039 fn extend(mut self, range: impl IntoIterator<Item = char>) -> Self {
1040 for c in range {
1041 let gid = GlyphId::new(self.next_gid as _);
1042 self.mappings.push((c, gid));
1043 self.next_gid += 1;
1044 }
1045 self
1046 }
1047
1048 fn compute(&mut self) -> Vec<RangeInclusive<char>> {
1050 self.mappings.sort();
1051 super::Format4SegmentComputer::new(&self.mappings)
1052 .compute()
1053 .into_iter()
1054 .map(|seg| self.mappings[seg.start_ix].0..=self.mappings[seg.end_ix].0)
1055 .collect()
1056 }
1057
1058 fn build(mut self) -> Vec<(char, GlyphId)> {
1059 self.mappings.sort();
1060 self.mappings
1061 }
1062 }
1063
1064 #[test]
1065 fn f4_segments_simple() {
1066 let mut one_big_discontiguous_mapping = MappingBuilder::default().extend(('a'..='z').rev());
1067 assert_eq!(one_big_discontiguous_mapping.compute(), ['a'..='z']);
1068 }
1069
1070 #[test]
1071 fn f4_segments_combine_small() {
1072 let mut mapping = MappingBuilder::default()
1073 .extend(['e', 'd', 'c', 'b', 'a'])
1075 .extend('f'..='g')
1078 .extend('m'..='n')
1079 .extend(('o'..='z').rev());
1080
1081 assert_eq!(mapping.compute(), ['a'..='g', 'm'..='z']);
1082 }
1083
1084 #[test]
1085 fn f4_segments_keep() {
1086 let mut mapping = MappingBuilder::default()
1087 .extend('a'..='m')
1088 .extend(['o', 'n']);
1089
1090 assert_eq!(mapping.compute(), ['a'..='m', 'n'..='o']);
1091 }
1092
1093 fn expect_f4(mapping: &[(char, GlyphId)]) -> super::Cmap4 {
1094 let format4 = super::CmapSubtable::create_format_4(mapping).unwrap();
1095 let super::CmapSubtable::Format4(format4) = format4 else {
1096 panic!("O_o")
1097 };
1098 format4
1099 }
1100
1101 fn get_read_mapping(table: &super::Cmap4) -> Vec<(char, GlyphId)> {
1103 let bytes = dump_table(table).unwrap();
1104 let readcmap = read_fonts::tables::cmap::Cmap4::read(bytes.as_slice().into()).unwrap();
1105
1106 let mut mapping = readcmap
1107 .iter()
1108 .map(|(c, gid)| (char::from_u32(c).unwrap(), gid))
1109 .collect::<Vec<_>>();
1110 assert_eq!(mapping.pop(), Some(('\u{FFFF}', GlyphId::NOTDEF)));
1113 mapping
1114 }
1115
1116 #[test]
1117 fn f4_segment_len_one_uses_delta() {
1118 let mapping = MappingBuilder::default()
1120 .extend(['a', 'z', '1', '9'])
1121 .build();
1122
1123 let format4 = expect_f4(&mapping);
1124 assert_eq!(format4.end_code.len(), 5); assert!(format4.glyph_id_array.is_empty());
1126 assert!(format4.id_delta.iter().all(|d| *d != 0));
1127 }
1128
1129 #[test]
1130 fn f4_efficiency() {
1131 let mapping = MappingBuilder::default()
1133 .extend('A'..='Z')
1134 .extend(('a'..='z').rev())
1135 .build();
1136
1137 let format4 = expect_f4(&mapping);
1138
1139 assert_eq!(
1140 format4.start_code,
1141 ['A' as u32 as u16, 'a' as u32 as u16, 0xffff]
1142 );
1143
1144 assert_eq!(
1145 format4.end_code,
1146 ['Z' as u32 as u16, 'z' as u32 as u16, 0xffff]
1147 );
1148
1149 assert_eq!(format4.id_delta, [-64, 0, 1]);
1150 assert_eq!(format4.id_range_offsets, [0, 4, 0]);
1151
1152 let read_mapping = get_read_mapping(&format4);
1153 assert_eq!(mapping.len(), read_mapping.len());
1154 assert!(mapping == read_mapping);
1155 }
1156
1157 #[test]
1158 fn f4_kinda_real_world() {
1159 let mapping = MappingBuilder::default()
1161 .extend(['\r']) .extend('\x20'..='\x7e') .extend('\u{a0}'..='\u{ac}') .extend('\u{ae}'..='\u{17f}') .extend(['\u{18f}', '\u{192}'])
1166 .build();
1167
1168 let format4 = expect_f4(&mapping);
1169 assert_eq!(format4.end_code.len(), 7);
1171 let read_mapping = get_read_mapping(&format4);
1172
1173 assert_eq!(mapping.len(), read_mapping.len());
1174 assert!(mapping == read_mapping);
1175 }
1176
1177 #[test]
1178 fn f4_sandwich_segment() {
1181 let mapping = MappingBuilder::default()
1182 .extend(['\r'])
1183 .extend(('\x20'..='\x27').rev()) .extend('\x28'..='\x2c') .extend(('\x2d'..='\x34').rev()) .extend('\x35'..='\x3e')
1189 .build();
1190
1191 let format4 = expect_f4(&mapping);
1192 assert_eq!(format4.end_code.len(), 4);
1193 }
1194
1195 #[test]
1197 fn cmap12_length_calculation() {
1198 let more_than_16_bits = u16::MAX as u32 + 5;
1199 let groups = (0..more_than_16_bits)
1200 .map(|i| SequentialMapGroup::new(i, i, i))
1201 .collect();
1202 let cmap12 = Cmap12::new(0, groups);
1203 let bytes = crate::dump_table(&cmap12).unwrap();
1204 let read_it_back = Cmap12::read(bytes.as_slice().into()).unwrap();
1205 assert_eq!(read_it_back.groups.len() as u32, more_than_16_bits);
1206 }
1207
1208 #[test]
1209 fn cmap14_round_trip() {
1210 let font = read_fonts::FontRef::new(font_test_data::CMAP14_FONT1).unwrap();
1211 let (_, cmap14) = font.cmap().unwrap().uvs_subtable().unwrap();
1212 let owned: write::Cmap14 = cmap14.to_owned_table();
1213 let bytes = dump_table(&owned).unwrap();
1214 assert_eq!(
1215 bytes,
1216 &cmap14.offset_data().as_bytes()[..cmap14.length() as usize]
1217 );
1218 }
1219
1220 fn default_uvs() -> write::DefaultUvs {
1221 write::DefaultUvs::new(1, vec![write::UnicodeRange::new(Uint24::new(0x4E00), 2)])
1222 }
1223
1224 fn non_default_uvs() -> write::NonDefaultUvs {
1225 write::NonDefaultUvs::new(1, vec![write::UvsMapping::new(Uint24::new(0x4E08), 25)])
1226 }
1227
1228 fn dump_cmap14(cmap14: &write::Cmap14) -> (Vec<u8>, Vec<u32>) {
1231 let bytes = dump_table(cmap14).unwrap();
1232 let read = read_fonts::tables::cmap::Cmap14::read(bytes.as_slice().into()).unwrap();
1233 assert_eq!(read.length() as usize, bytes.len());
1234 let offsets = read
1235 .var_selector()
1236 .iter()
1237 .flat_map(|rec| [rec.default_uvs_offset(), rec.non_default_uvs_offset()])
1238 .map(|off| off.offset().to_u32())
1239 .collect();
1240 (bytes, offsets)
1241 }
1242
1243 #[test]
1245 fn cmap14_identical_uvs_tables_are_not_shared() {
1246 let cmap14 = write::Cmap14::new(vec![
1247 write::VariationSelector::new(
1248 Uint24::new(0xE0100),
1249 Some(default_uvs()),
1250 Some(non_default_uvs()),
1251 ),
1252 write::VariationSelector::new(
1253 Uint24::new(0xE0101),
1254 Some(default_uvs()),
1255 Some(non_default_uvs()),
1256 ),
1257 ]);
1258 let (bytes, offsets) = dump_cmap14(&cmap14);
1259 assert_eq!(offsets, [32, 40, 49, 57]);
1262 assert_eq!(bytes.len(), 66);
1263 }
1264
1265 #[test]
1266 fn cmap14_null_offsets() {
1267 let cmap14 = write::Cmap14::new(vec![
1268 write::VariationSelector::new(Uint24::new(0xE0100), None, Some(non_default_uvs())),
1269 write::VariationSelector::new(Uint24::new(0xE0101), Some(default_uvs()), None),
1270 ]);
1271 let (bytes, offsets) = dump_cmap14(&cmap14);
1272 assert_eq!(offsets, [0, 32, 41, 0]);
1274 let tail = [
1275 dump_table(&non_default_uvs()).unwrap(),
1276 dump_table(&default_uvs()).unwrap(),
1277 ]
1278 .concat();
1279 assert_eq!(&bytes[32..], tail);
1280 }
1281
1282 fn cmap4_has_a_unique_final_segment<I>(mappings: I)
1283 where
1284 I: IntoIterator<Item = (char, GlyphId)>,
1285 {
1286 let cmap = crate::tables::cmap::Cmap::from_mappings(mappings).unwrap();
1287 for record in &cmap.encoding_records {
1288 let crate::tables::cmap::CmapSubtable::Format4(cmap4) = &*record.subtable else {
1289 continue;
1290 };
1291 if !matches!((cmap4.start_code.as_slice(), cmap4.end_code.as_slice()),
1292 (&[.., before_last_start, 0xFFFF], &[.., before_last_end, 0xFFFF])
1293 if before_last_start != 0xFFFF || before_last_end != 0xFFFF)
1294 {
1295 panic!(
1296 "Expected cmap4 to end with a single (0xFFFF, 0xFFFF) segment, but found {:?}",
1297 (&cmap4.start_code, &cmap4.end_code)
1298 );
1299 }
1300 }
1301 }
1302
1303 #[test]
1304 fn cmap4_final_segment_is_not_duplicated() {
1305 cmap4_has_a_unique_final_segment([('a', GlyphId::new(1)), ('\u{FFFF}', GlyphId::new(0))]);
1306 }
1307
1308 #[test]
1309 fn cmap4_final_segment_is_added_even_if_last_generated_segment_ends_with_0xffff() {
1310 cmap4_has_a_unique_final_segment([
1311 ('a', GlyphId::new(1)),
1312 ('\u{FFFE}', GlyphId::new(0)),
1313 ('\u{FFFF}', GlyphId::new(0)),
1314 ]);
1315 }
1316
1317 type RecordKey = (PlatformId, u16, u16);
1318
1319 fn read_back(cmap: &write::Cmap) -> Vec<(RecordKey, Vec<(char, GlyphId)>)> {
1322 let bytes = dump_table(cmap).unwrap();
1323 let font_data = FontData::new(&bytes);
1324 let cmap = Cmap::read(font_data).unwrap();
1325 cmap.encoding_records()
1326 .iter()
1327 .map(|rec| {
1328 let subtable = rec.subtable(font_data).unwrap();
1329 let mut mappings = subtable
1330 .iter()
1331 .map(|(c, gid)| (char::from_u32(c).unwrap(), gid))
1332 .collect::<Vec<_>>();
1333 if subtable.format() == 4 {
1334 assert_eq!(mappings.pop(), Some(('\u{FFFF}', GlyphId::NOTDEF)));
1336 }
1337 let key = (rec.platform_id(), rec.encoding_id(), subtable.format());
1338 (key, mappings)
1339 })
1340 .collect()
1341 }
1342
1343 fn assert_format_12_only(mut mappings: Vec<(char, GlyphId)>) {
1345 let cmap = write::Cmap::from_mappings(mappings.clone()).unwrap();
1346 mappings.sort();
1347 mappings.dedup();
1348 assert_eq!(
1349 read_back(&cmap),
1350 [
1351 (
1352 (PlatformId::Unicode, UNICODE_FULL_REPERTOIRE_ENCODING, 12),
1353 mappings.clone()
1354 ),
1355 (
1356 (PlatformId::Windows, WINDOWS_FULL_REPERTOIRE_ENCODING, 12),
1357 mappings
1358 ),
1359 ]
1360 );
1361 }
1362
1363 fn too_long_for_format_4() -> MappingBuilder {
1366 MappingBuilder::default().extend(('\u{1000}'..='\u{AC3F}').rev())
1367 }
1368
1369 #[test]
1370 fn omit_format_4_if_too_long() {
1371 let mappings = too_long_for_format_4().build();
1372 assert_eq!(mappings.len(), 40_000);
1373 assert_eq!(
1374 super::CmapSubtable::create_format_4(&mappings),
1375 Err(super::Format4Error::LengthOverflow(16 + 2 * 8 + 40_000 * 2))
1377 );
1378 assert_format_12_only(mappings);
1379 }
1380
1381 #[test]
1382 fn omit_format_4_if_too_long_with_supplementary_chars() {
1383 let mappings = too_long_for_format_4()
1384 .extend(['\u{1F600}', '\u{1F602}', '\u{20000}'])
1385 .build();
1386 assert_format_12_only(mappings);
1387 }
1388
1389 #[test]
1390 fn omit_format_4_if_bmp_glyph_id_too_large() {
1391 let mut mappings = simple_cmap_mappings();
1392 let big_gid = ('\u{4E00}', GlyphId::new(70_000));
1393 mappings.push(big_gid);
1394 mappings.sort();
1395 assert_eq!(
1396 super::CmapSubtable::create_format_4(&mappings),
1397 Err(super::Format4Error::GlyphIdOverflow {
1398 ch: big_gid.0,
1399 gid: big_gid.1
1400 })
1401 );
1402 assert_format_12_only(mappings);
1403 }
1404
1405 #[test]
1406 fn empty_mappings() {
1407 let cmap = write::Cmap::from_mappings([]).unwrap();
1408 assert!(read_back(&cmap).is_empty());
1409 }
1410
1411 #[test]
1412 fn large_supplementary_glyph_id_keeps_format_4() {
1413 let mut mappings = bmp_and_non_bmp_cmap_mappings();
1414 mappings.push(('\u{20000}', GlyphId::new(70_000)));
1415 mappings.sort();
1416 mappings.dedup();
1417 let bmp_mappings = simple_cmap_mappings();
1418
1419 let cmap = write::Cmap::from_mappings(mappings.clone()).unwrap();
1420 assert_eq!(
1421 read_back(&cmap),
1422 [
1423 (
1424 (PlatformId::Unicode, UNICODE_BMP_ENCODING, 4),
1425 bmp_mappings.clone()
1426 ),
1427 (
1428 (PlatformId::Unicode, UNICODE_FULL_REPERTOIRE_ENCODING, 12),
1429 mappings.clone()
1430 ),
1431 ((PlatformId::Windows, WINDOWS_BMP_ENCODING, 4), bmp_mappings),
1432 (
1433 (PlatformId::Windows, WINDOWS_FULL_REPERTOIRE_ENCODING, 12),
1434 mappings
1435 ),
1436 ]
1437 );
1438 }
1439
1440 #[test]
1441 fn oversized_cmap4_fails_validation() {
1442 let cmap4 = write::Cmap4::new(
1445 0,
1446 vec![0x7FFF, 0xFFFF],
1447 vec![0, 0xFFFF],
1448 vec![0, 1],
1449 vec![4, 0],
1450 (1..=0x8000).collect(),
1451 );
1452 assert!(matches!(
1453 dump_table(&cmap4),
1454 Err(crate::error::Error::ValidationFailed(_))
1455 ));
1456
1457 let cmap = write::Cmap::new(vec![write::EncodingRecord::new(
1458 PlatformId::Windows,
1459 WINDOWS_BMP_ENCODING,
1460 cmap4.into(),
1461 )]);
1462 assert!(matches!(
1463 dump_table(&cmap),
1464 Err(crate::error::Error::ValidationFailed(_))
1465 ));
1466 }
1467}