Skip to main content

harfrust/hb/
charmap.rs

1use crate::hb::tables::{SelectedCmapSubtable, TableRanges};
2
3use super::cache::hb_cache_t;
4use read_fonts::{
5    tables::cmap::{Cmap, Cmap14, CmapSubtable, MapVariant},
6    types::GlyphId,
7    FontRef, TableProvider,
8};
9
10pub type cache_t = hb_cache_t<21, 19, 256, 32>;
11
12#[derive(Clone)]
13pub struct Charmap<'a> {
14    subtable: Option<(SelectedCmapSubtable, CmapSubtable<'a>)>,
15    vs_subtable: Option<Cmap14<'a>>,
16}
17
18impl<'a> Charmap<'a> {
19    pub fn new(font: &FontRef<'a>, table_ranges: &TableRanges) -> Self {
20        if let Some(cmap) = table_ranges.cmap.resolve_table::<Cmap>(font) {
21            let data = cmap.offset_data();
22            let records = cmap.encoding_records();
23            let subtable = table_ranges
24                .cmap_subtable
25                .and_then(|s| Some((s, records.get(s.index as usize)?.subtable(data).ok()?)));
26            let vs_subtable = table_ranges
27                .cmap_vs_subtable
28                .and_then(|index| records.get(index as usize))
29                .and_then(|rec| rec.subtable(data).ok())
30                .and_then(|subtable| match subtable {
31                    CmapSubtable::Format14(table) => Some(table),
32                    _ => None,
33                });
34            Self {
35                subtable,
36                vs_subtable,
37            }
38        } else {
39            Self {
40                subtable: None,
41                vs_subtable: None,
42            }
43        }
44    }
45
46    pub fn from_tables(font: &impl TableProvider<'a>) -> Self {
47        if let Ok(cmap) = font.cmap() {
48            let subtable = if let Some((index, record, subtable)) = cmap.best_subtable() {
49                Some((
50                    SelectedCmapSubtable {
51                        index,
52                        is_mac_roman: record.is_mac_roman(),
53                        is_symbol: record.is_symbol(),
54                    },
55                    subtable,
56                ))
57            } else {
58                None
59            };
60            Self {
61                subtable,
62                vs_subtable: cmap.uvs_subtable().map(|(_, subtable)| subtable),
63            }
64        } else {
65            Self {
66                subtable: None,
67                vs_subtable: None,
68            }
69        }
70    }
71
72    pub fn map(&self, mut c: u32) -> Option<GlyphId> {
73        let subtable = self.subtable.as_ref()?;
74        if subtable.0.is_mac_roman && c > 0x7F {
75            c = unicode_to_macroman(c);
76        }
77        let result = match &subtable.1 {
78            CmapSubtable::Format0(table) => table.map_codepoint(c),
79            CmapSubtable::Format6(table) => table.map_codepoint(c),
80            CmapSubtable::Format10(table) => {
81                if let Some(index) = c.checked_sub(table.start_char_code()) {
82                    if index < table.num_chars() {
83                        table
84                            .glyph_id_array()
85                            .get(index as usize)
86                            .map(|gid| GlyphId::from(gid.get()))
87                    } else {
88                        None
89                    }
90                } else {
91                    None
92                }
93            }
94            CmapSubtable::Format4(table) => table.map_codepoint(c),
95            CmapSubtable::Format12(table) => table.map_codepoint(c),
96            CmapSubtable::Format13(table) => table.map_codepoint(c),
97            _ => None,
98        };
99        if result.is_none() && subtable.0.is_symbol && c <= 0x00FF {
100            // For symbol-encoded OpenType fonts, we duplicate the
101            // U+F000..F0FF range at U+0000..U+00FF.  That's what
102            // Windows seems to do, and that's hinted about at:
103            // https://docs.microsoft.com/en-us/typography/opentype/spec/recom
104            // under "Non-Standard (Symbol) Fonts".
105            return self.map(0xF000 + c);
106        }
107        result
108    }
109
110    pub fn map_variant(&self, c: u32, vs: u32) -> Option<GlyphId> {
111        let subtable = self.vs_subtable.as_ref()?;
112        match subtable.map_variant(c, vs)? {
113            MapVariant::UseDefault => self.map(c),
114            MapVariant::Variant(gid) => Some(gid),
115        }
116    }
117}
118
119#[rustfmt::skip]
120static UNICODE_TO_MACROMAN: &[u16] = &[
121    0x00C4, 0x00C5, 0x00C7, 0x00C9, 0x00D1, 0x00D6, 0x00DC, 0x00E1,
122    0x00E0, 0x00E2, 0x00E4, 0x00E3, 0x00E5, 0x00E7, 0x00E9, 0x00E8,
123    0x00EA, 0x00EB, 0x00ED, 0x00EC, 0x00EE, 0x00EF, 0x00F1, 0x00F3,
124    0x00F2, 0x00F4, 0x00F6, 0x00F5, 0x00FA, 0x00F9, 0x00FB, 0x00FC,
125    0x2020, 0x00B0, 0x00A2, 0x00A3, 0x00A7, 0x2022, 0x00B6, 0x00DF,
126    0x00AE, 0x00A9, 0x2122, 0x00B4, 0x00A8, 0x2260, 0x00C6, 0x00D8,
127    0x221E, 0x00B1, 0x2264, 0x2265, 0x00A5, 0x00B5, 0x2202, 0x2211,
128    0x220F, 0x03C0, 0x222B, 0x00AA, 0x00BA, 0x03A9, 0x00E6, 0x00F8,
129    0x00BF, 0x00A1, 0x00AC, 0x221A, 0x0192, 0x2248, 0x2206, 0x00AB,
130    0x00BB, 0x2026, 0x00A0, 0x00C0, 0x00C3, 0x00D5, 0x0152, 0x0153,
131    0x2013, 0x2014, 0x201C, 0x201D, 0x2018, 0x2019, 0x00F7, 0x25CA,
132    0x00FF, 0x0178, 0x2044, 0x20AC, 0x2039, 0x203A, 0xFB01, 0xFB02,
133    0x2021, 0x00B7, 0x201A, 0x201E, 0x2030, 0x00C2, 0x00CA, 0x00C1,
134    0x00CB, 0x00C8, 0x00CD, 0x00CE, 0x00CF, 0x00CC, 0x00D3, 0x00D4,
135    0xF8FF, 0x00D2, 0x00DA, 0x00DB, 0x00D9, 0x0131, 0x02C6, 0x02DC,
136    0x00AF, 0x02D8, 0x02D9, 0x02DA, 0x00B8, 0x02DD, 0x02DB, 0x02C7,
137];
138
139fn unicode_to_macroman(c: u32) -> u32 {
140    let u = c as u16;
141    let Some(index) = UNICODE_TO_MACROMAN.iter().position(|m| *m == u) else {
142        return 0;
143    };
144    (0x80 + index) as u32
145}