1use crate::{
4 model::{font::SharedFont, Font},
5 ps::type1::Type1Font,
6 ps::PredefinedEncoding,
7 tables::{
8 cmap::{
9 Cmap, CmapIterLimits, CmapSubtable, EncodingRecord, MapVariant, PlatformId, UvsSubtable,
10 },
11 name::MacRomanMapping,
12 },
13 FontData, TableProvider,
14};
15use alloc::vec::Vec;
16use types::{GlyphId, Tag};
17use yoke::Yokeable;
18
19#[derive(Clone, Copy)]
66pub struct Charmap<'a> {
67 font: &'a SharedFont,
68}
69
70impl<'a> Charmap<'a> {
71 pub(crate) fn new(font: &'a Font) -> Self {
72 Self {
73 font: font.shared(),
74 }
75 }
76
77 pub fn has_unicode(&self) -> bool {
81 match self.font.kind() {
82 crate::model::Kind::Type1(font) => font.unicode_charmap().iter().next().is_some(),
83 crate::model::Kind::Cff(_, _) => self
84 .font
85 .standalone_cff_unicode_charmap()
86 .is_some_and(|map| map.iter().next().is_some()),
87 _ => self
88 .font
89 .unicode_charmap()
90 .is_some_and(|map| map.dmap.subtable.is_some() || map.cmap.subtable.is_some()),
91 }
92 }
93
94 pub fn unicode_is_symbol(&self) -> bool {
96 self.font
97 .unicode_charmap()
98 .is_some_and(|map| map.dmap.is_symbol || map.cmap.is_symbol)
99 }
100
101 pub fn unicode_is_mac_roman(&self) -> bool {
103 self.font
104 .unicode_charmap()
105 .is_some_and(|map| map.dmap.is_mac_roman || map.cmap.is_mac_roman)
106 }
107
108 pub fn map_unicode(&self, codepoint: impl Into<u32>) -> Option<GlyphId> {
114 let codepoint = codepoint.into();
115 let glyph = match self.font.kind() {
116 crate::model::Kind::Type1(font) => font.unicode_charmap().map(codepoint),
117 crate::model::Kind::Cff(_, _) => {
118 self.font.standalone_cff_unicode_charmap()?.map(codepoint)
119 }
120 _ => self.font.unicode_charmap()?.map(codepoint),
121 };
122 glyph.filter(|glyph| *glyph != GlyphId::NOTDEF)
123 }
124
125 pub fn map_unicode_batched<'o>(
133 &self,
134 codepoints: impl Iterator<Item = (u32, &'o mut GlyphId)>,
135 ) -> usize {
136 match self.font.kind() {
137 crate::model::Kind::Type1(font) => {
138 map_ps_batched(Some(font.unicode_charmap()), codepoints)
139 }
140 crate::model::Kind::Cff(_, _) => {
141 map_ps_batched(self.font.standalone_cff_unicode_charmap(), codepoints)
142 }
143 _ => {
144 if let Some(map) = self.font.unicode_charmap() {
145 map.map_batched(codepoints)
146 } else {
147 for (_, out) in codepoints {
148 *out = GlyphId::NOTDEF;
149 }
150 0
151 }
152 }
153 }
154 }
155
156 pub fn unicodes(&self) -> impl Iterator<Item = (u32, GlyphId)> + 'a {
165 let this = *self;
166 let type1 = match this.font.kind() {
167 crate::model::Kind::Type1(font) => Some(font),
168 _ => None,
169 };
170 let type1 = type1.into_iter().flat_map(iter_type1_unicodes);
171 let standalone_cff = this
172 .font
173 .standalone_cff_unicode_charmap()
174 .into_iter()
175 .flat_map(iter_ps_unicodes);
176 let sfnt = this.font.unicode_charmap().into_iter().flat_map(|map| {
177 map.iter(CmapIterLimits {
178 max_char: char::MAX as u32,
179 glyph_count: this.font.num_glyphs(),
180 })
181 });
182 type1
183 .chain(standalone_cff)
184 .chain(sfnt)
185 .filter(|(_, glyph)| *glyph != GlyphId::NOTDEF)
186 }
187
188 pub fn has_unicode_variants(&self) -> bool {
190 self.font
191 .unicode_charmap()
192 .is_some_and(|map| map.dmap.vs_subtable.is_some() || map.cmap.vs_subtable.is_some())
193 }
194
195 pub fn map_unicode_variant(
200 &self,
201 codepoint: impl Into<u32>,
202 selector: impl Into<u32>,
203 ) -> Option<GlyphId> {
204 self.font
205 .unicode_charmap()?
206 .map_variant(codepoint.into(), selector.into())
207 .filter(|glyph| *glyph != GlyphId::NOTDEF)
208 }
209
210 pub fn unicode_variants(&self) -> impl Iterator<Item = (u32, u32, GlyphId)> + 'a {
218 let this = *self;
219 this.font.unicode_charmap().into_iter().flat_map(|map| {
220 map.variant_mappings(CmapIterLimits {
221 max_char: char::MAX as u32,
222 glyph_count: this.font.num_glyphs(),
223 })
224 .filter_map(move |(codepoint, selector, variant)| {
225 let glyph = match variant {
226 MapVariant::UseDefault => map.map(codepoint)?,
227 MapVariant::Variant(glyph) => glyph,
228 };
229 (glyph != GlyphId::NOTDEF).then_some((codepoint, selector, glyph))
230 })
231 })
232 }
233
234 pub fn encodings(&self) -> impl Iterator<Item = Encoding> + 'a {
239 let this = *self;
240 let sfnt = this
241 .font
242 .encodings()
243 .into_iter()
244 .flat_map(EncodingTables::iter);
245 let type1 = match this.font.kind() {
246 crate::model::Kind::Type1(font) => Some(font),
247 _ => None,
248 };
249 let unicode = type1
250 .filter(|font| font.unicode_charmap().iter().next().is_some())
251 .map(|_| Encoding::type1_unicode());
252 let native = type1
253 .and_then(|font| font.encoding())
254 .map(|encoding| Encoding::type1_native(encoding.predefined()));
255 let standalone_cff = match this.font.kind() {
256 crate::model::Kind::Cff(font, _) => Some(font),
257 _ => None,
258 };
259 let standalone_cff_unicode = standalone_cff
260 .as_ref()
261 .filter(|_| this.has_unicode())
262 .map(|_| Encoding::standalone_cff_unicode());
263 let standalone_cff_native = standalone_cff
264 .as_ref()
265 .filter(|font| !font.is_cid())
266 .and_then(|font| font.encoding())
267 .map(|encoding| Encoding::standalone_cff_native(encoding.predefined()));
268 sfnt.chain(unicode)
269 .chain(native)
270 .chain(standalone_cff_unicode)
271 .chain(standalone_cff_native)
272 }
273
274 pub fn map_code(&self, encoding: Encoding, code: u32) -> Option<GlyphId> {
280 let glyph = match self.font.kind() {
281 crate::model::Kind::Type1(font) if encoding == Encoding::type1_unicode() => {
282 font.unicode_charmap().map(code)
283 }
284 crate::model::Kind::Type1(font) => {
285 let native = font.encoding()?;
286 if encoding != Encoding::type1_native(native.predefined()) {
287 return None;
288 }
289 native.map(u8::try_from(code).ok()?)
290 }
291 crate::model::Kind::Cff(_, _) if encoding == Encoding::standalone_cff_unicode() => {
292 self.font.standalone_cff_unicode_charmap()?.map(code)
293 }
294 crate::model::Kind::Cff(font, _) => {
295 if font.is_cid() {
296 return None;
297 }
298 let native = font.encoding()?;
299 if encoding != Encoding::standalone_cff_native(native.predefined()) {
300 return None;
301 }
302 native.map(u8::try_from(code).ok()?)
303 }
304 _ => self.font.encodings()?.map(encoding, code),
305 };
306 glyph.filter(|glyph| *glyph != GlyphId::NOTDEF)
307 }
308
309 pub fn codes(&self, encoding: Encoding) -> impl Iterator<Item = (u32, GlyphId)> + 'a {
316 let this = *self;
317 let type1 = match this.font.kind() {
318 crate::model::Kind::Type1(font) => Some(font),
319 _ => None,
320 };
321 let type1_unicode = type1
322 .filter(|_| encoding == Encoding::type1_unicode())
323 .into_iter()
324 .flat_map(iter_type1_unicodes);
325 let type1_native = type1
326 .and_then(|font| font.encoding())
327 .filter(|native| encoding == Encoding::type1_native(native.predefined()))
328 .into_iter()
329 .flat_map(|native| {
330 (0_u32..=255)
331 .filter_map(move |code| native.map(code as u8).map(|glyph| (code, glyph)))
332 });
333 let standalone_cff = match this.font.kind() {
334 crate::model::Kind::Cff(font, _) => Some(font),
335 _ => None,
336 };
337 let standalone_cff_unicode = (encoding == Encoding::standalone_cff_unicode())
338 .then(|| this.font.standalone_cff_unicode_charmap())
339 .flatten()
340 .into_iter()
341 .flat_map(iter_ps_unicodes);
342 let standalone_cff_native = standalone_cff
343 .as_ref()
344 .filter(|font| !font.is_cid())
345 .and_then(|font| font.encoding())
346 .filter(|native| encoding == Encoding::standalone_cff_native(native.predefined()))
347 .into_iter()
348 .flat_map(|native| {
349 (0_u32..=255)
350 .filter_map(move |code| native.map(code as u8).map(|glyph| (code, glyph)))
351 });
352 let sfnt = this.font.encodings().into_iter().flat_map(move |tables| {
353 tables.codes(
354 encoding,
355 CmapIterLimits {
356 max_char: u32::MAX,
357 glyph_count: this.font.num_glyphs(),
358 },
359 )
360 });
361 type1_unicode
362 .chain(type1_native)
363 .chain(standalone_cff_unicode)
364 .chain(standalone_cff_native)
365 .chain(sfnt)
366 .filter(|(_, glyph)| *glyph != GlyphId::NOTDEF)
367 }
368}
369
370fn map_ps_batched<'o>(
371 map: Option<&crate::ps::UnicodeCharmap>,
372 codepoints: impl Iterator<Item = (u32, &'o mut GlyphId)>,
373) -> usize {
374 let mut mapped = 0;
375 let mut initial = true;
376 for (codepoint, out) in codepoints {
377 *out = map
378 .and_then(|map| map.map(codepoint))
379 .unwrap_or(GlyphId::NOTDEF);
380 initial &= *out != GlyphId::NOTDEF;
381 mapped += usize::from(initial);
382 }
383 mapped
384}
385
386fn iter_type1_unicodes(font: &Type1Font) -> impl Iterator<Item = (u32, GlyphId)> + '_ {
387 iter_ps_unicodes(font.unicode_charmap())
388}
389
390fn iter_ps_unicodes(map: &crate::ps::UnicodeCharmap) -> impl Iterator<Item = (u32, GlyphId)> + '_ {
391 let mut codepoint = 0;
396 core::iter::from_fn(move || {
397 let (next, glyph) = map.next_char(codepoint)?;
398 codepoint = next;
399 (glyph != GlyphId::NOTDEF).then_some((next, glyph))
400 })
401}
402
403#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
408#[repr(u32)]
409pub enum EncodingKind {
410 None = 0,
412 MsSymbol = 0x7379_6d62,
414 Unicode = 0x756e_6963,
416 Sjis = 0x736a_6973,
418 Prc = 0x6762_2020,
420 Big5 = 0x6269_6735,
422 Wansung = 0x7761_6e73,
424 Johab = 0x6a6f_6861,
426 AdobeStandard = 0x4144_4f42,
428 AdobeExpert = 0x4144_4245,
430 AdobeCustom = 0x4144_4243,
432 AdobeLatin1 = 0x6c61_7431,
434 AppleRoman = 0x6172_6d6e,
436}
437
438#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
443#[repr(C)]
444pub struct Encoding {
445 kind: EncodingKind,
446 index: u32,
447 platform_id: u16,
448 encoding_id: u16,
449}
450
451impl Encoding {
452 const DMAP: u32 = 1 << 31;
453 const BARE_CFF_UNICODE: u32 = u32::MAX - 3;
454 const BARE_CFF_NATIVE: u32 = u32::MAX - 2;
455 const TYPE1_UNICODE: u32 = u32::MAX - 1;
456 const TYPE1_NATIVE: u32 = u32::MAX;
457
458 pub fn kind(self) -> EncodingKind {
460 self.kind
461 }
462
463 pub fn sfnt_index(self) -> Option<(Tag, u32)> {
468 if self.index >= Self::BARE_CFF_UNICODE {
471 return None;
472 }
473 let tag = if self.index & Self::DMAP != 0 {
474 Tag::new(b"DMAP")
475 } else {
476 Tag::new(b"cmap")
477 };
478 Some((tag, self.index & !Self::DMAP))
479 }
480
481 pub fn sfnt_ids(self) -> Option<(u16, u16)> {
483 self.sfnt_index()
484 .map(|_| (self.platform_id, self.encoding_id))
485 }
486
487 pub(crate) fn type1_unicode() -> Self {
488 Self {
489 kind: EncodingKind::Unicode,
490 index: Self::TYPE1_UNICODE,
491 platform_id: 0,
492 encoding_id: 0,
493 }
494 }
495
496 pub(crate) fn type1_native(predefined: Option<PredefinedEncoding>) -> Self {
497 let kind = match predefined {
498 Some(PredefinedEncoding::Standard) => EncodingKind::AdobeStandard,
499 Some(PredefinedEncoding::Expert) => EncodingKind::AdobeExpert,
500 Some(PredefinedEncoding::IsoLatin1) => EncodingKind::AdobeLatin1,
501 None => EncodingKind::AdobeCustom,
502 };
503 Self {
504 kind,
505 index: Self::TYPE1_NATIVE,
506 platform_id: 0,
507 encoding_id: 0,
508 }
509 }
510
511 pub(crate) fn standalone_cff_unicode() -> Self {
512 Self {
513 index: Self::BARE_CFF_UNICODE,
514 ..Self::type1_unicode()
515 }
516 }
517
518 pub(crate) fn standalone_cff_native(predefined: Option<PredefinedEncoding>) -> Self {
519 Self {
520 index: Self::BARE_CFF_NATIVE,
521 ..Self::type1_native(predefined)
522 }
523 }
524}
525
526#[derive(Clone, Default, Yokeable)]
528pub(crate) struct EncodingTables<'a> {
529 cmap: Vec<Option<EncodingTable<'a>>>,
530 dmap: Vec<Option<EncodingTable<'a>>>,
531}
532
533#[derive(Clone, Yokeable)]
534struct EncodingTable<'a> {
535 encoding: Encoding,
536 subtable: CmapSubtable<'a>,
537 delta: Option<CmapSubtable<'a>>,
538}
539
540impl<'a> EncodingTables<'a> {
541 pub(crate) fn read(font: &impl TableProvider<'a>) -> Self {
542 let read = |cmap: Option<Cmap<'a>>, source| {
543 cmap.into_iter()
544 .flat_map(|cmap| {
545 cmap.encoding_records()
546 .iter()
547 .enumerate()
548 .map(move |(index, record)| {
549 EncodingTable::read(index as u32 | source, record, cmap.offset_data())
550 })
551 })
552 .collect::<Vec<_>>()
553 };
554 let dmap = font.dmap().ok().map(|table| table.as_cmap());
555 let mut tables = Self {
556 cmap: read(font.cmap().ok(), 0),
557 dmap: read(dmap.clone(), Encoding::DMAP),
558 };
559 let unicode_delta = dmap
560 .and_then(|table| table.best_subtable())
561 .and_then(|(index, _, _)| tables.dmap.get(index as usize)?.as_ref())
562 .filter(|table| table.encoding.kind == EncodingKind::Unicode);
563 for base in tables.cmap.iter_mut().flatten() {
564 let matches = |delta: &&EncodingTable<'_>| {
565 delta.encoding.kind == base.encoding.kind
566 && (base.encoding.kind != EncodingKind::None
567 || delta.encoding.sfnt_ids() == base.encoding.sfnt_ids())
568 };
569 let delta = (base.encoding.kind == EncodingKind::Unicode)
570 .then_some(unicode_delta)
571 .flatten()
572 .or_else(|| {
573 tables
574 .dmap
575 .iter()
576 .flatten()
577 .find(|delta| delta.encoding.sfnt_ids() == base.encoding.sfnt_ids())
578 })
579 .or_else(|| tables.dmap.iter().flatten().find(matches));
580 base.delta = delta.map(|table| table.subtable.clone());
581 }
582 tables
583 }
584
585 pub(crate) fn iter(&self) -> impl Iterator<Item = Encoding> + '_ {
586 self.cmap
587 .iter()
588 .flatten()
589 .chain(self.dmap.iter().flatten().filter(|delta| {
590 !self.cmap.iter().flatten().any(|base| {
591 base.encoding.kind == delta.encoding.kind
592 && (base.encoding.kind != EncodingKind::None
593 || base.encoding.sfnt_ids() == delta.encoding.sfnt_ids())
594 })
595 }))
596 .map(|table| table.encoding)
597 }
598
599 fn table(&self, encoding: Encoding) -> Option<&EncodingTable<'a>> {
600 let (tag, index) = encoding.sfnt_index()?;
601 let tables = if tag == Tag::new(b"DMAP") {
602 &self.dmap
603 } else {
604 &self.cmap
605 };
606 tables
607 .get(index as usize)?
608 .as_ref()
609 .filter(|table| table.encoding == encoding)
610 }
611
612 pub(crate) fn map(&self, encoding: Encoding, code: u32) -> Option<GlyphId> {
613 let table = self.table(encoding)?;
614 table
615 .delta
616 .as_ref()
617 .and_then(|delta| delta.map_codepoint(code))
618 .filter(|glyph| *glyph != GlyphId::NOTDEF)
619 .or_else(|| table.subtable.map_codepoint(code))
620 }
621
622 fn codes(
623 &self,
624 encoding: Encoding,
625 limits: CmapIterLimits,
626 ) -> impl Iterator<Item = (u32, GlyphId)> + '_ {
627 self.table(encoding).into_iter().flat_map(move |table| {
628 table
629 .delta
630 .as_ref()
631 .into_iter()
632 .flat_map(move |delta| iter_subtable(delta, limits))
633 .filter(|(_, glyph)| *glyph != GlyphId::NOTDEF)
634 .chain(iter_subtable(&table.subtable, limits).filter(|(code, _)| {
635 table
636 .delta
637 .as_ref()
638 .and_then(|delta| delta.map_codepoint(*code))
639 .is_none_or(|glyph| glyph == GlyphId::NOTDEF)
640 }))
641 })
642 }
643}
644
645fn iter_subtable<'a>(
646 subtable: &'a CmapSubtable<'a>,
647 limits: CmapIterLimits,
648) -> impl Iterator<Item = (u32, GlyphId)> + 'a {
649 subtable
650 .iter_with_limits(limits)
651 .chain((0_u32..=255).filter_map(move |code| {
652 matches!(subtable, CmapSubtable::Format0(_))
653 .then(|| subtable.map_codepoint(code))
654 .flatten()
655 .map(|glyph| (code, glyph))
656 }))
657}
658
659impl<'a> EncodingTable<'a> {
660 fn read(index: u32, record: &EncodingRecord, data: FontData<'a>) -> Option<Self> {
661 let subtable = record.subtable(data).ok()?;
662 match subtable {
663 CmapSubtable::Format0(_)
664 | CmapSubtable::Format4(_)
665 | CmapSubtable::Format6(_)
666 | CmapSubtable::Format10(_)
667 | CmapSubtable::Format12(_)
668 | CmapSubtable::Format13(_) => {}
669 _ => return None,
670 }
671 let platform = record.platform_id();
672 let id = record.encoding_id();
673 let kind = match platform {
674 PlatformId::Unicode | PlatformId::ISO => EncodingKind::Unicode,
675 PlatformId::Macintosh if id == 0 => EncodingKind::AppleRoman,
676 PlatformId::Windows => match id {
677 0 => EncodingKind::MsSymbol,
678 1 | 10 => EncodingKind::Unicode,
679 2 => EncodingKind::Sjis,
680 3 => EncodingKind::Prc,
681 4 => EncodingKind::Big5,
682 5 => EncodingKind::Wansung,
683 6 => EncodingKind::Johab,
684 _ => EncodingKind::None,
685 },
686 _ => EncodingKind::None,
687 };
688 Some(EncodingTable {
689 encoding: Encoding {
690 kind,
691 index,
692 platform_id: platform as u16,
693 encoding_id: id,
694 },
695 subtable,
696 delta: None,
697 })
698 }
699}
700
701#[derive(Clone, Default, Yokeable)]
703pub(crate) struct SfntUnicodeCharmap<'a> {
704 cmap: UnicodeSubtables<'a>,
705 dmap: UnicodeSubtables<'a>,
706}
707
708#[derive(Clone, Default, Yokeable)]
709struct UnicodeSubtables<'a> {
710 subtable: Option<CmapSubtable<'a>>,
711 vs_subtable: Option<UvsSubtable<'a>>,
712 is_symbol: bool,
713 is_mac_roman: bool,
714}
715
716impl<'a> SfntUnicodeCharmap<'a> {
717 pub(crate) fn read(font: &impl TableProvider<'a>) -> Self {
718 Self {
719 cmap: UnicodeSubtables::read(font.cmap().ok()),
720 dmap: UnicodeSubtables::read(font.dmap().ok().map(|table| table.as_cmap())),
721 }
722 }
723
724 pub(crate) fn map(&self, codepoint: u32) -> Option<GlyphId> {
725 self.dmap
726 .map(codepoint)
727 .filter(|glyph| *glyph != GlyphId::NOTDEF)
728 .or_else(|| self.cmap.map(codepoint))
729 }
730
731 fn map_batched<'o>(&self, codepoints: impl Iterator<Item = (u32, &'o mut GlyphId)>) -> usize {
737 if self.dmap.subtable.is_none() {
738 return self.cmap.map_batched(codepoints);
739 }
740 let mut mapped = 0;
741 let mut initial = true;
742 for (codepoint, out) in codepoints {
743 *out = self.map(codepoint).unwrap_or(GlyphId::NOTDEF);
744 initial &= *out != GlyphId::NOTDEF;
745 mapped += usize::from(initial);
746 }
747 mapped
748 }
749
750 pub(crate) fn map_variant(&self, codepoint: u32, selector: u32) -> Option<GlyphId> {
751 let variant = self
752 .dmap
753 .vs_subtable
754 .as_ref()
755 .and_then(|table| table.map_variant(codepoint, selector))
756 .or_else(|| {
757 self.cmap
758 .vs_subtable
759 .as_ref()?
760 .map_variant(codepoint, selector)
761 })?;
762 match variant {
763 MapVariant::UseDefault => self.map(codepoint),
764 MapVariant::Variant(glyph) => Some(glyph),
765 }
766 }
767
768 fn iter(&self, limits: CmapIterLimits) -> impl Iterator<Item = (u32, GlyphId)> + '_ {
769 self.dmap
770 .iter(limits)
771 .filter(|(_, glyph)| *glyph != GlyphId::NOTDEF)
772 .chain(self.cmap.iter(limits).filter(|(codepoint, _)| {
773 self.dmap
774 .map(*codepoint)
775 .is_none_or(|glyph| glyph == GlyphId::NOTDEF)
776 }))
777 }
778
779 fn variant_mappings(
780 &self,
781 limits: CmapIterLimits,
782 ) -> impl Iterator<Item = (u32, u32, MapVariant)> + '_ {
783 self.dmap
784 .vs_subtable
785 .as_ref()
786 .into_iter()
787 .flat_map(move |table| table.iter_with_limits(limits))
788 .chain(
789 self.cmap
790 .vs_subtable
791 .as_ref()
792 .into_iter()
793 .flat_map(move |table| table.iter_with_limits(limits))
794 .filter(|(codepoint, selector, _)| {
795 self.dmap
796 .vs_subtable
797 .as_ref()
798 .and_then(|table| table.map_variant(*codepoint, *selector))
799 .is_none()
800 }),
801 )
802 }
803}
804
805impl<'a> UnicodeSubtables<'a> {
806 fn read(cmap: Option<Cmap<'a>>) -> Self {
807 let Some(cmap) = cmap else {
808 return Self::default();
809 };
810 let (subtable, is_symbol, is_mac_roman) = cmap
811 .best_subtable()
812 .map(|(_, record, subtable)| {
813 (Some(subtable), record.is_symbol(), record.is_mac_roman())
814 })
815 .unwrap_or((None, false, false));
816 Self {
817 subtable,
818 vs_subtable: cmap.uvs_subtable().map(|(_, subtable)| subtable),
819 is_symbol,
820 is_mac_roman,
821 }
822 }
823
824 pub(crate) fn map(&self, codepoint: u32) -> Option<GlyphId> {
825 let subtable = self.subtable.as_ref()?;
826 self.map_with(codepoint, |codepoint| subtable.map_codepoint(codepoint))
827 }
828
829 fn map_with(
830 &self,
831 mut codepoint: u32,
832 mut lookup: impl FnMut(u32) -> Option<GlyphId>,
833 ) -> Option<GlyphId> {
834 if self.is_mac_roman && codepoint > 0x7f {
836 codepoint = char::from_u32(codepoint)
837 .and_then(|value| MacRomanMapping.encode(value))
838 .map(u32::from)?;
839 }
840 let result = lookup(codepoint);
841 if result.is_none_or(|glyph| glyph == GlyphId::NOTDEF)
842 && self.is_symbol
843 && codepoint <= 0xff
844 {
845 return lookup(0xf000 + codepoint);
846 }
847 result
848 }
849
850 fn map_batched<'o>(&self, codepoints: impl Iterator<Item = (u32, &'o mut GlyphId)>) -> usize {
851 let mut mapped = 0;
852 let mut initial = true;
853 match self.subtable.as_ref() {
854 Some(CmapSubtable::Format4(subtable)) => {
855 let mut cached = None;
856 for (codepoint, out) in codepoints {
857 *out = self
858 .map_with(codepoint, |codepoint| {
859 subtable.map_codepoint_cached(codepoint, &mut cached)
860 })
861 .unwrap_or(GlyphId::NOTDEF);
862 initial &= *out != GlyphId::NOTDEF;
863 mapped += usize::from(initial);
864 }
865 }
866 Some(CmapSubtable::Format12(subtable)) => {
867 let mut cached = None;
868 for (codepoint, out) in codepoints {
869 *out = self
870 .map_with(codepoint, |codepoint| {
871 subtable.map_codepoint_cached(codepoint, &mut cached)
872 })
873 .unwrap_or(GlyphId::NOTDEF);
874 initial &= *out != GlyphId::NOTDEF;
875 mapped += usize::from(initial);
876 }
877 }
878 Some(subtable) => {
879 for (codepoint, out) in codepoints {
880 *out = self
881 .map_with(codepoint, |codepoint| subtable.map_codepoint(codepoint))
882 .unwrap_or(GlyphId::NOTDEF);
883 initial &= *out != GlyphId::NOTDEF;
884 mapped += usize::from(initial);
885 }
886 }
887 None => {
888 for (_, out) in codepoints {
889 *out = GlyphId::NOTDEF;
890 }
891 }
892 }
893 mapped
894 }
895
896 fn iter(&self, limits: CmapIterLimits) -> impl Iterator<Item = (u32, GlyphId)> + '_ {
897 let roman = self
898 .is_mac_roman
899 .then_some(self)
900 .into_iter()
901 .flat_map(|map| {
902 (u8::MIN..=u8::MAX)
903 .map(|code| MacRomanMapping.decode(code) as u32)
904 .filter_map(move |codepoint| map.map(codepoint).map(|glyph| (codepoint, glyph)))
905 });
906 let normal = (!self.is_mac_roman)
907 .then_some(self)
908 .into_iter()
909 .flat_map(move |map| {
910 map.subtable
911 .as_ref()
912 .into_iter()
913 .flat_map(move |subtable| subtable.iter_with_limits(limits))
914 });
915 roman.chain(normal)
916 }
917}
918
919#[cfg(test)]
920mod tests {
921 use super::*;
922 use crate::{
923 model::{Blob, Font},
924 FontRef,
925 };
926 use alloc::{sync::Arc, vec};
927 use core::sync::atomic::{AtomicUsize, Ordering};
928 use types::Tag;
929
930 #[test]
933 fn iterators_outlive_the_charmap() {
934 let font = Font::new(font_test_data::CMAP14_FONT1, 0).unwrap();
935 let unicodes = font.charmap().unicodes();
936 let variants = font.charmap().unicode_variants();
937 let encodings: Vec<_> = font.charmap().encodings().collect();
938 let codes = font.charmap().codes(encodings[0]);
939 assert!(unicodes.count() > 0);
940 assert!(variants.count() > 0);
941 assert!(codes.count() > 0);
942 }
943
944 fn check_unicode_batch(data: &'static [u8], codepoints: &[u32]) {
945 let font = Font::new(data, 0).unwrap();
946 let charmap = font.charmap();
947 let expected: Vec<_> = codepoints
948 .iter()
949 .map(|&codepoint| charmap.map_unicode(codepoint).unwrap_or(GlyphId::NOTDEF))
950 .collect();
951 let mut actual = vec![GlyphId::new(123); codepoints.len()];
952 let mapped = charmap.map_unicode_batched(codepoints.iter().copied().zip(actual.iter_mut()));
953 assert_eq!(actual, expected);
954 assert_eq!(
955 mapped,
956 expected
957 .iter()
958 .take_while(|&&glyph| glyph != GlyphId::NOTDEF)
959 .count()
960 );
961 }
962
963 #[test]
964 fn batched_unicode_matches_scalar_across_cmap_formats() {
965 let codepoints = [
966 0, 0x20, 0x41, 0x42, 0xe9, 0x400, 0xf000, 0xffff, 0x10000, 0x10ffff, 0x110000, 0x41,
967 0x20, 0,
968 ];
969 for data in [
970 font_test_data::TINOS_SUBSET,
971 font_test_data::CMAP12_FONT1,
972 font_test_data::CMAP4_SYMBOL_PUA,
973 font_test_data::CMAP6,
974 font_test_data::NOTO_SERIF_DISPLAY_TRIMMED,
975 font_test_data::NOTO_COLOR_EMOJI_FLAGS,
976 ] {
977 check_unicode_batch(data, &codepoints);
978 }
979 check_unicode_batch(
980 font_test_data::type1::NOTO_SERIF_REGULAR_SUBSET_PFB,
981 &codepoints,
982 );
983
984 let font = Font::new(font_test_data::TINOS_SUBSET, 0).unwrap();
985 let mut from_chars = [GlyphId::NOTDEF; 3];
986 let mapped = font.charmap().map_unicode_batched(
987 ['A', 'B', 'C']
988 .into_iter()
989 .map(u32::from)
990 .zip(from_chars.iter_mut()),
991 );
992 assert_eq!(
993 from_chars,
994 ['A', 'B', 'C'].map(|ch| font.charmap().map_unicode(ch).unwrap_or(GlyphId::NOTDEF))
995 );
996 assert_eq!(
997 mapped,
998 from_chars
999 .iter()
1000 .take_while(|&&glyph| glyph != GlyphId::NOTDEF)
1001 .count()
1002 );
1003 }
1004
1005 #[test]
1006 fn batched_unicode_handles_large_ordered_and_reversed_inputs() {
1007 let codepoints: Vec<_> = (0..=0x11000).step_by(7).collect();
1008 for data in [font_test_data::TINOS_SUBSET, font_test_data::CMAP12_FONT1] {
1009 check_unicode_batch(data, &codepoints);
1010 check_unicode_batch(data, &codepoints.iter().copied().rev().collect::<Vec<_>>());
1011 }
1012 }
1013
1014 #[test]
1017 fn batched_unicode_honors_dmap() {
1018 use font_test_data::cmap::{font_with_cmaps, format12, table};
1019 let cmap = format12(&[(65, 1), (66, 2), (67, 3)]);
1020 let dmap = format12(&[(66, 9), (68, 10)]);
1021 let data = font_with_cmaps(
1022 Some(&table(&[(3, 10, cmap.as_slice())])),
1023 Some(&table(&[(3, 10, dmap.as_slice())])),
1024 );
1025 let font = Font::new(data, 0).unwrap();
1026 let charmap = font.charmap();
1027 let codepoints = [65, 66, 67, 68, 69, 66];
1028 let mut actual = [GlyphId::new(123); 6];
1029 let mapped = charmap.map_unicode_batched(codepoints.iter().copied().zip(actual.iter_mut()));
1030 let expected = [1, 9, 3, 10, 0, 9].map(GlyphId::new);
1031 assert_eq!(actual, expected);
1032 assert_eq!(mapped, 4);
1033 for (codepoint, glyph) in codepoints.iter().zip(expected) {
1034 assert_eq!(
1035 charmap.map_unicode(*codepoint).unwrap_or(GlyphId::NOTDEF),
1036 glyph
1037 );
1038 }
1039 }
1040
1041 #[test]
1042 fn cmap15_selection_and_dmap_variants() {
1043 use font_test_data::cmap::{font_with_cmaps, format12, format14, format15, table};
1044 let nominal = format12(&[(65, 1)]);
1045 let old = format14(0xfe0f, &[], &[(65, 2), (67, 3)]);
1046 let new = format15(0xfe0f, &[65], &[(66, 70000), (68, 0xffffff)]);
1047 for records in [
1048 vec![
1049 (3, 10, nominal.as_slice()),
1050 (0, 5, old.as_slice()),
1051 (0, 5, new.as_slice()),
1052 ],
1053 vec![
1054 (3, 10, nominal.as_slice()),
1055 (0, 5, new.as_slice()),
1056 (0, 5, old.as_slice()),
1057 ],
1058 ] {
1059 let base = table(&records);
1060 let delta = table(&[(0, 5, old.as_slice())]);
1061 for (dmap, expected) in [
1062 (None, vec![(65, 1), (66, 70000), (68, 0xffffff)]),
1063 (
1064 Some(delta.as_slice()),
1065 vec![(65, 2), (66, 70000), (67, 3), (68, 0xffffff)],
1066 ),
1067 ] {
1068 let data = font_with_cmaps(Some(&base), dmap);
1069 let font = Font::new(data, 0).unwrap();
1070 let charmap = font.charmap();
1071 assert!(charmap.has_unicode_variants());
1072 let mut mappings: Vec<_> = charmap.unicode_variants().collect();
1073 mappings.sort_unstable();
1074 assert_eq!(
1075 mappings,
1076 expected
1077 .iter()
1078 .map(|&(ch, glyph)| (ch, 0xfe0f, GlyphId::new(glyph)))
1079 .collect::<Vec<_>>()
1080 );
1081 for (ch, selector, glyph) in mappings {
1082 assert_eq!(charmap.map_unicode_variant(ch, selector), Some(glyph));
1083 }
1084 }
1085 }
1086 let delta = table(&[(0, 5, new.as_slice())]);
1087 let data = font_with_cmaps(None, Some(&delta));
1088 let font = Font::new(data, 0).unwrap();
1089 assert_eq!(
1090 font.charmap().map_unicode_variant(66u32, 0xfe0fu32),
1091 Some(GlyphId::new(70000))
1092 );
1093 }
1094
1095 #[test]
1096 fn dmap_unicode_lookups_and_iterators() {
1097 use font_test_data::cmap::{font_with_cmaps, format12, format14, table};
1098 let base = table(&[
1099 (3, 10, &format12(&[(65, 1), (66, 2), (68, 5)])),
1100 (0, 5, &format14(0xfe0f, &[66], &[(65, 5), (67, 6)])),
1101 ]);
1102 let delta = table(&[
1103 (0, 4, &format12(&[(65, 3), (66, 0), (67, 4)])),
1104 (0, 5, &format14(0xfe0f, &[65], &[(66, 4), (68, 7)])),
1105 ]);
1106 let data = font_with_cmaps(Some(&base), Some(&delta));
1107 let font = Font::new(data, 0).unwrap();
1108 let charmap = font.charmap();
1109 assert!(charmap.has_unicode());
1110 assert!(charmap.has_unicode_variants());
1111 let mut mappings: Vec<_> = charmap.unicodes().collect();
1112 mappings.sort_unstable();
1113 assert_eq!(
1114 mappings,
1115 vec![
1116 (65, GlyphId::new(3)),
1117 (66, GlyphId::new(2)),
1118 (67, GlyphId::new(4)),
1119 (68, GlyphId::new(5))
1120 ]
1121 );
1122 for (ch, glyph) in mappings {
1123 assert_eq!(charmap.map_unicode(ch), Some(glyph));
1124 }
1125 for encoding in charmap.encodings() {
1126 assert_eq!(encoding.sfnt_index().unwrap().0, Tag::new(b"cmap"));
1127 assert_eq!(charmap.map_code(encoding, 65), Some(GlyphId::new(3)));
1128 let mut codes: Vec<_> = charmap.codes(encoding).collect();
1129 codes.sort_unstable();
1130 assert_eq!(
1131 codes,
1132 vec![
1133 (65, GlyphId::new(3)),
1134 (66, GlyphId::new(2)),
1135 (67, GlyphId::new(4)),
1136 (68, GlyphId::new(5))
1137 ]
1138 );
1139 }
1140 let mut variants: Vec<_> = charmap.unicode_variants().collect();
1141 variants.sort_unstable();
1142 assert_eq!(
1143 variants,
1144 vec![
1145 (65, 0xfe0f, GlyphId::new(3)),
1146 (66, 0xfe0f, GlyphId::new(4)),
1147 (67, 0xfe0f, GlyphId::new(6)),
1148 (68, 0xfe0f, GlyphId::new(7))
1149 ]
1150 );
1151 for (ch, selector, glyph) in variants {
1152 assert_eq!(charmap.map_unicode_variant(ch, selector), Some(glyph));
1153 }
1154 let data = font_with_cmaps(None, Some(&delta));
1155 let font = Font::new(data, 0).unwrap();
1156 assert!(font.charmap().has_unicode());
1157 assert_eq!(font.charmap().map_unicode('A'), Some(GlyphId::new(3)));
1158 assert_eq!(
1159 font.charmap().map_unicode_variant('A', 0xfe0fu32),
1160 Some(GlyphId::new(3))
1161 );
1162 let encoding = font.charmap().encodings().next().unwrap();
1163 assert_eq!(encoding.sfnt_index(), Some((Tag::new(b"DMAP"), 0)));
1165 assert_eq!(font.charmap().map_code(encoding, 65), Some(GlyphId::new(3)));
1166 }
1167
1168 #[test]
1169 fn standalone_cff_batch_matches_scalar_and_writes_after_a_miss() {
1170 let sfnt = FontRef::new(font_test_data::NOTO_SERIF_DISPLAY_TRIMMED).unwrap();
1171 let cff = sfnt.cff().unwrap().offset_data().as_bytes();
1172 let codepoints = ['i' as u32, 0x10ffff, 'i' as u32];
1173 check_unicode_batch(cff, &codepoints);
1174
1175 let font = Font::new(cff, 0).unwrap();
1176 let mut glyphs = [GlyphId::new(123); 3];
1177 let mapped = font
1178 .charmap()
1179 .map_unicode_batched(codepoints.into_iter().zip(glyphs.iter_mut()));
1180 #[cfg(feature = "agl")]
1181 {
1182 assert_eq!(mapped, 1);
1183 assert_eq!(glyphs, [GlyphId::new(1), GlyphId::NOTDEF, GlyphId::new(1)]);
1184 }
1185 #[cfg(not(feature = "agl"))]
1186 {
1187 assert_eq!(mapped, 0);
1188 assert_eq!(glyphs, [GlyphId::NOTDEF; 3]);
1189 }
1190 }
1191
1192 #[test]
1193 fn cid_cff_has_no_glyph_name_unicode_mapping() {
1194 let sfnt = FontRef::new(font_test_data::NOTO_SANS_JP_CFF).unwrap();
1195 let cff = sfnt.cff().unwrap().offset_data().as_bytes();
1196 let font = Font::new(cff, 0).unwrap();
1197 let charmap = font.charmap();
1198 assert!(!charmap.has_unicode());
1199 assert_eq!(charmap.map_unicode('A'), None);
1200 assert_eq!(charmap.encodings().count(), 0);
1201 assert_eq!(
1202 charmap.map_code(Encoding::standalone_cff_native(None), 65),
1203 None
1204 );
1205 let mut glyphs = [GlyphId::new(123); 2];
1206 assert_eq!(
1207 charmap.map_unicode_batched([0x41, 0x42].into_iter().zip(glyphs.iter_mut())),
1208 0
1209 );
1210 assert_eq!(glyphs, [GlyphId::NOTDEF; 2]);
1211 }
1212
1213 #[test]
1214 fn maps_a_selected_unicode_subtable() {
1215 let font = Font::new(font_test_data::VAZIRMATN_VAR, 0).unwrap();
1216 assert!(font.charmap().has_unicode());
1217 assert!(!font.charmap().unicode_is_symbol());
1218 assert!(!font.charmap().unicode_is_mac_roman());
1219 assert_eq!(font.charmap().map_unicode('A'), Some(GlyphId::new(1)));
1220 assert_eq!(
1221 font.charmap().map_unicode('A' as u32),
1222 Some(GlyphId::new(1))
1223 );
1224 assert_eq!(font.charmap().map_unicode('B'), None);
1225 assert_eq!(font.charmap().map_unicode(0xffff_u32), None);
1226 assert!(font
1227 .charmap()
1228 .unicodes()
1229 .any(|entry| entry == ('A' as u32, GlyphId::new(1))));
1230 }
1231
1232 #[test]
1233 fn selectable_mappings_are_fixed_size_and_keep_record_identity() {
1234 assert_eq!(core::mem::size_of::<Encoding>(), 12);
1235 assert_eq!(EncodingKind::Unicode as u32, u32::from_be_bytes(*b"unic"));
1236 assert_eq!(
1237 EncodingKind::AppleRoman as u32,
1238 u32::from_be_bytes(*b"armn")
1239 );
1240
1241 let data = font_test_data::VORG;
1242 let font = Font::new(data, 0).unwrap();
1243 let direct = FontRef::new(data).unwrap();
1244 let cmap = direct.cmap().unwrap();
1245 let encodings: Vec<_> = font.charmap().encodings().collect();
1246 for encoding in &encodings {
1247 let (tag, index) = encoding.sfnt_index().unwrap();
1248 assert_eq!(tag, Tag::new(b"cmap"));
1249 let index = index as usize;
1250 let record = &cmap.encoding_records()[index];
1251 assert_eq!(
1252 encoding.sfnt_ids(),
1253 Some((record.platform_id() as u16, record.encoding_id()))
1254 );
1255 let subtable = record.subtable(cmap.offset_data()).unwrap();
1256 assert_eq!(
1257 font.charmap().map_code(*encoding, 'A' as u32),
1258 subtable.map_codepoint('A')
1259 );
1260 for (code, glyph) in font.charmap().codes(*encoding).take(3) {
1261 assert_eq!(font.charmap().map_code(*encoding, code), Some(glyph));
1262 }
1263 }
1264 assert!(
1265 encodings
1266 .iter()
1267 .filter(|encoding| encoding.kind() == EncodingKind::Unicode)
1268 .count()
1269 > 1
1270 );
1271 assert!(encodings.windows(2).all(|pair| pair[0] != pair[1]));
1272 }
1273
1274 #[test]
1275 #[cfg(feature = "agl")]
1276 fn type1_exposes_unicode_and_native_mappings() {
1277 let font = Font::new(font_test_data::type1::NOTO_SERIF_REGULAR_SUBSET_PFB, 0).unwrap();
1278 let encodings: Vec<_> = font.charmap().encodings().collect();
1279 let unicode = encodings
1280 .iter()
1281 .find(|encoding| encoding.kind() == EncodingKind::Unicode)
1282 .unwrap();
1283 assert_eq!(unicode.sfnt_index(), None);
1284 assert_eq!(
1285 font.charmap().map_code(*unicode, 'H' as u32),
1286 font.charmap().map_unicode('H')
1287 );
1288 let native = encodings
1289 .iter()
1290 .find(|encoding| encoding.kind() != EncodingKind::Unicode)
1291 .unwrap();
1292 assert_eq!(native.sfnt_index(), None);
1293 assert_eq!(
1294 font.charmap().map_code(*native, b'H' as u32),
1295 Some(GlyphId::new(1))
1296 );
1297 assert_eq!(font.charmap().map_code(*native, 256), None);
1298 assert!(font
1299 .charmap()
1300 .unicodes()
1301 .any(|entry| entry == ('H' as u32, GlyphId::new(1))));
1302 assert!(font
1303 .charmap()
1304 .codes(*native)
1305 .any(|entry| entry == (b'H' as u32, GlyphId::new(1))));
1306 }
1307
1308 #[test]
1309 fn selected_subtables_are_cached_across_font_instances() {
1310 let cmap_reads = Arc::new(AtomicUsize::new(0));
1311 let count = cmap_reads.clone();
1312 let source: Arc<dyn Fn(Tag) -> Option<Blob> + Send + Sync> = Arc::new(move |tag| {
1313 if tag == Tag::new(b"cmap") {
1314 count.fetch_add(1, Ordering::Relaxed);
1315 }
1316 let font = FontRef::new(font_test_data::VAZIRMATN_VAR).ok()?;
1317 font.table_data(tag)
1318 .map(|data| Blob::from(data.as_bytes().to_vec()))
1319 });
1320 let font = Font::new(source, 0).unwrap();
1321 let charmap = font.charmap();
1322 assert_eq!(cmap_reads.load(Ordering::Relaxed), 0);
1323 assert_eq!(charmap.map_unicode('A'), Some(GlyphId::new(1)));
1324 let first_reads = cmap_reads.load(Ordering::Relaxed);
1325 assert!(first_reads > 0);
1326 assert_eq!(font.charmap().map_unicode('A'), Some(GlyphId::new(1)));
1327 assert_eq!(
1328 font.default_instance().charmap().map_unicode('A'),
1329 Some(GlyphId::new(1))
1330 );
1331 let varied = font
1332 .instance_builder()
1333 .variations([("wght", 500.0)])
1334 .build();
1335 assert!(!varied.normalized_coords().is_empty());
1336 assert_eq!(varied.charmap().map_unicode('A'), Some(GlyphId::new(1)));
1337 assert_eq!(cmap_reads.load(Ordering::Relaxed), first_reads);
1338 }
1339
1340 #[test]
1341 fn resolves_default_and_non_default_variants() {
1342 let font = Font::new(font_test_data::CMAP14_FONT1, 0).unwrap();
1343 assert!(font.charmap().has_unicode_variants());
1344 let selector = '\u{e0100}';
1345 assert_eq!(
1346 font.charmap().map_unicode_variant('\u{4e00}', selector),
1347 font.charmap().map_unicode('\u{4e00}')
1348 );
1349 assert_eq!(
1350 font.charmap().map_unicode_variant('\u{4e08}', selector),
1351 Some(GlyphId::new(25))
1352 );
1353 assert_eq!(
1354 font.charmap()
1355 .map_unicode_variant(0x4e08_u32, selector as u32),
1356 Some(GlyphId::new(25))
1357 );
1358 assert_eq!(font.charmap().map_unicode_variant('a', selector), None);
1359 let variants: Vec<_> = font.charmap().unicode_variants().collect();
1360 assert!(variants.contains(&(
1361 0x4e00,
1362 selector as u32,
1363 font.charmap().map_unicode(0x4e00_u32).unwrap()
1364 )));
1365 assert!(variants.contains(&(0x4e08, selector as u32, GlyphId::new(25))));
1366 }
1367
1368 #[test]
1369 fn symbol_encoding_falls_back_to_the_private_use_page() {
1370 let data = font_test_data::CMAP4_SYMBOL_PUA;
1371 let direct = FontRef::new(data).unwrap();
1372 let (_, _, subtable) = direct.cmap().unwrap().best_subtable().unwrap();
1373 let (codepoint, glyph) = (0xf000..=0xf0ff)
1374 .find_map(|codepoint| {
1375 subtable
1376 .map_codepoint(codepoint)
1377 .map(|glyph| (codepoint, glyph))
1378 })
1379 .unwrap();
1380 let character = char::from_u32(codepoint - 0xf000).unwrap();
1381 assert_eq!(subtable.map_codepoint(character), None);
1382 let font = Font::new(data, 0).unwrap();
1383 assert!(font.charmap().has_unicode());
1384 assert!(font.charmap().unicode_is_symbol());
1385 assert!(!font.charmap().unicode_is_mac_roman());
1386 assert_eq!(font.charmap().map_unicode(character), Some(glyph));
1387 let unicodes: Vec<_> = font.charmap().unicodes().collect();
1388 assert!(unicodes.contains(&(codepoint, glyph)));
1389 assert!(!unicodes.contains(&(character as u32, glyph)));
1390 let encoding = font.charmap().encodings().next().unwrap();
1391 assert_eq!(font.charmap().map_code(encoding, codepoint), Some(glyph));
1392 assert!(font
1393 .charmap()
1394 .codes(encoding)
1395 .any(|entry| entry == (codepoint, glyph)));
1396 }
1397
1398 #[test]
1399 fn symbol_encoding_retries_when_direct_mapping_is_notdef() {
1400 let mut cmap = vec![0, 0, 0, 1, 0, 3, 0, 0, 0, 0, 0, 12];
1401 cmap.extend_from_slice(&[0, 12, 0, 0]);
1403 cmap.extend_from_slice(&40_u32.to_be_bytes());
1404 cmap.extend_from_slice(&0_u32.to_be_bytes());
1405 cmap.extend_from_slice(&2_u32.to_be_bytes());
1406 for (codepoint, glyph) in [(0x41_u32, 0_u32), (0xf041, 3)] {
1407 cmap.extend_from_slice(&codepoint.to_be_bytes());
1408 cmap.extend_from_slice(&codepoint.to_be_bytes());
1409 cmap.extend_from_slice(&glyph.to_be_bytes());
1410 }
1411 let source: Arc<dyn Fn(Tag) -> Option<Blob> + Send + Sync> = Arc::new(move |tag| {
1412 if tag == Tag::new(b"cmap") {
1413 return Some(Blob::from(cmap.clone()));
1414 }
1415 let font = FontRef::new(font_test_data::TINOS_SUBSET).ok()?;
1416 font.table_data(tag)
1417 .map(|data| Blob::from(data.as_bytes().to_vec()))
1418 });
1419 let font = Font::new(source, 0).unwrap();
1420 let charmap = font.charmap();
1421 assert_eq!(charmap.map_unicode('A'), Some(GlyphId::new(3)));
1422 let mut batched = [GlyphId::NOTDEF; 3];
1423 let mapped = charmap.map_unicode_batched(
1424 ['A', 'B', 'A']
1425 .into_iter()
1426 .map(u32::from)
1427 .zip(batched.iter_mut()),
1428 );
1429 assert_eq!(batched, [GlyphId::new(3), GlyphId::NOTDEF, GlyphId::new(3)]);
1430 assert_eq!(mapped, 1);
1431 let unicodes: Vec<_> = charmap.unicodes().collect();
1432 assert!(unicodes.contains(&(0xf041, GlyphId::new(3))));
1433 assert!(!unicodes.iter().any(|(code, _)| *code == 0x41));
1434 let encoding = charmap.encodings().next().unwrap();
1435 assert_eq!(charmap.map_code(encoding, 0x41), None);
1436 assert!(!charmap.codes(encoding).any(|(code, _)| code == 0x41));
1437 }
1438
1439 fn font_with_format0_cmap(platform: u8, encoding: u8, glyphs: [u8; 256]) -> Font {
1444 let mut cmap = vec![0, 0, 0, 1, 0, platform, 0, encoding, 0, 0, 0, 12];
1445 cmap.extend_from_slice(&[0, 0, 1, 6, 0, 0]);
1446 cmap.extend_from_slice(&glyphs);
1447 let source: Arc<dyn Fn(Tag) -> Option<Blob> + Send + Sync> = Arc::new(move |tag| {
1448 if tag == Tag::new(b"cmap") {
1449 return Some(Blob::from(cmap.clone()));
1450 }
1451 let font = FontRef::new(font_test_data::TINOS_SUBSET).ok()?;
1452 font.table_data(tag)
1453 .map(|data| Blob::from(data.as_bytes().to_vec()))
1454 });
1455 Font::new(source, 0).unwrap()
1456 }
1457
1458 #[test]
1459 fn mac_roman_encoding_translates_unicode() {
1460 let mut glyphs = [0u8; 256];
1461 glyphs[0x8e] = 3; glyphs[0] = 5;
1463 let font = font_with_format0_cmap(1, 0, glyphs);
1464 assert!(font.charmap().has_unicode());
1465 assert!(!font.charmap().unicode_is_symbol());
1466 assert!(font.charmap().unicode_is_mac_roman());
1467 assert_eq!(font.charmap().map_unicode('é'), Some(GlyphId::new(3)));
1468 assert_eq!(font.charmap().map_unicode('A'), None);
1469 assert_eq!(font.charmap().map_unicode('\u{100}'), None);
1472 let mut batch = [GlyphId::new(9)];
1473 font.charmap()
1474 .map_unicode_batched([0x100u32].into_iter().zip(batch.iter_mut()));
1475 assert_eq!(batch, [GlyphId::NOTDEF]);
1476 assert!(font
1477 .charmap()
1478 .unicodes()
1479 .any(|entry| entry == ('é' as u32, GlyphId::new(3))));
1480 let encoding = font.charmap().encodings().next().unwrap();
1481 assert_eq!(
1482 font.charmap().map_code(encoding, 0x8e),
1483 Some(GlyphId::new(3))
1484 );
1485 assert!(font
1486 .charmap()
1487 .codes(encoding)
1488 .any(|entry| entry == (0x8e, GlyphId::new(3))));
1489 }
1490
1491 #[test]
1492 fn format0_mappings_are_listed_once() {
1493 let mut glyphs = [0u8; 256];
1494 glyphs[0x41] = 3;
1495 glyphs[0x42] = 4;
1496 let font = font_with_format0_cmap(3, 1, glyphs);
1497 assert!(!font.charmap().unicode_is_mac_roman());
1498 let mappings: Vec<_> = font.charmap().unicodes().collect();
1499 let mut unique = mappings.clone();
1500 unique.sort();
1501 unique.dedup();
1502 assert_eq!(mappings.len(), unique.len());
1503 assert!(mappings.contains(&(0x41, GlyphId::new(3))));
1504 assert!(mappings.contains(&(0x42, GlyphId::new(4))));
1505 }
1506
1507 #[test]
1508 #[cfg(feature = "agl")]
1509 fn type1_uses_its_unicode_charmap() {
1510 let font = Font::new(font_test_data::type1::NOTO_SERIF_REGULAR_SUBSET_PFB, 0).unwrap();
1511 assert!(font.charmap().has_unicode());
1512 assert!(!font.charmap().unicode_is_symbol());
1513 assert!(!font.charmap().unicode_is_mac_roman());
1514 assert!(!font.charmap().has_unicode_variants());
1515 assert_eq!(font.charmap().map_unicode('H'), Some(GlyphId::new(1)));
1516 assert_eq!(font.charmap().map_unicode('a'), None);
1517 assert_eq!(font.charmap().map_unicode_variant('H', '\u{fe0f}'), None);
1518 }
1519}