1include!("../../generated/generated_cmap.rs");
6
7use std::collections::HashMap;
8
9use crate::search_range::SearchRange;
10
11#[cfg(test)]
12mod dmap_tests {
13 use super::*;
14 use crate::{dump_table, FontBuilder};
15 use read_fonts::{FontRef, TableProvider};
16
17 #[test]
18 fn dmap_roundtrip() {
19 let dmap = Dmap::new(vec![EncodingRecord::new(
20 PlatformId::Windows,
21 10,
22 CmapSubtable::format_12(0, vec![SequentialMapGroup::new(65, 65, 70000)]),
23 )]);
24 let bytes = dump_table(&dmap).unwrap();
25 assert_eq!(Dmap::read(FontData::new(&bytes)).unwrap(), dmap);
26 let mut builder = FontBuilder::new();
27 builder.add_table(&dmap).unwrap();
28 let bytes = builder.build();
29 let font = FontRef::new(&bytes).unwrap();
30 assert_eq!(
31 font.dmap().unwrap().as_cmap().map_codepoint('A'),
32 Some(GlyphId::new(70000))
33 );
34 assert!(font.cmap().is_err());
35 }
36}
37
38const WINDOWS_BMP_ENCODING: u16 = 1;
40const WINDOWS_FULL_REPERTOIRE_ENCODING: u16 = 10;
41
42const UNICODE_BMP_ENCODING: u16 = 3;
44const UNICODE_FULL_REPERTOIRE_ENCODING: u16 = 4;
45
46#[derive(Clone, Copy, Debug, PartialEq, Eq)]
48enum Format4Error {
49 NoBmpMappings,
51 GlyphIdOverflow { ch: char, gid: GlyphId },
53 LengthOverflow(usize),
55}
56
57impl std::fmt::Display for Format4Error {
58 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
59 match self {
60 Self::NoBmpMappings => write!(f, "there are no chars in the BMP"),
61 Self::GlyphIdOverflow { ch, gid } => write!(
62 f,
63 "U+{:04X} is mapped to {gid}, which is above the format 4 maximum of 65535",
64 *ch as u32
65 ),
66 Self::LengthOverflow(len) => write!(
67 f,
68 "length of {len} bytes is above the format 4 maximum of 65535"
69 ),
70 }
71 }
72}
73
74impl CmapSubtable {
75 fn create_format_4(mappings: &[(char, GlyphId)]) -> Result<Self, Format4Error> {
85 let mut end_code = Vec::with_capacity(mappings.len() + 1);
86 let mut start_code = Vec::with_capacity(mappings.len() + 1);
87 let mut id_deltas = Vec::with_capacity(mappings.len() + 1);
88 let mut id_range_offsets = Vec::with_capacity(mappings.len() + 1);
89 let mut glyph_ids = Vec::new();
90
91 if let Some((ch, gid)) = mappings
93 .iter()
94 .take_while(|(ch, _)| *ch <= '\u{FFFF}')
95 .find(|(_, gid)| gid.to_u32() > 0xFFFF)
96 {
97 return Err(Format4Error::GlyphIdOverflow { ch: *ch, gid: *gid });
98 }
99 let segments = compute_format4_segments(mappings);
100 if segments.is_empty() {
101 return Err(Format4Error::NoBmpMappings);
102 }
103 let add_final_segment = segments.last().is_none_or(|seg| {
104 (mappings[seg.start_ix].0, mappings[seg.end_ix].0) != ('\u{FFFF}', '\u{FFFF}')
105 });
106 let n_segments = segments.len() + add_final_segment as usize;
107 let n_glyph_ids = segments
108 .iter()
109 .filter(|seg| seg.id_delta.is_none())
110 .map(Format4Segment::len)
111 .sum();
112 Cmap4::checked_length(n_segments, n_glyph_ids)?;
113 for (i, segment) in segments.into_iter().enumerate() {
114 let start = mappings[segment.start_ix].0;
115 let end = mappings[segment.end_ix].0;
116 start_code.push(start as u32 as u16);
117 end_code.push(end as u32 as u16);
118 if let Some(delta) = segment.id_delta {
119 id_deltas.push(delta as i16);
121 id_range_offsets.push(0u16);
122 } else {
123 let current_n_ids = glyph_ids.len();
130 let n_following_segments = n_segments - i;
131 let id_range_offset = (n_following_segments + current_n_ids) * u16::RAW_BYTE_LEN;
134 id_deltas.push(0);
135 id_range_offsets.push(
138 id_range_offset
139 .try_into()
140 .expect("length checked before now"),
141 );
142 glyph_ids.extend(
143 mappings[segment.start_ix..=segment.end_ix]
144 .iter()
145 .map(|(_, gid)| u16::try_from(gid.to_u32()).expect("checked before now")),
146 )
147 }
148 }
149
150 if add_final_segment {
152 end_code.push(0xFFFF);
153 start_code.push(0xFFFF);
154 id_deltas.push(1);
155 id_range_offsets.push(0);
156 }
157
158 Ok(Self::format_4(
159 0,
160 end_code,
161 start_code,
162 id_deltas,
163 id_range_offsets,
164 glyph_ids,
165 ))
166 }
167
168 fn create_format_12(mappings: &[(char, GlyphId)]) -> Self {
173 let (mut char_codes, gids): (Vec<u32>, Vec<u32>) = mappings
174 .iter()
175 .map(|(cp, gid)| (*cp as u32, gid.to_u32()))
176 .unzip();
177 let cmap: HashMap<_, _> = char_codes.iter().cloned().zip(gids).collect();
178 char_codes.dedup();
179
180 let mut start_char_code = *char_codes.first().unwrap();
182 let mut start_glyph_id = cmap[&start_char_code];
183 let mut last_glyph_id = start_glyph_id.wrapping_sub(1);
184 let mut last_char_code = start_char_code.wrapping_sub(1);
185 let mut groups = Vec::new();
186 for char_code in char_codes {
187 let glyph_id = cmap[&char_code];
188 if glyph_id != last_glyph_id.wrapping_add(1)
189 || char_code != last_char_code.wrapping_add(1)
190 {
191 groups.push((start_char_code, last_char_code, start_glyph_id));
192 start_char_code = char_code;
193 start_glyph_id = glyph_id;
194 }
195 last_glyph_id = glyph_id;
196 last_char_code = char_code;
197 }
198 groups.push((start_char_code, last_char_code, start_glyph_id));
199
200 let seq_map_groups = groups
201 .into_iter()
202 .map(|(start_char, end_char, gid)| SequentialMapGroup::new(start_char, end_char, gid))
203 .collect::<Vec<_>>();
204 CmapSubtable::format_12(
205 0, seq_map_groups,
207 )
208 }
209}
210
211#[derive(Debug, Clone, PartialEq, Eq)]
216pub struct CmapConflict {
217 ch: char,
218 gid1: GlyphId,
219 gid2: GlyphId,
220}
221
222impl std::fmt::Display for CmapConflict {
223 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
224 let ch32 = self.ch as u32;
225 write!(
226 f,
227 "Cannot map {:?} (U+{ch32:04X}) to two different glyph ids: {} and {}",
228 self.ch, self.gid1, self.gid2
229 )
230 }
231}
232
233impl std::error::Error for CmapConflict {}
234
235impl Cmap {
236 pub fn from_mappings(
258 mappings: impl IntoIterator<Item = (char, GlyphId)>,
259 ) -> Result<Cmap, CmapConflict> {
260 let mut mappings: Vec<_> = mappings.into_iter().collect();
261 mappings.sort();
262 mappings.dedup();
263 if let Some((ch, gid1, gid2)) =
264 mappings
265 .iter()
266 .zip(mappings.iter().skip(1))
267 .find_map(|((c1, g1), (c2, g2))| {
268 (c1 == c2 && g1 != g2).then(|| (*c1, *g1.min(g2), *g1.max(g2)))
269 })
270 {
271 return Err(CmapConflict { ch, gid1, gid2 });
272 }
273
274 let mut uni_records = Vec::new(); let mut win_records = Vec::new(); let bmp_subtable = CmapSubtable::create_format_4(&mappings)
280 .inspect_err(|e| log::warn!("omitting cmap format 4 subtable: {e}"))
281 .ok();
282 let has_format_4 = bmp_subtable.is_some();
283 if let Some(bmp_subtable) = bmp_subtable {
284 uni_records.push(EncodingRecord::new(
289 PlatformId::Unicode,
290 UNICODE_BMP_ENCODING,
291 bmp_subtable.clone(),
292 ));
293 win_records.push(EncodingRecord::new(
294 PlatformId::Windows,
295 WINDOWS_BMP_ENCODING,
296 bmp_subtable,
297 ));
298 }
299
300 if !mappings.is_empty()
304 && (!has_format_4 || mappings.iter().any(|(cp, _)| *cp > '\u{FFFF}'))
305 {
306 let full_repertoire_subtable = CmapSubtable::create_format_12(&mappings);
307 uni_records.push(EncodingRecord::new(
309 PlatformId::Unicode,
310 UNICODE_FULL_REPERTOIRE_ENCODING,
311 full_repertoire_subtable.clone(),
312 ));
313 win_records.push(EncodingRecord::new(
314 PlatformId::Windows,
315 WINDOWS_FULL_REPERTOIRE_ENCODING,
316 full_repertoire_subtable,
317 ));
318 }
319
320 Ok(Cmap::new(
326 uni_records.into_iter().chain(win_records).collect(),
327 ))
328 }
329}
330
331#[derive(Clone, Copy, Debug)]
333struct Format4Segment {
334 start_ix: usize,
336 end_ix: usize,
337 id_delta: Option<i32>,
338}
339
340impl Format4Segment {
341 fn len(&self) -> usize {
342 self.end_ix - self.start_ix + 1
343 }
344}
345
346fn compute_format4_segments(mappings: &[(char, GlyphId)]) -> Vec<Format4Segment> {
359 let mappings = mappings
361 .iter()
362 .position(|(c, _)| u16::try_from(*c as u32).is_err())
363 .map(|bad_idx| &mappings[..bad_idx])
364 .unwrap_or(mappings);
365 let mut segments = Vec::new();
366 let mut run_start = 0;
367 for run in mappings.chunk_by(|(c1, _), (c2, _)| *c1 as u32 + 1 == *c2 as u32) {
368 push_cheapest_segments(run, run_start, &mut segments);
369 run_start += run.len();
370 }
371 segments
372}
373
374fn push_cheapest_segments(
391 run: &[(char, GlyphId)],
392 offset: usize,
393 segments: &mut Vec<Format4Segment>,
394) {
395 const SEGMENT_COST: usize = 4 * u16::RAW_BYTE_LEN;
397 const GLYPH_ID_COST: usize = u16::RAW_BYTE_LEN;
399
400 let mut best = vec![0; run.len() + 1];
402 let mut last_segment = vec![(0, false); run.len() + 1];
404 let mut delta_start = 0;
406 let mut array_start = 0;
408 for k in 1..=run.len() {
409 if k > 1 && run[k - 2].1.to_u32() + 1 != run[k - 1].1.to_u32() {
410 delta_start = k - 1;
411 }
412 let delta_cost = best[delta_start] + SEGMENT_COST;
413 let array_cost = best[array_start] + SEGMENT_COST + (k - array_start) * GLYPH_ID_COST;
414 (best[k], last_segment[k]) = if delta_cost <= array_cost {
416 (delta_cost, (delta_start, true))
417 } else {
418 (array_cost, (array_start, false))
419 };
420 if best[k] < best[array_start] + (k - array_start) * GLYPH_ID_COST {
423 array_start = k;
424 }
425 }
426
427 let first_new = segments.len();
429 let mut end = run.len();
430 while end > 0 {
431 let (start, use_delta) = last_segment[end];
432 let (ch, gid) = run[start];
433 segments.push(Format4Segment {
434 start_ix: offset + start,
435 end_ix: offset + end - 1,
436 id_delta: use_delta.then(|| gid.to_u32() as i32 - ch as u32 as i32),
437 });
438 end = start;
439 }
440 segments[first_new..].reverse();
441}
442
443impl Cmap4 {
444 fn checked_length(n_segments: usize, n_glyph_ids: usize) -> Result<u16, Format4Error> {
448 const FIXED_SIZE: usize = 8 * u16::RAW_BYTE_LEN;
451 const PER_SEGMENT_LEN: usize = 4 * u16::RAW_BYTE_LEN;
452
453 let segment_len = n_segments * PER_SEGMENT_LEN;
454 let gid_len = n_glyph_ids * u16::RAW_BYTE_LEN;
455
456 let length = FIXED_SIZE + segment_len + gid_len;
457 length
458 .try_into()
459 .map_err(|_| Format4Error::LengthOverflow(length))
460 }
461
462 fn compute_length(&self) -> u16 {
463 Self::checked_length(self.end_code.len(), self.glyph_id_array.len())
465 .expect("cmap4 overflow")
466 }
467
468 fn check_length(&self, ctx: &mut ValidationCtx) {
469 if let Err(e) = Self::checked_length(self.end_code.len(), self.glyph_id_array.len()) {
470 ctx.report(e);
471 }
472 }
473
474 fn compute_search_range(&self) -> u16 {
475 SearchRange::compute(self.end_code.len(), u16::RAW_BYTE_LEN).search_range
476 }
477
478 fn compute_entry_selector(&self) -> u16 {
479 SearchRange::compute(self.end_code.len(), u16::RAW_BYTE_LEN).entry_selector
480 }
481
482 fn compute_range_shift(&self) -> u16 {
483 SearchRange::compute(self.end_code.len(), u16::RAW_BYTE_LEN).range_shift
484 }
485}
486
487impl Cmap12 {
488 fn compute_length(&self) -> u32 {
489 const FIXED_SIZE: usize = 2 * u16::RAW_BYTE_LEN + 3 * u32::RAW_BYTE_LEN;
491 const PER_SEGMENT_LEN: usize = 3 * u32::RAW_BYTE_LEN;
492
493 (FIXED_SIZE + PER_SEGMENT_LEN * self.groups.len())
494 .try_into()
495 .unwrap()
496 }
497}
498
499macro_rules! impl_uvs_writer {
511 ($cmap:ident, $format:literal) => {
512 impl FontWrite for $cmap {
513 fn write_into(&self, writer: &mut TableWriter) {
514 ($format as u16).write_into(writer);
515 self.compute_length().write_into(writer);
516 u32::try_from(self.var_selector.len())
517 .unwrap()
518 .write_into(writer);
519
520 let mut next_offset = Self::HEADER_LEN + self.var_selector.len() * Self::RECORD_LEN;
523 let mut write_offset = |writer: &mut TableWriter, len: Option<usize>| match len {
524 Some(len) => {
525 (next_offset as u32).write_into(writer);
526 next_offset += len;
527 }
528 None => 0u32.write_into(writer),
529 };
530 for record in &self.var_selector {
531 record.var_selector.write_into(writer);
532 write_offset(
533 writer,
534 record.default_uvs.as_ref().map(DefaultUvs::compute_len),
535 );
536 write_offset(
537 writer,
538 record.non_default_uvs.as_ref().map(|uvs| uvs.compute_len()),
539 );
540 }
541 for record in &self.var_selector {
542 if let Some(uvs) = record.default_uvs.as_ref() {
543 uvs.write_into(writer);
544 }
545 if let Some(uvs) = record.non_default_uvs.as_ref() {
546 uvs.write_into(writer);
547 }
548 }
549 }
550
551 fn table_type(&self) -> TableType {
552 TableType::Named(stringify!($cmap))
553 }
554 }
555
556 impl $cmap {
557 const HEADER_LEN: usize = u16::RAW_BYTE_LEN + 2 * u32::RAW_BYTE_LEN;
559 const RECORD_LEN: usize = Uint24::RAW_BYTE_LEN + 2 * u32::RAW_BYTE_LEN;
561
562 fn compute_length(&self) -> u32 {
563 let uvs_len: usize = self
564 .var_selector
565 .iter()
566 .map(|record| {
567 record
568 .default_uvs
569 .as_ref()
570 .map(DefaultUvs::compute_len)
571 .unwrap_or(0)
572 + record
573 .non_default_uvs
574 .as_ref()
575 .map(|uvs| uvs.compute_len())
576 .unwrap_or(0)
577 })
578 .sum();
579
580 (Self::HEADER_LEN + self.var_selector.len() * Self::RECORD_LEN + uvs_len)
581 .try_into()
582 .expect("UVS subtable overflow")
583 }
584 }
585 };
586}
587
588impl_uvs_writer!(Cmap14, 14);
589impl_uvs_writer!(Cmap15, 15);
590
591impl DefaultUvs {
592 fn compute_len(&self) -> usize {
593 u32::RAW_BYTE_LEN + self.ranges.len() * (Uint24::RAW_BYTE_LEN + u8::RAW_BYTE_LEN)
595 }
596}
597
598impl NonDefaultUvs {
599 fn compute_len(&self) -> usize {
600 u32::RAW_BYTE_LEN + self.uvs_mapping.len() * (Uint24::RAW_BYTE_LEN + u16::RAW_BYTE_LEN)
602 }
603}
604
605impl NonDefaultUvs24 {
606 fn compute_len(&self) -> usize {
607 u32::RAW_BYTE_LEN + self.uvs_mapping.len() * (2 * Uint24::RAW_BYTE_LEN)
608 }
609}
610
611#[cfg(test)]
612mod tests {
613 use std::ops::RangeInclusive;
614
615 use font_types::{GlyphId, Uint24};
616 use read_fonts::{
617 tables::cmap::{Cmap, CmapSubtable, PlatformId},
618 FontData, FontRead, TableProvider,
619 };
620
621 use crate::{
622 dump_table,
623 from_obj::ToOwnedTable,
624 tables::cmap::{
625 self as write, CmapConflict, UNICODE_BMP_ENCODING, UNICODE_FULL_REPERTOIRE_ENCODING,
626 WINDOWS_BMP_ENCODING, WINDOWS_FULL_REPERTOIRE_ENCODING,
627 },
628 };
629
630 use super::{Cmap12, SequentialMapGroup};
631
632 fn assert_generates_simple_cmap(mappings: Vec<(char, GlyphId)>) {
633 let cmap = write::Cmap::from_mappings(mappings).unwrap();
634
635 let bytes = dump_table(&cmap).unwrap();
636 let font_data = FontData::new(&bytes);
637 let cmap = Cmap::read(font_data).unwrap();
638
639 assert_eq!(
640 2,
641 cmap.encoding_records().len(),
642 "{:?}",
643 cmap.encoding_records()
644 );
645 assert_eq!(
646 vec![
647 (PlatformId::Unicode, UNICODE_BMP_ENCODING),
648 (PlatformId::Windows, WINDOWS_BMP_ENCODING)
649 ],
650 cmap.encoding_records()
651 .iter()
652 .map(|er| (er.platform_id(), er.encoding_id()))
653 .collect::<Vec<_>>()
654 );
655
656 for encoding_record in cmap.encoding_records() {
657 let CmapSubtable::Format4(cmap4) = encoding_record.subtable(font_data).unwrap() else {
658 panic!("Expected a cmap4 in {encoding_record:?}");
659 };
660
661 assert_eq!(
663 (8, 8, 2, 0),
664 (
665 cmap4.seg_count_x2(),
666 cmap4.search_range(),
667 cmap4.entry_selector(),
668 cmap4.range_shift()
669 )
670 );
671 assert_eq!(cmap4.start_code(), &[10u16, 30u16, 153u16, 0xffffu16]);
672 assert_eq!(cmap4.end_code(), &[20u16, 90u16, 480u16, 0xffffu16]);
673 assert_eq!(cmap4.id_delta(), &[-10i16, -19i16, -81i16, 1i16]);
675 assert_eq!(cmap4.id_range_offsets(), &[0u16, 0u16, 0u16, 0u16]);
676 }
677 }
678
679 fn simple_cmap_mappings() -> Vec<(char, GlyphId)> {
680 (10..=20)
681 .chain(30..=90)
682 .chain(153..=480)
683 .enumerate()
684 .map(|(idx, codepoint)| (char::from_u32(codepoint).unwrap(), GlyphId::new(idx as u32)))
685 .collect()
686 }
687
688 #[test]
691 fn generate_simple_cmap4() {
692 let mappings = simple_cmap_mappings();
693 assert_generates_simple_cmap(mappings);
694 }
695
696 #[test]
697 fn generate_cmap4_out_of_order_input() {
698 let mut ordered = simple_cmap_mappings();
699 let mut disordered = Vec::new();
700 while !ordered.is_empty() {
701 if ordered.len() % 2 == 0 {
702 disordered.insert(0, ordered.remove(0));
703 } else {
704 disordered.push(ordered.remove(0));
705 }
706 }
707 assert_ne!(ordered, disordered);
708 assert_generates_simple_cmap(disordered);
709 }
710
711 #[test]
712 fn generate_cmap4_large_values() {
713 let mut mappings = simple_cmap_mappings();
714 let codepoint = char::from_u32(0xa78b).unwrap();
716 let gid = GlyphId::new(153);
717 mappings.push((codepoint, gid));
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 assert_eq!(cmap.map_codepoint(codepoint), Some(gid));
725 }
726
727 #[test]
728 fn generate_cmap4_large_positive_delta() {
729 let mut mappings = simple_cmap_mappings();
730 let codepoint = '\u{2026}';
733 let gid = GlyphId::new(58562);
734 mappings.push((codepoint, gid));
735
736 let cmap = write::Cmap::from_mappings(mappings).unwrap();
737
738 let bytes = dump_table(&cmap).unwrap();
739 let font_data = FontData::new(&bytes);
740 let cmap = Cmap::read(font_data).unwrap();
741 assert_eq!(cmap.map_codepoint(codepoint), Some(gid));
742 }
743
744 #[test]
745 fn bytes_are_reused() {
746 let mappings = simple_cmap_mappings();
748 let cmap_both = write::Cmap::from_mappings(mappings).unwrap();
749 assert_eq!(2, cmap_both.encoding_records.len(), "{cmap_both:?}");
750
751 let bytes_for_both = dump_table(&cmap_both).unwrap().len();
752
753 for i in 0..cmap_both.encoding_records.len() {
754 let mut cmap = cmap_both.clone();
755 cmap.encoding_records.remove(i);
756 let bytes_for_one = dump_table(&cmap).unwrap().len();
757 assert_eq!(bytes_for_one + 8, bytes_for_both);
758 }
759 }
760
761 fn non_bmp_cmap_mappings() -> Vec<(char, GlyphId)> {
762 vec![
764 ('\u{1f12f}', GlyphId::new(481)),
766 ('\u{1f130}', GlyphId::new(482)),
767 ('\u{1f132}', GlyphId::new(483)),
769 ('\u{1f133}', GlyphId::new(484)),
770 ('\u{1f134}', GlyphId::new(486)),
772 ('\u{1f136}', GlyphId::new(488)),
774 ('\u{1f136}', GlyphId::new(488)),
775 ]
776 }
777
778 fn bmp_and_non_bmp_cmap_mappings() -> Vec<(char, GlyphId)> {
779 let mut mappings = simple_cmap_mappings();
780 mappings.extend(non_bmp_cmap_mappings());
781 mappings
782 }
783
784 fn assert_cmap12_groups(
785 font_data: FontData,
786 cmap: &Cmap,
787 record_index: usize,
788 expected: &[(u32, u32, u32)],
789 ) {
790 let rec = &cmap.encoding_records()[record_index];
791 let CmapSubtable::Format12(subtable) = rec.subtable(font_data).unwrap() else {
792 panic!("Expected a cmap12 in {rec:?}");
793 };
794 let groups = subtable
795 .groups()
796 .iter()
797 .map(|g| (g.start_char_code(), g.end_char_code(), g.start_glyph_id()))
798 .collect::<Vec<_>>();
799 assert_eq!(groups.len(), expected.len());
800 assert_eq!(groups, expected);
801 }
802
803 #[test]
804 fn generate_cmap4_and_12() {
805 let mappings = bmp_and_non_bmp_cmap_mappings();
806
807 let cmap = write::Cmap::from_mappings(mappings).unwrap();
808
809 let bytes = dump_table(&cmap).unwrap();
810 let font_data = FontData::new(&bytes);
811 let cmap = Cmap::read(font_data).unwrap();
812
813 assert_eq!(
814 4,
815 cmap.encoding_records().len(),
816 "{:?}",
817 cmap.encoding_records()
818 );
819 assert_eq!(
820 vec![
821 (PlatformId::Unicode, UNICODE_BMP_ENCODING),
822 (PlatformId::Unicode, UNICODE_FULL_REPERTOIRE_ENCODING),
823 (PlatformId::Windows, WINDOWS_BMP_ENCODING),
824 (PlatformId::Windows, WINDOWS_FULL_REPERTOIRE_ENCODING)
825 ],
826 cmap.encoding_records()
827 .iter()
828 .map(|er| (er.platform_id(), er.encoding_id()))
829 .collect::<Vec<_>>()
830 );
831
832 let encoding_records = cmap.encoding_records();
833 let first_rec = &encoding_records[0];
834 assert!(
835 matches!(
836 first_rec.subtable(font_data).unwrap(),
837 CmapSubtable::Format4(_)
838 ),
839 "Expected a cmap4 in {first_rec:?}"
840 );
841
842 let expected_groups = vec![
844 (10, 20, 0),
845 (30, 90, 11),
846 (153, 480, 72),
847 (0x1f12f, 0x1f130, 481),
848 (0x1f132, 0x1f133, 483),
849 (0x1f134, 0x1f134, 486),
850 (0x1f136, 0x1f136, 488),
851 ];
852 assert_cmap12_groups(font_data, &cmap, 1, &expected_groups);
853 assert_cmap12_groups(font_data, &cmap, 3, &expected_groups);
854 }
855
856 #[test]
857 fn generate_cmap12_only() {
858 let mappings = non_bmp_cmap_mappings();
859
860 let cmap = write::Cmap::from_mappings(mappings).unwrap();
861
862 let bytes = dump_table(&cmap).unwrap();
863 let font_data = FontData::new(&bytes);
864 let cmap = Cmap::read(font_data).unwrap();
865
866 assert_eq!(
867 2,
868 cmap.encoding_records().len(),
869 "{:?}",
870 cmap.encoding_records()
871 );
872 assert_eq!(
873 vec![
874 (PlatformId::Unicode, UNICODE_FULL_REPERTOIRE_ENCODING),
875 (PlatformId::Windows, WINDOWS_FULL_REPERTOIRE_ENCODING)
876 ],
877 cmap.encoding_records()
878 .iter()
879 .map(|er| (er.platform_id(), er.encoding_id()))
880 .collect::<Vec<_>>()
881 );
882
883 let expected_groups = vec![
885 (0x1f12f, 0x1f130, 481),
886 (0x1f132, 0x1f133, 483),
887 (0x1f134, 0x1f134, 486),
888 (0x1f136, 0x1f136, 488),
889 ];
890 assert_cmap12_groups(font_data, &cmap, 0, &expected_groups);
891 assert_cmap12_groups(font_data, &cmap, 1, &expected_groups);
892 }
893
894 #[test]
895 fn multiple_mappings_fails() {
896 let mut mappings = non_bmp_cmap_mappings();
897 let (ch, gid1) = mappings[0];
899 let gid2 = GlyphId::new(gid1.to_u32() + 1);
900 mappings.push((ch, gid2));
901
902 let result = write::Cmap::from_mappings(mappings);
903
904 assert_eq!(result, Err(CmapConflict { ch, gid1, gid2 }))
905 }
906
907 struct MappingBuilder {
908 mappings: Vec<(char, GlyphId)>,
909 next_gid: u16,
910 }
911
912 impl Default for MappingBuilder {
913 fn default() -> Self {
914 Self {
915 mappings: Default::default(),
916 next_gid: 1,
917 }
918 }
919 }
920
921 impl MappingBuilder {
922 fn extend(mut self, range: impl IntoIterator<Item = char>) -> Self {
923 for c in range {
924 let gid = GlyphId::new(self.next_gid as _);
925 self.mappings.push((c, gid));
926 self.next_gid += 1;
927 }
928 self
929 }
930
931 fn compute(&mut self) -> Vec<RangeInclusive<char>> {
933 self.mappings.sort();
934 super::compute_format4_segments(&self.mappings)
935 .into_iter()
936 .map(|seg| self.mappings[seg.start_ix].0..=self.mappings[seg.end_ix].0)
937 .collect()
938 }
939
940 fn build(mut self) -> Vec<(char, GlyphId)> {
941 self.mappings.sort();
942 self.mappings
943 }
944 }
945
946 #[test]
947 fn f4_segments_simple() {
948 let mut one_big_discontiguous_mapping = MappingBuilder::default().extend(('a'..='z').rev());
949 assert_eq!(one_big_discontiguous_mapping.compute(), ['a'..='z']);
950 }
951
952 #[test]
953 fn f4_segments_combine_small() {
954 let mut mapping = MappingBuilder::default()
955 .extend(['e', 'd', 'c', 'b', 'a'])
957 .extend('f'..='g')
960 .extend('m'..='n')
961 .extend(('o'..='z').rev());
962
963 assert_eq!(mapping.compute(), ['a'..='g', 'm'..='z']);
964 }
965
966 #[test]
967 fn f4_segments_keep() {
968 let mut mapping = MappingBuilder::default()
969 .extend('a'..='m')
970 .extend(['o', 'n']);
971
972 assert_eq!(mapping.compute(), ['a'..='m', 'n'..='o']);
973 }
974
975 fn expect_f4(mapping: &[(char, GlyphId)]) -> super::Cmap4 {
976 let format4 = super::CmapSubtable::create_format_4(mapping).unwrap();
977 let super::CmapSubtable::Format4(format4) = format4 else {
978 panic!("O_o")
979 };
980 format4
981 }
982
983 fn get_read_mapping(table: &super::Cmap4) -> Vec<(char, GlyphId)> {
985 let bytes = dump_table(table).unwrap();
986 let readcmap = read_fonts::tables::cmap::Cmap4::read(bytes.as_slice().into()).unwrap();
987
988 let mut mapping = readcmap
989 .iter()
990 .map(|(c, gid)| (char::from_u32(c).unwrap(), gid))
991 .collect::<Vec<_>>();
992 assert_eq!(mapping.pop(), Some(('\u{FFFF}', GlyphId::NOTDEF)));
995 mapping
996 }
997
998 #[test]
999 fn f4_segment_len_one_uses_delta() {
1000 let mapping = MappingBuilder::default()
1002 .extend(['a', 'z', '1', '9'])
1003 .build();
1004
1005 let format4 = expect_f4(&mapping);
1006 assert_eq!(format4.end_code.len(), 5); assert!(format4.glyph_id_array.is_empty());
1008 assert!(format4.id_delta.iter().all(|d| *d != 0));
1009 }
1010
1011 #[test]
1012 fn f4_efficiency() {
1013 let mapping = MappingBuilder::default()
1015 .extend('A'..='Z')
1016 .extend(('a'..='z').rev())
1017 .build();
1018
1019 let format4 = expect_f4(&mapping);
1020
1021 assert_eq!(
1022 format4.start_code,
1023 ['A' as u32 as u16, 'a' as u32 as u16, 0xffff]
1024 );
1025
1026 assert_eq!(
1027 format4.end_code,
1028 ['Z' as u32 as u16, 'z' as u32 as u16, 0xffff]
1029 );
1030
1031 assert_eq!(format4.id_delta, [-64, 0, 1]);
1032 assert_eq!(format4.id_range_offsets, [0, 4, 0]);
1033
1034 let read_mapping = get_read_mapping(&format4);
1035 assert_eq!(mapping.len(), read_mapping.len());
1036 assert!(mapping == read_mapping);
1037 }
1038
1039 #[test]
1040 fn f4_kinda_real_world() {
1041 let mapping = MappingBuilder::default()
1043 .extend(['\r']) .extend('\x20'..='\x7e') .extend('\u{a0}'..='\u{ac}') .extend('\u{ae}'..='\u{17f}') .extend(['\u{18f}', '\u{192}'])
1048 .build();
1049
1050 let format4 = expect_f4(&mapping);
1051 assert_eq!(format4.end_code.len(), 7);
1053 let read_mapping = get_read_mapping(&format4);
1054
1055 assert_eq!(mapping.len(), read_mapping.len());
1056 assert!(mapping == read_mapping);
1057 }
1058
1059 #[test]
1060 fn f4_sandwich_segment() {
1063 let mapping = MappingBuilder::default()
1064 .extend(['\r'])
1065 .extend(('\x20'..='\x27').rev()) .extend('\x28'..='\x2c') .extend(('\x2d'..='\x34').rev()) .extend('\x35'..='\x3e')
1071 .build();
1072
1073 let format4 = expect_f4(&mapping);
1074 assert_eq!(format4.end_code.len(), 4);
1075 }
1076
1077 #[test]
1078 fn f4_segments_adamina() {
1083 #[rustfmt::skip]
1084 let gids = [
1085 175, 144, 150, 146, 180, 196, 198, 151, 159, 160, 138, 185, 143, 163, 147, 153,
1087 120, 121, 122, 123, 124, 125, 126, 127, 128, 129,
1089 142, 152, 191, 189, 190, 148, 197,
1091 1, 9, 10, 12, 14, 19, 20, 21, 22, 27, 28, 29, 30, 31, 33, 41, 43, 44, 45, 46, 47,
1093 52, 53, 54, 55, 57,
1094 157, 139, 158, 206, 154, 212,
1096 58, 66, 67, 69, 71, 76, 77, 78, 79, 85, 86, 87, 88, 89, 91, 99, 101, 102, 103, 105,
1098 106, 111, 112, 113, 114, 117,
1099 155, 204, 156, 193,
1101 ];
1102 let mappings = ('\x20'..='\x7e').zip(gids.map(GlyphId::new)).collect();
1103 let mut mapping = MappingBuilder {
1104 mappings,
1105 ..Default::default()
1106 };
1107
1108 assert_eq!(
1109 mapping.compute(),
1110 ['\x20'..='\x2f', '\x30'..='\x39', '\x3a'..='\x7e']
1111 );
1112 }
1113
1114 fn cheapest_segments_cost(mappings: &[(char, GlyphId)]) -> usize {
1118 mappings
1119 .chunk_by(|(c1, _), (c2, _)| *c1 as u32 + 1 == *c2 as u32)
1120 .map(|run| {
1121 let mut best = vec![0; run.len() + 1];
1123 for end in 1..=run.len() {
1124 let cheapest = (0..end)
1125 .map(|start| {
1126 let segment = &run[start..end];
1127 let use_delta = segment
1128 .windows(2)
1129 .all(|pair| pair[0].1.to_u32() + 1 == pair[1].1.to_u32());
1130 let glyph_ids_cost = if use_delta { 0 } else { 2 * segment.len() };
1131 best[start] + 8 + glyph_ids_cost
1132 })
1133 .min()
1134 .unwrap();
1135 best[end] = cheapest;
1136 }
1137 best[run.len()]
1138 })
1139 .sum()
1140 }
1141
1142 #[test]
1143 fn f4_segments_are_cheapest() {
1144 use rand::{rngs::StdRng, RngExt, SeedableRng};
1145
1146 const FIXED_COST: usize = 16 + 8;
1148
1149 let mut rng = StdRng::seed_from_u64(4);
1150 for _ in 0..500 {
1151 let p_contiguous = rng.random_range(0.0..1.0);
1153 let mut mappings = Vec::new();
1154 let (mut ch, mut gid) = (0x20, 1);
1155 for _ in 0..rng.random_range(1..40) {
1156 mappings.push((char::from_u32(ch).unwrap(), GlyphId::new(gid)));
1157 ch += if rng.random_bool(0.05) { 2 } else { 1 };
1158 gid = if rng.random_bool(p_contiguous) {
1159 gid + 1
1160 } else {
1161 rng.random_range(1..100)
1162 };
1163 }
1164
1165 let format4 = expect_f4(&mappings);
1166 assert_eq!(get_read_mapping(&format4), mappings);
1167 assert_eq!(
1168 dump_table(&format4).unwrap().len(),
1169 FIXED_COST + cheapest_segments_cost(&mappings),
1170 "{mappings:?}"
1171 );
1172 }
1173 }
1174
1175 #[test]
1177 fn cmap12_length_calculation() {
1178 let more_than_16_bits = u16::MAX as u32 + 5;
1179 let groups = (0..more_than_16_bits)
1180 .map(|i| SequentialMapGroup::new(i, i, i))
1181 .collect();
1182 let cmap12 = Cmap12::new(0, groups);
1183 let bytes = crate::dump_table(&cmap12).unwrap();
1184 let read_it_back = Cmap12::read(bytes.as_slice().into()).unwrap();
1185 assert_eq!(read_it_back.groups.len() as u32, more_than_16_bits);
1186 }
1187
1188 #[test]
1189 fn cmap14_round_trip() {
1190 let font = read_fonts::FontRef::new(font_test_data::CMAP14_FONT1).unwrap();
1191 let cmap = font.cmap().unwrap();
1192 let cmap14 = cmap
1193 .encoding_records()
1194 .iter()
1195 .find_map(|record| match record.subtable(cmap.offset_data()) {
1196 Ok(read_fonts::tables::cmap::CmapSubtable::Format14(table)) => Some(table),
1197 _ => None,
1198 })
1199 .unwrap();
1200 let owned: write::Cmap14 = cmap14.to_owned_table();
1201 let bytes = dump_table(&owned).unwrap();
1202 assert_eq!(
1203 bytes,
1204 &cmap14.offset_data().as_bytes()[..cmap14.length() as usize]
1205 );
1206 }
1207
1208 #[test]
1209 fn cmap15_round_trip() {
1210 use font_test_data::cmap::format15;
1211 let bytes = format15(0xe0100, &[65], &[(66, 65536), (67, 70000), (68, 0xffffff)]);
1212 let read = read_fonts::tables::cmap::Cmap15::read(bytes.as_slice().into()).unwrap();
1213 let owned: write::Cmap15 = read.to_owned_table();
1214 assert_eq!(dump_table(&owned).unwrap(), bytes);
1215 let cmap = write::Cmap::new(vec![write::EncodingRecord::new(
1216 PlatformId::Unicode,
1217 5,
1218 owned.into(),
1219 )]);
1220 let bytes = dump_table(&cmap).unwrap();
1221 let dmap = write::Dmap::new(cmap.encoding_records.clone());
1222 assert_eq!(dump_table(&dmap).unwrap(), bytes);
1223 let cmap = Cmap::read(bytes.as_slice().into()).unwrap();
1224 let (_, subtable) = cmap.uvs_subtable().unwrap();
1225 assert_eq!(
1226 subtable.map_variant(67u32, 0xe0100u32),
1227 Some(read_fonts::tables::cmap::MapVariant::Variant(GlyphId::new(
1228 70000
1229 )))
1230 );
1231 }
1232
1233 #[test]
1234 fn cmap15_offsets_and_record_widths() {
1235 let non_default = write::NonDefaultUvs24::new(vec![write::UvsMapping24::new(
1236 Uint24::new(0x4e08),
1237 Uint24::new(70000),
1238 )]);
1239 let cmap = write::Cmap15::new(vec![
1240 write::VariationSelector15::new(
1241 Uint24::new(0xe0100),
1242 Some(default_uvs()),
1243 Some(non_default.clone()),
1244 ),
1245 write::VariationSelector15::new(
1246 Uint24::new(0xe0101),
1247 Some(default_uvs()),
1248 Some(non_default.clone()),
1249 ),
1250 write::VariationSelector15::new(Uint24::new(0xe0102), None, Some(non_default)),
1251 write::VariationSelector15::new(Uint24::new(0xe0103), Some(default_uvs()), None),
1252 ]);
1253 let bytes = dump_table(&cmap).unwrap();
1254 let read = read_fonts::tables::cmap::Cmap15::read(bytes.as_slice().into()).unwrap();
1255 let offsets: Vec<_> = read
1258 .var_selector()
1259 .iter()
1260 .flat_map(|record| {
1261 [
1262 record.default_uvs_offset().offset().to_u32(),
1263 record.non_default_uvs_offset().offset().to_u32(),
1264 ]
1265 })
1266 .collect();
1267 assert_eq!(offsets, [54, 62, 72, 80, 0, 90, 100, 0]);
1268 assert_eq!(read.length(), 108);
1269 assert_eq!(bytes.len(), 108);
1270 for selector in 0xe0100..=0xe0102u32 {
1271 assert_eq!(
1272 read_fonts::tables::cmap::UvsSubtable::from(read.clone())
1273 .map_variant(0x4e08u32, selector),
1274 Some(read_fonts::tables::cmap::MapVariant::Variant(GlyphId::new(
1275 70000
1276 )))
1277 );
1278 }
1279 let owned: write::Cmap15 = read.to_owned_table();
1280 assert_eq!(dump_table(&owned).unwrap(), bytes);
1281 }
1282
1283 fn default_uvs() -> write::DefaultUvs {
1284 write::DefaultUvs::new(1, vec![write::UnicodeRange::new(Uint24::new(0x4E00), 2)])
1285 }
1286
1287 fn non_default_uvs() -> write::NonDefaultUvs {
1288 write::NonDefaultUvs::new(1, vec![write::UvsMapping::new(Uint24::new(0x4E08), 25)])
1289 }
1290
1291 fn dump_cmap14(cmap14: &write::Cmap14) -> (Vec<u8>, Vec<u32>) {
1294 let bytes = dump_table(cmap14).unwrap();
1295 let read = read_fonts::tables::cmap::Cmap14::read(bytes.as_slice().into()).unwrap();
1296 assert_eq!(read.length() as usize, bytes.len());
1297 let offsets = read
1298 .var_selector()
1299 .iter()
1300 .flat_map(|rec| [rec.default_uvs_offset(), rec.non_default_uvs_offset()])
1301 .map(|off| off.offset().to_u32())
1302 .collect();
1303 (bytes, offsets)
1304 }
1305
1306 #[test]
1308 fn cmap14_identical_uvs_tables_are_not_shared() {
1309 let cmap14 = write::Cmap14::new(vec![
1310 write::VariationSelector::new(
1311 Uint24::new(0xE0100),
1312 Some(default_uvs()),
1313 Some(non_default_uvs()),
1314 ),
1315 write::VariationSelector::new(
1316 Uint24::new(0xE0101),
1317 Some(default_uvs()),
1318 Some(non_default_uvs()),
1319 ),
1320 ]);
1321 let (bytes, offsets) = dump_cmap14(&cmap14);
1322 assert_eq!(offsets, [32, 40, 49, 57]);
1325 assert_eq!(bytes.len(), 66);
1326 }
1327
1328 #[test]
1329 fn cmap14_null_offsets() {
1330 let cmap14 = write::Cmap14::new(vec![
1331 write::VariationSelector::new(Uint24::new(0xE0100), None, Some(non_default_uvs())),
1332 write::VariationSelector::new(Uint24::new(0xE0101), Some(default_uvs()), None),
1333 ]);
1334 let (bytes, offsets) = dump_cmap14(&cmap14);
1335 assert_eq!(offsets, [0, 32, 41, 0]);
1337 let tail = [
1338 dump_table(&non_default_uvs()).unwrap(),
1339 dump_table(&default_uvs()).unwrap(),
1340 ]
1341 .concat();
1342 assert_eq!(&bytes[32..], tail);
1343 }
1344
1345 fn cmap4_has_a_unique_final_segment<I>(mappings: I)
1346 where
1347 I: IntoIterator<Item = (char, GlyphId)>,
1348 {
1349 let cmap = crate::tables::cmap::Cmap::from_mappings(mappings).unwrap();
1350 for record in &cmap.encoding_records {
1351 let crate::tables::cmap::CmapSubtable::Format4(cmap4) = &*record.subtable else {
1352 continue;
1353 };
1354 if !matches!((cmap4.start_code.as_slice(), cmap4.end_code.as_slice()),
1355 (&[.., before_last_start, 0xFFFF], &[.., before_last_end, 0xFFFF])
1356 if before_last_start != 0xFFFF || before_last_end != 0xFFFF)
1357 {
1358 panic!(
1359 "Expected cmap4 to end with a single (0xFFFF, 0xFFFF) segment, but found {:?}",
1360 (&cmap4.start_code, &cmap4.end_code)
1361 );
1362 }
1363 }
1364 }
1365
1366 #[test]
1367 fn cmap4_final_segment_is_not_duplicated() {
1368 cmap4_has_a_unique_final_segment([('a', GlyphId::new(1)), ('\u{FFFF}', GlyphId::new(0))]);
1369 }
1370
1371 #[test]
1372 fn cmap4_final_segment_is_added_even_if_last_generated_segment_ends_with_0xffff() {
1373 cmap4_has_a_unique_final_segment([
1374 ('a', GlyphId::new(1)),
1375 ('\u{FFFE}', GlyphId::new(0)),
1376 ('\u{FFFF}', GlyphId::new(0)),
1377 ]);
1378 }
1379
1380 type RecordKey = (PlatformId, u16, u16);
1381
1382 fn read_back(cmap: &write::Cmap) -> Vec<(RecordKey, Vec<(char, GlyphId)>)> {
1385 let bytes = dump_table(cmap).unwrap();
1386 let font_data = FontData::new(&bytes);
1387 let cmap = Cmap::read(font_data).unwrap();
1388 cmap.encoding_records()
1389 .iter()
1390 .map(|rec| {
1391 let subtable = rec.subtable(font_data).unwrap();
1392 let mut mappings = subtable
1393 .iter()
1394 .map(|(c, gid)| (char::from_u32(c).unwrap(), gid))
1395 .collect::<Vec<_>>();
1396 if subtable.format() == 4 {
1397 assert_eq!(mappings.pop(), Some(('\u{FFFF}', GlyphId::NOTDEF)));
1399 }
1400 let key = (rec.platform_id(), rec.encoding_id(), subtable.format());
1401 (key, mappings)
1402 })
1403 .collect()
1404 }
1405
1406 fn assert_format_12_only(mut mappings: Vec<(char, GlyphId)>) {
1408 let cmap = write::Cmap::from_mappings(mappings.clone()).unwrap();
1409 mappings.sort();
1410 mappings.dedup();
1411 assert_eq!(
1412 read_back(&cmap),
1413 [
1414 (
1415 (PlatformId::Unicode, UNICODE_FULL_REPERTOIRE_ENCODING, 12),
1416 mappings.clone()
1417 ),
1418 (
1419 (PlatformId::Windows, WINDOWS_FULL_REPERTOIRE_ENCODING, 12),
1420 mappings
1421 ),
1422 ]
1423 );
1424 }
1425
1426 fn too_long_for_format_4() -> MappingBuilder {
1429 MappingBuilder::default().extend(('\u{1000}'..='\u{AC3F}').rev())
1430 }
1431
1432 #[test]
1433 fn omit_format_4_if_too_long() {
1434 let mappings = too_long_for_format_4().build();
1435 assert_eq!(mappings.len(), 40_000);
1436 assert_eq!(
1437 super::CmapSubtable::create_format_4(&mappings),
1438 Err(super::Format4Error::LengthOverflow(16 + 2 * 8 + 40_000 * 2))
1440 );
1441 assert_format_12_only(mappings);
1442 }
1443
1444 #[test]
1445 fn omit_format_4_if_too_long_with_supplementary_chars() {
1446 let mappings = too_long_for_format_4()
1447 .extend(['\u{1F600}', '\u{1F602}', '\u{20000}'])
1448 .build();
1449 assert_format_12_only(mappings);
1450 }
1451
1452 #[test]
1453 fn omit_format_4_if_bmp_glyph_id_too_large() {
1454 let mut mappings = simple_cmap_mappings();
1455 let big_gid = ('\u{4E00}', GlyphId::new(70_000));
1456 mappings.push(big_gid);
1457 mappings.sort();
1458 assert_eq!(
1459 super::CmapSubtable::create_format_4(&mappings),
1460 Err(super::Format4Error::GlyphIdOverflow {
1461 ch: big_gid.0,
1462 gid: big_gid.1
1463 })
1464 );
1465 assert_format_12_only(mappings);
1466 }
1467
1468 #[test]
1469 fn empty_mappings() {
1470 let cmap = write::Cmap::from_mappings([]).unwrap();
1471 assert!(read_back(&cmap).is_empty());
1472 }
1473
1474 #[test]
1475 fn large_supplementary_glyph_id_keeps_format_4() {
1476 let mut mappings = bmp_and_non_bmp_cmap_mappings();
1477 mappings.push(('\u{20000}', GlyphId::new(70_000)));
1478 mappings.sort();
1479 mappings.dedup();
1480 let bmp_mappings = simple_cmap_mappings();
1481
1482 let cmap = write::Cmap::from_mappings(mappings.clone()).unwrap();
1483 assert_eq!(
1484 read_back(&cmap),
1485 [
1486 (
1487 (PlatformId::Unicode, UNICODE_BMP_ENCODING, 4),
1488 bmp_mappings.clone()
1489 ),
1490 (
1491 (PlatformId::Unicode, UNICODE_FULL_REPERTOIRE_ENCODING, 12),
1492 mappings.clone()
1493 ),
1494 ((PlatformId::Windows, WINDOWS_BMP_ENCODING, 4), bmp_mappings),
1495 (
1496 (PlatformId::Windows, WINDOWS_FULL_REPERTOIRE_ENCODING, 12),
1497 mappings
1498 ),
1499 ]
1500 );
1501 }
1502
1503 #[test]
1504 fn oversized_cmap4_fails_validation() {
1505 let cmap4 = write::Cmap4::new(
1508 0,
1509 vec![0x7FFF, 0xFFFF],
1510 vec![0, 0xFFFF],
1511 vec![0, 1],
1512 vec![4, 0],
1513 (1..=0x8000).collect(),
1514 );
1515 assert!(matches!(
1516 dump_table(&cmap4),
1517 Err(crate::error::Error::ValidationFailed(_))
1518 ));
1519
1520 let cmap = write::Cmap::new(vec![write::EncodingRecord::new(
1521 PlatformId::Windows,
1522 WINDOWS_BMP_ENCODING,
1523 cmap4.into(),
1524 )]);
1525 assert!(matches!(
1526 dump_table(&cmap),
1527 Err(crate::error::Error::ValidationFailed(_))
1528 ));
1529 }
1530}