font_map_core/raw/ttf/
cmap.rs

1#![allow(clippy::cast_possible_wrap)]
2use super::PlatformType;
3use crate::error::ParseResult;
4use crate::reader::{BinaryReader, Parse};
5
6/// CMAP table data  
7/// Contains only the subset of the table needed for mapping unicode codepoints to glyph indices
8#[derive(Debug, Default)]
9pub struct CmapTable {
10    /// Mapping from glyph indices to unicode codepoints
11    pub mappings: Vec<u32>,
12
13    /// Raw Subtables
14    pub tables: Vec<CmapSubtable>,
15}
16
17impl CmapTable {
18    /// Returns the unicode codepoint for the given glyph index
19    #[must_use]
20    pub fn get_codepoint(&self, index: u16) -> Option<u32> {
21        if index as usize >= self.mappings.len() {
22            None
23        } else {
24            Some(self.mappings[index as usize])
25        }
26    }
27}
28
29impl Parse for CmapTable {
30    fn parse(reader: &mut BinaryReader) -> ParseResult<Self> {
31        let mut table = Self::default();
32
33        //
34        // Table header
35        reader.skip_u16()?; // version
36        let num_tables = reader.read_u16()?;
37
38        //
39        // Subtables
40        for _ in 0..num_tables {
41            let platform_id = reader.read_u16()?;
42            let encoding_id = reader.read_u16()?;
43            let offset = reader.read_u32()?;
44
45            debug_msg!(
46                "  CMAP subtable: platform={}, encoding={}, offset={}",
47                platform_id,
48                encoding_id,
49                offset
50            );
51
52            let mut subtable_reader = reader.clone();
53            subtable_reader.advance_to(offset as usize)?;
54            let mut subtable = CmapSubtable::parse(&mut subtable_reader)?;
55            subtable.platform = platform_id.into();
56            subtable.encoding = encoding_id;
57
58            for (idx, cde) in &subtable.mappings {
59                let idx = *idx as usize;
60                if table.mappings.len() <= idx {
61                    table.mappings.resize(idx + 1, 0xFFFF);
62                }
63                table.mappings[idx] = *cde;
64            }
65            table.tables.push(subtable);
66        }
67
68        Ok(table)
69    }
70}
71
72/// An individual CMAP subtable
73#[derive(Debug, Default)]
74pub struct CmapSubtable {
75    /// Platform ID
76    pub platform: PlatformType,
77
78    /// Encoding type
79    pub encoding: u16,
80
81    /// Mappings from glyph indices to unicode codepoints
82    pub mappings: Vec<(u16, u32)>,
83}
84
85impl Parse for CmapSubtable {
86    #[allow(clippy::too_many_lines)]
87    fn parse(reader: &mut BinaryReader) -> ParseResult<Self> {
88        let fmt = reader.read_u16()?;
89
90        let mut subtable = Self::default();
91        debug_msg!("  CMAP format: {}", fmt);
92
93        match fmt {
94            0 => {
95                //
96                // Format 0 CMAP tables are simple 1:1 mappings
97                reader.skip_u16()?; // length
98                reader.skip_u16()?; // language
99
100                for codepoint in 0u32..=0xFF {
101                    let glyph_index = u16::from(reader.read_u8()?);
102                    subtable.mappings.push((glyph_index, codepoint));
103                }
104            }
105
106            4 => {
107                //
108                // Format 4 CMAP tables are segmented mappings
109                reader.skip_u16()?; // length
110                reader.skip_u16()?; // language
111
112                let mut seg_count = reader.read_u16()?;
113                seg_count /= 2;
114
115                reader.skip_u16()?; // search range
116                reader.skip_u16()?; // entry selector
117                reader.skip_u16()?; // range shift
118
119                let mut end_code = Vec::with_capacity(seg_count as usize);
120                for _ in 0..seg_count {
121                    end_code.push(reader.read_u16()?);
122                }
123
124                reader.skip_u16()?; // reserved pad
125
126                let mut start_code = Vec::with_capacity(seg_count as usize);
127                for _ in 0..seg_count {
128                    start_code.push(reader.read_u16()?);
129                }
130
131                let mut id_delta = Vec::with_capacity(seg_count as usize);
132                for _ in 0..seg_count {
133                    id_delta.push(reader.read_u16()?);
134                }
135
136                for i in 0..seg_count as usize {
137                    let id_range_offset = reader.read_u16()?;
138
139                    for codepoint in start_code[i]..=end_code[i] {
140                        if codepoint == 0xFFFF {
141                            subtable.mappings.push((0, 0xFFFF));
142                            break;
143                        }
144
145                        let glyph_index = if id_range_offset == 0 {
146                            //
147                            // Simple mapping
148                            codepoint.wrapping_add(id_delta[i])
149                        } else {
150                            //
151                            // Indexed mapping
152                            //  let index_offset = id_range_offset / 2 + (codepoint - start_code[i]);
153
154                            let index_offset =
155                                id_range_offset + 2 * (codepoint - start_code[i]) - 2;
156
157                            let mut glyph_reader = reader.clone();
158                            glyph_reader.advance_by(index_offset as isize)?;
159
160                            let glyph_index = glyph_reader.read_u16()?;
161                            if glyph_index != 0 {
162                                glyph_index.wrapping_add(id_delta[i])
163                            } else {
164                                glyph_index
165                            }
166                        };
167
168                        subtable.mappings.push((glyph_index, u32::from(codepoint)));
169                    }
170                }
171            }
172
173            6 => {
174                reader.skip_u16()?; // len
175                reader.skip_u16()?; // lang
176
177                let first_code = reader.read_u16()?;
178                let entry_count = reader.read_u16()?;
179
180                debug_msg!(
181                    "  CMAP format 6: first_code={}, entry_count={}",
182                    first_code,
183                    entry_count
184                );
185
186                for i in 0..u32::from(entry_count) {
187                    let glyph_index = reader.read_u16()?;
188                    let codepoint = u32::from(first_code) + i;
189                    subtable.mappings.push((glyph_index, codepoint));
190                }
191            }
192
193            12 => {
194                //
195                // Format 12 CMAP tables are segmented mappings
196                reader.skip_u16()?; // reserved
197                reader.skip_u32()?; // len
198                reader.skip_u32()?; // lang
199                let num_groups = reader.read_u32()?;
200
201                debug_msg!("  CMAP format 12: num_groups={}", num_groups);
202
203                for _ in 0..num_groups {
204                    let start = reader.read_u32()?;
205                    let end = reader.read_u32()?;
206                    let start_glyph = reader.read_u32()?; // Glyph index corresponding to the starting character code
207
208                    debug_msg!(
209                        "  CMAP group: start={}, end={}, start_glyph={}",
210                        start,
211                        end,
212                        start_glyph
213                    );
214
215                    let adj = if start < end { 1 } else { -1 };
216
217                    let n = start.abs_diff(end);
218                    let mut codepoint = start;
219                    for i in 0..n {
220                        let index = u16::try_from(start_glyph + i).unwrap_or_default();
221                        subtable.mappings.push((index, codepoint));
222                        codepoint = codepoint.wrapping_add_signed(adj);
223                    }
224                }
225            }
226
227            _ => return Err(reader.err(&format!("Unsupported CMAP format: {fmt}"))),
228        }
229
230        debug_msg!("  Found {} mappings", subtable.mappings.len());
231        Ok(subtable)
232    }
233}