1use read_fonts::{
18 tables::cmap::{
19 self, Cmap, Cmap12, Cmap12Iter, Cmap13, Cmap13Iter, Cmap14, Cmap14Iter, Cmap4, Cmap4Iter,
20 CmapIterLimits, CmapSubtable, EncodingRecord, PlatformId,
21 },
22 types::GlyphId,
23 FontData, FontRef, TableProvider,
24};
25
26pub use read_fonts::tables::cmap::MapVariant;
27
28#[derive(Clone, Default)]
56pub struct Charmap<'a> {
57 codepoint_subtable: Option<CodepointSubtable<'a>>,
58 variant_subtable: Option<Cmap14<'a>>,
59 cmap_limits: CmapIterLimits,
60}
61
62impl<'a> Charmap<'a> {
63 pub fn new(font: &FontRef<'a>) -> Self {
65 let Ok(cmap) = font.cmap() else {
66 return Default::default();
67 };
68 let selection = MappingSelection::new(font, &cmap);
69 Self {
70 codepoint_subtable: selection
71 .codepoint_subtable
72 .map(|subtable| CodepointSubtable {
73 subtable,
74 is_symbol: selection.mapping_index.codepoint_subtable_is_symbol,
75 }),
76 variant_subtable: selection.variant_subtable,
77 cmap_limits: selection.mapping_index.cmap_limits,
78 }
79 }
80
81 pub fn has_map(&self) -> bool {
83 self.codepoint_subtable.is_some()
84 }
85
86 pub fn is_symbol(&self) -> bool {
88 self.codepoint_subtable
89 .as_ref()
90 .map(|x| x.is_symbol)
91 .unwrap_or(false)
92 }
93
94 pub fn has_variant_map(&self) -> bool {
96 self.variant_subtable.is_some()
97 }
98
99 pub fn map(&self, ch: impl Into<u32>) -> Option<GlyphId> {
103 self.codepoint_subtable.as_ref()?.map(ch.into())
104 }
105
106 pub fn mappings(&self) -> Mappings<'a> {
109 self.codepoint_subtable
110 .as_ref()
111 .map(|subtable| {
112 Mappings(match &subtable.subtable {
113 SupportedSubtable::Format4(cmap4) => MappingsInner::Format4(cmap4.iter()),
114 SupportedSubtable::Format12(cmap12) => {
115 MappingsInner::Format12(cmap12.iter_with_limits(self.cmap_limits))
116 }
117 SupportedSubtable::Format13(cmap13) => {
118 MappingsInner::Format13(cmap13.iter_with_limits(self.cmap_limits))
119 }
120 })
121 })
122 .unwrap_or(Mappings(MappingsInner::None))
123 }
124
125 pub fn map_variant(&self, ch: impl Into<u32>, selector: impl Into<u32>) -> Option<MapVariant> {
129 self.variant_subtable.as_ref()?.map_variant(ch, selector)
130 }
131
132 pub fn variant_mappings(&self) -> VariantMappings<'a> {
135 VariantMappings(
136 self.variant_subtable
137 .clone()
138 .map(|cmap14| cmap14.iter_with_limits(self.cmap_limits)),
139 )
140 }
141}
142
143#[derive(Copy, Clone, Default, Debug)]
151pub struct MappingIndex {
152 codepoint_subtable: Option<u16>,
154 codepoint_subtable_is_symbol: bool,
156 variant_subtable: Option<u16>,
158 cmap_limits: CmapIterLimits,
160}
161
162impl MappingIndex {
163 pub fn new(font: &FontRef) -> Self {
166 let Ok(cmap) = font.cmap() else {
167 return Default::default();
168 };
169 MappingSelection::new(font, &cmap).mapping_index
170 }
171
172 pub fn charmap<'a>(&self, font: &FontRef<'a>) -> Charmap<'a> {
177 let Ok(cmap) = font.cmap() else {
178 return Default::default();
179 };
180 let records = cmap.encoding_records();
181 let data = cmap.offset_data();
182 Charmap {
183 codepoint_subtable: self
184 .codepoint_subtable
185 .and_then(|index| get_subtable(data, records, index))
186 .and_then(SupportedSubtable::new)
187 .map(|subtable| CodepointSubtable {
188 subtable,
189 is_symbol: self.codepoint_subtable_is_symbol,
190 }),
191 variant_subtable: self
192 .variant_subtable
193 .and_then(|index| get_subtable(data, records, index))
194 .and_then(|subtable| match subtable {
195 CmapSubtable::Format14(cmap14) => Some(cmap14),
196 _ => None,
197 }),
198 cmap_limits: self.cmap_limits,
199 }
200 }
201}
202
203#[derive(Clone)]
208pub struct Mappings<'a>(MappingsInner<'a>);
209
210impl Iterator for Mappings<'_> {
211 type Item = (u32, GlyphId);
212
213 fn next(&mut self) -> Option<Self::Item> {
214 loop {
215 let item = match &mut self.0 {
216 MappingsInner::None => None,
217 MappingsInner::Format4(iter) => iter.next(),
218 MappingsInner::Format12(iter) => iter.next(),
219 MappingsInner::Format13(iter) => iter.next(),
220 }?;
221 if item.1 != GlyphId::NOTDEF {
222 return Some(item);
223 }
224 }
225 }
226}
227
228#[derive(Clone)]
229enum MappingsInner<'a> {
230 None,
231 Format4(Cmap4Iter<'a>),
232 Format12(Cmap12Iter<'a>),
233 Format13(Cmap13Iter<'a>),
234}
235
236#[derive(Clone)]
241pub struct VariantMappings<'a>(Option<Cmap14Iter<'a>>);
242
243impl Iterator for VariantMappings<'_> {
244 type Item = (u32, u32, MapVariant);
245
246 fn next(&mut self) -> Option<Self::Item> {
247 self.0.as_mut()?.next()
248 }
249}
250
251fn get_subtable<'a>(
252 data: FontData<'a>,
253 records: &[EncodingRecord],
254 index: u16,
255) -> Option<CmapSubtable<'a>> {
256 records
257 .get(index as usize)
258 .and_then(|record| record.subtable(data).ok())
259}
260
261#[derive(Clone)]
262struct CodepointSubtable<'a> {
263 subtable: SupportedSubtable<'a>,
264 is_symbol: bool,
266}
267
268impl CodepointSubtable<'_> {
269 fn map(&self, codepoint: u32) -> Option<GlyphId> {
270 self.map_impl(codepoint).or_else(|| {
271 if self.is_symbol && codepoint <= 0x00FF {
272 self.map_impl(codepoint + 0xF000)
280 } else {
281 None
282 }
283 })
284 }
285
286 fn map_impl(&self, codepoint: u32) -> Option<GlyphId> {
287 let gid = match &self.subtable {
288 SupportedSubtable::Format4(subtable) => subtable.map_codepoint(codepoint),
289 SupportedSubtable::Format12(subtable) => subtable.map_codepoint(codepoint),
290 SupportedSubtable::Format13(subtable) => subtable.map_codepoint(codepoint),
291 }?;
292 (gid != GlyphId::NOTDEF).then_some(gid)
293 }
294}
295
296#[derive(Clone)]
297enum SupportedSubtable<'a> {
298 Format4(Cmap4<'a>),
299 Format12(Cmap12<'a>),
300 Format13(Cmap13<'a>),
301}
302
303impl<'a> SupportedSubtable<'a> {
304 fn new(subtable: CmapSubtable<'a>) -> Option<Self> {
305 Some(match subtable {
306 CmapSubtable::Format4(cmap4) => Self::Format4(cmap4),
307 CmapSubtable::Format12(cmap12) => Self::Format12(cmap12),
308 CmapSubtable::Format13(cmap13) => Self::Format13(cmap13),
309 _ => return None,
310 })
311 }
312
313 fn from_cmap_record(cmap: &Cmap<'a>, record: &cmap::EncodingRecord) -> Option<Self> {
314 Self::new(record.subtable(cmap.offset_data()).ok()?)
315 }
316}
317
318#[derive(Copy, Clone, PartialEq, PartialOrd)]
323enum MappingKind {
324 None = 0,
325 UnicodeBmp = 1,
326 UnicodeFull = 2,
327 Symbol = 3,
328}
329
330struct MappingSelection<'a> {
338 mapping_index: MappingIndex,
341 codepoint_subtable: Option<SupportedSubtable<'a>>,
344 variant_subtable: Option<Cmap14<'a>>,
346}
347
348impl<'a> MappingSelection<'a> {
349 fn new(font: &FontRef<'a>, cmap: &Cmap<'a>) -> Self {
350 const ENCODING_MS_SYMBOL: u16 = 0;
351 const ENCODING_MS_UNICODE_CS: u16 = 1;
352 const ENCODING_APPLE_ID_UNICODE_32: u16 = 4;
353 const ENCODING_APPLE_ID_VARIANT_SELECTOR: u16 = 5;
354 const ENCODING_MS_ID_UCS_4: u16 = 10;
355 let mut mapping_index = MappingIndex::default();
356 let mut mapping_kind = MappingKind::None;
357 let mut codepoint_subtable = None;
358 let mut variant_subtable = None;
359 let mut maybe_choose_subtable = |kind, index, subtable| {
360 if kind > mapping_kind {
361 mapping_kind = kind;
362 mapping_index.codepoint_subtable_is_symbol = kind == MappingKind::Symbol;
363 mapping_index.codepoint_subtable = Some(index as u16);
364 codepoint_subtable = Some(subtable);
365 }
366 };
367 for (i, record) in cmap.encoding_records().iter().enumerate().rev() {
374 match (record.platform_id(), record.encoding_id()) {
375 (PlatformId::Unicode, ENCODING_APPLE_ID_VARIANT_SELECTOR) => {
376 if let Ok(CmapSubtable::Format14(subtable)) =
378 record.subtable(cmap.offset_data())
379 {
380 if variant_subtable.is_none() {
381 mapping_index.variant_subtable = Some(i as u16);
382 variant_subtable = Some(subtable);
383 }
384 }
385 }
386 (PlatformId::Windows, ENCODING_MS_SYMBOL) => {
387 if let Some(subtable) = SupportedSubtable::from_cmap_record(cmap, record) {
389 maybe_choose_subtable(MappingKind::Symbol, i, subtable);
390 }
391 }
392 (PlatformId::Windows, ENCODING_MS_ID_UCS_4)
393 | (PlatformId::Unicode, ENCODING_APPLE_ID_UNICODE_32) => {
394 if let Some(subtable) = SupportedSubtable::from_cmap_record(cmap, record) {
396 maybe_choose_subtable(MappingKind::UnicodeFull, i, subtable);
397 }
398 }
399 (PlatformId::ISO, _)
400 | (PlatformId::Unicode, _)
401 | (PlatformId::Windows, ENCODING_MS_UNICODE_CS) => {
402 if let Some(subtable) = SupportedSubtable::from_cmap_record(cmap, record) {
404 maybe_choose_subtable(MappingKind::UnicodeBmp, i, subtable);
405 }
406 }
407 _ => {}
408 }
409 }
410 mapping_index.cmap_limits = CmapIterLimits::default_for_font(font);
411 Self {
412 mapping_index,
413 codepoint_subtable,
414 variant_subtable,
415 }
416 }
417}
418
419#[cfg(test)]
420mod tests {
421 use super::*;
422 use crate::MetadataProvider;
423 use read_fonts::FontRef;
424
425 #[test]
426 fn choose_format_12_over_4() {
427 let font = FontRef::new(font_test_data::CMAP12_FONT1).unwrap();
428 let charmap = font.charmap();
429 assert!(matches!(
430 charmap.codepoint_subtable.unwrap().subtable,
431 SupportedSubtable::Format12(..)
432 ));
433 }
434
435 #[test]
436 fn choose_format_13_over_4() {
437 let font = FontRef::new(font_test_data::TOFU).unwrap();
438 let charmap = font.charmap();
439 assert!(matches!(
440 charmap.codepoint_subtable.unwrap().subtable,
441 SupportedSubtable::Format13(..)
442 ));
443 }
444
445 #[test]
446 fn choose_format_4() {
447 let font = FontRef::new(font_test_data::VAZIRMATN_VAR).unwrap();
448 let charmap = font.charmap();
449 assert!(matches!(
450 charmap.codepoint_subtable.unwrap().subtable,
451 SupportedSubtable::Format4(..)
452 ));
453 }
454
455 #[test]
456 fn choose_symbol() {
457 let font = FontRef::new(font_test_data::CMAP4_SYMBOL_PUA).unwrap();
458 let charmap = font.charmap();
459 assert!(charmap.is_symbol());
460 assert!(matches!(
461 charmap.codepoint_subtable.unwrap().subtable,
462 SupportedSubtable::Format4(..)
463 ));
464 }
465
466 #[test]
467 fn map_format_4() {
468 let font = FontRef::new(font_test_data::VAZIRMATN_VAR).unwrap();
469 let charmap = font.charmap();
470 assert_eq!(charmap.map('A'), Some(GlyphId::new(1)));
471 assert_eq!(charmap.map('À'), Some(GlyphId::new(2)));
472 assert_eq!(charmap.map('`'), Some(GlyphId::new(3)));
473 assert_eq!(charmap.map('B'), None);
474 }
475
476 #[test]
477 fn map_format_12() {
478 let font = FontRef::new(font_test_data::CMAP12_FONT1).unwrap();
479 let charmap = font.charmap();
480 assert_eq!(charmap.map(' '), None);
481 assert_eq!(charmap.map(0x101723_u32), Some(GlyphId::new(1)));
482 assert_eq!(charmap.map(0x101725_u32), Some(GlyphId::new(3)));
483 assert_eq!(charmap.map(0x102523_u32), Some(GlyphId::new(6)));
484 assert_eq!(charmap.map(0x102526_u32), Some(GlyphId::new(9)));
485 assert_eq!(charmap.map(0x102527_u32), Some(GlyphId::new(10)));
486 }
487
488 #[test]
489 fn map_format_13() {
490 let font = FontRef::new(font_test_data::TOFU).unwrap();
491 let charmap = font.charmap();
492 for ch in 2..0x7F_u32 {
494 assert_eq!(charmap.map(ch), Some(GlyphId::new(1)));
495 }
496 }
497
498 #[test]
499 fn map_symbol_pua() {
500 let font = FontRef::new(font_test_data::CMAP4_SYMBOL_PUA).unwrap();
501 let charmap = font.charmap();
502 assert!(charmap.codepoint_subtable.as_ref().unwrap().is_symbol);
503 assert_eq!(charmap.map(0xF001_u32), Some(GlyphId::new(1)));
504 assert_eq!(charmap.map(0xF002_u32), Some(GlyphId::new(2)));
505 assert_eq!(charmap.map(0xF003_u32), Some(GlyphId::new(3)));
506 assert_eq!(charmap.map(0xF0FE_u32), Some(GlyphId::new(4)));
507 assert_eq!(charmap.map(0x1_u32), Some(GlyphId::new(1)));
510 assert_eq!(charmap.map(0x2_u32), Some(GlyphId::new(2)));
511 assert_eq!(charmap.map(0x3_u32), Some(GlyphId::new(3)));
512 assert_eq!(charmap.map(0xFE_u32), Some(GlyphId::new(4)));
513 }
514
515 #[test]
516 fn map_variants() {
517 use super::MapVariant::*;
518 let font = FontRef::new(font_test_data::CMAP14_FONT1).unwrap();
519 let charmap = font.charmap();
520 let selector = '\u{e0100}';
521 assert_eq!(charmap.map_variant('a', selector), None);
522 assert_eq!(charmap.map_variant('\u{4e00}', selector), Some(UseDefault));
523 assert_eq!(charmap.map_variant('\u{4e06}', selector), Some(UseDefault));
524 assert_eq!(
525 charmap.map_variant('\u{4e08}', selector),
526 Some(Variant(GlyphId::new(25)))
527 );
528 assert_eq!(
529 charmap.map_variant('\u{4e09}', selector),
530 Some(Variant(GlyphId::new(26)))
531 );
532 }
533
534 #[test]
535 fn mappings() {
536 for font_data in [
537 font_test_data::VAZIRMATN_VAR,
538 font_test_data::CMAP12_FONT1,
539 font_test_data::SIMPLE_GLYF,
540 font_test_data::CMAP4_SYMBOL_PUA,
541 font_test_data::TOFU,
542 ] {
543 let font = FontRef::new(font_data).unwrap();
544 let charmap = font.charmap();
545 for (codepoint, glyph_id) in charmap.mappings() {
546 assert_ne!(
547 glyph_id,
548 GlyphId::NOTDEF,
549 "we should never encounter notdef glyphs"
550 );
551 assert_eq!(charmap.map(codepoint), Some(glyph_id));
552 }
553 }
554 }
555
556 #[test]
557 fn variant_mappings() {
558 let font = FontRef::new(font_test_data::CMAP14_FONT1).unwrap();
559 let charmap = font.charmap();
560 for (codepoint, selector, variant) in charmap.variant_mappings() {
561 assert_eq!(charmap.map_variant(codepoint, selector), Some(variant));
562 }
563 }
564}