1
//! Parsing and writing of the `cmap` table.
2
//!
3
//! > This table defines the mapping of character codes to the glyph index values used in the font.
4
//! > It may contain more than one subtable, in order to support more than one character encoding
5
//! > scheme.
6
//!
7
//! — <https://docs.microsoft.com/en-us/typography/opentype/spec/cmap>
8

            
9
pub(crate) mod subset;
10

            
11
use std::collections::HashMap;
12

            
13

            
14
use crate::binary::read::{ReadArray, ReadArrayIter, ReadBinary, ReadCtxt, ReadFrom, ReadScope};
15
use crate::binary::write::{WriteBinary, WriteContext};
16
use crate::binary::{I16Be, U16Be, U32Be, U8};
17
use crate::error::{ParseError, WriteError};
18
use crate::size;
19
use crate::SafeFrom;
20

            
21
use self::owned::CmapSubtable as OwnedCmapSubtable;
22

            
23
const SUB_HEADER_SIZE: usize = 4 * 2;
24

            
25
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
26
pub struct PlatformId(pub u16);
27

            
28
impl PlatformId {
29
    pub const UNICODE: PlatformId = PlatformId(0);
30
    pub const MACINTOSH: PlatformId = PlatformId(1);
31
    pub const WINDOWS: PlatformId = PlatformId(3);
32
    pub const CUSTOM: PlatformId = PlatformId(4);
33
}
34

            
35
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
36
pub struct EncodingId(pub u16);
37

            
38
impl EncodingId {
39
    pub const WINDOWS_SYMBOL: EncodingId = EncodingId(0);
40
    pub const WINDOWS_UNICODE_BMP_UCS2: EncodingId = EncodingId(1);
41
    pub const WINDOWS_SHIFT_JIS: EncodingId = EncodingId(2);
42
    pub const WINDOWS_PRC: EncodingId = EncodingId(3);
43
    pub const WINDOWS_BIG5: EncodingId = EncodingId(4);
44
    pub const WINDOWS_WANSUNG: EncodingId = EncodingId(5);
45
    pub const WINDOWS_JOHAB: EncodingId = EncodingId(6);
46
    // pub const WINDOWS_RESERVED: EncodingId = EncodingId(7);
47
    // pub const WINDOWS_RESERVED: EncodingId = EncodingId(8);
48
    // pub const WINDOWS_RESERVED: EncodingId = EncodingId(9);
49
    pub const WINDOWS_UNICODE_UCS4: EncodingId = EncodingId(10);
50

            
51
    pub const MACINTOSH_APPLE_ROMAN: EncodingId = EncodingId(0);
52
    pub const MACINTOSH_UNICODE_UCS4: EncodingId = EncodingId(4);
53

            
54
    /// Unicode 2.0 and onwards semantics, Unicode BMP only
55
    pub const UNICODE_BMP: EncodingId = EncodingId(3);
56
    /// Unicode 2.0 and onwards semantics, Unicode full repertoire
57
    pub const UNICODE_FULL: EncodingId = EncodingId(4);
58
}
59

            
60
pub struct Cmap<'a> {
61
    pub scope: ReadScope<'a>,
62
    encoding_records: ReadArray<'a, EncodingRecord>,
63
}
64

            
65
#[derive(Copy, Clone, Debug)]
66
pub struct EncodingRecord {
67
    pub platform_id: PlatformId,
68
    pub encoding_id: EncodingId,
69
    pub offset: u32,
70
}
71

            
72
pub enum CmapSubtable<'a> {
73
    Format0 {
74
        language: u16,
75
        glyph_id_array: ReadArray<'a, U8>,
76
    },
77
    Format2 {
78
        language: u16,
79
        sub_header_keys: ReadArray<'a, U16Be>,
80
        sub_headers: ReadArray<'a, SubHeader>,
81
        sub_headers_scope: ReadScope<'a>,
82
    },
83
    Format4(CmapSubtableFormat4<'a>),
84
    Format6 {
85
        language: u16,
86
        first_code: u16,
87
        glyph_id_array: ReadArray<'a, U16Be>,
88
    },
89
    Format10 {
90
        language: u32,
91
        start_char_code: u32,
92
        glyph_id_array: ReadArray<'a, U16Be>,
93
    },
94
    Format12 {
95
        language: u32,
96
        groups: ReadArray<'a, SequentialMapGroup>,
97
    },
98
}
99

            
100
// cmap subtable format 2 sub-header
101
#[derive(Debug, Copy, Clone, PartialEq, PartialOrd, Hash)]
102
pub struct SubHeader {
103
    first_code: u16,
104
    entry_count: u16,
105
    id_delta: i16,
106
    id_range_offset: u16,
107
}
108

            
109
#[derive(Debug, Clone)]
110
pub struct CmapSubtableFormat4<'a> {
111
    pub language: u16,
112
    pub end_codes: ReadArray<'a, U16Be>,
113
    pub start_codes: ReadArray<'a, U16Be>,
114
    pub id_deltas: ReadArray<'a, I16Be>,
115
    pub id_range_offsets: ReadArray<'a, U16Be>,
116
    pub glyph_id_array: ReadArray<'a, U16Be>,
117
}
118

            
119
#[derive(Debug, Copy, Clone, PartialEq, PartialOrd, Hash)]
120
struct Format4Calculator {
121
    seg_count: u16,
122
}
123

            
124
#[derive(Debug, Copy, Clone, PartialEq, PartialOrd, Hash)]
125
pub struct SequentialMapGroup {
126
    pub(crate) start_char_code: u32,
127
    pub(crate) end_char_code: u32,
128
    pub(crate) start_glyph_id: u32,
129
}
130

            
131
impl ReadBinary for Cmap<'_> {
132
    type HostType<'a> = Cmap<'a>;
133

            
134
22464
    fn read<'a>(ctxt: &mut ReadCtxt<'a>) -> Result<Self::HostType<'a>, ParseError> {
135
22464
        let scope = ctxt.scope();
136
22464
        let version = ctxt.read_u16be()?;
137
22464
        ctxt.check(version == 0)?;
138
22464
        let num_tables = usize::from(ctxt.read_u16be()?);
139
22464
        let encoding_records = ctxt.read_array::<EncodingRecord>(num_tables)?;
140
22464
        Ok(Cmap {
141
22464
            scope,
142
22464
            encoding_records,
143
22464
        })
144
22464
    }
145
}
146

            
147
impl ReadFrom for EncodingRecord {
148
    type ReadType = (U16Be, U16Be, U32Be);
149
102114
    fn read_from((platform_id, encoding_id, offset): (u16, u16, u32)) -> Self {
150
102114
        EncodingRecord {
151
102114
            platform_id: PlatformId(platform_id),
152
102114
            encoding_id: EncodingId(encoding_id),
153
102114
            offset,
154
102114
        }
155
102114
    }
156
}
157

            
158
impl ReadBinary for CmapSubtable<'_> {
159
    type HostType<'a> = CmapSubtable<'a>;
160

            
161
22464
    fn read<'a>(ctxt: &mut ReadCtxt<'a>) -> Result<Self::HostType<'a>, ParseError> {
162
22464
        let subtable_format = ctxt.read_u16be()?;
163
22464
        match subtable_format {
164
            0 => {
165
                let length = usize::from(ctxt.read_u16be()?);
166
                ctxt.check(length >= 3 * size::U16 + 256)?;
167
                let language = ctxt.read_u16be()?;
168
                let glyph_id_array = ctxt.read_array::<U8>(256)?;
169
                Ok(CmapSubtable::Format0 {
170
                    language,
171
                    glyph_id_array,
172
                })
173
            }
174
            2 => {
175
                let _length = usize::from(ctxt.read_u16be()?);
176
                let language = ctxt.read_u16be()?;
177
                let sub_header_keys = ctxt.read_array::<U16Be>(256)?;
178

            
179
                // value is subHeader index × 8.
180
                // NOTE(unwrap): Safe because sub_header_keys has a non-zero length
181
                let max_sub_header_index =
182
                    sub_header_keys.iter().map(|value| value / 8).max().unwrap();
183
                let sub_headers_scope = ctxt.scope();
184
                let sub_headers =
185
                    ctxt.read_array::<SubHeader>(usize::from(max_sub_header_index) + 1)?;
186

            
187
                Ok(CmapSubtable::Format2 {
188
                    language,
189
                    sub_header_keys,
190
                    sub_headers,
191
                    sub_headers_scope,
192
                })
193
            }
194
            4 => {
195
20844
                let length = usize::from(ctxt.read_u16be()?);
196
20844
                let language = ctxt.read_u16be()?;
197
20844
                let seg_count_x2 = usize::from(ctxt.read_u16be()?);
198
20844
                ctxt.check((seg_count_x2 & 1) == 0)?;
199
20844
                let seg_count = seg_count_x2 >> 1;
200
20844
                let _search_range = ctxt.read_u16be()?;
201
20844
                let _entry_selector = ctxt.read_u16be()?;
202
20844
                let _range_shift = ctxt.read_u16be()?;
203
20844
                let end_codes = ctxt.read_array::<U16Be>(seg_count)?;
204
20844
                let _reserved_pad = ctxt.read_u16be()?;
205
20844
                let start_codes = ctxt.read_array::<U16Be>(seg_count)?;
206
20844
                let id_deltas = ctxt.read_array::<I16Be>(seg_count)?;
207
20844
                let id_range_offsets = ctxt.read_array::<U16Be>(seg_count)?;
208
20844
                ctxt.check(length >= (8 + (4 * seg_count)) * size::U16)?;
209
20844
                let remaining = length - ((8 + (4 * seg_count)) * size::U16);
210
20844
                let num_indices = remaining / 2;
211
20844
                let glyph_id_array = ctxt.read_array::<U16Be>(num_indices)?;
212
20844
                Ok(CmapSubtable::Format4(CmapSubtableFormat4 {
213
20844
                    language,
214
20844
                    end_codes,
215
20844
                    start_codes,
216
20844
                    id_deltas,
217
20844
                    id_range_offsets,
218
20844
                    glyph_id_array,
219
20844
                }))
220
            }
221
            6 => {
222
                let _length = ctxt.read_u16be()?;
223
                let language = ctxt.read_u16be()?;
224
                let first_code = ctxt.read_u16be()?;
225
                let entry_count = usize::from(ctxt.read_u16be()?);
226
                let glyph_id_array = ctxt.read_array::<U16Be>(entry_count)?;
227
                Ok(CmapSubtable::Format6 {
228
                    language,
229
                    first_code,
230
                    glyph_id_array,
231
                })
232
            }
233
            10 => {
234
                let reserved = ctxt.read_u16be()?;
235
                ctxt.check(reserved == 0)?;
236
                let _length = ctxt.read_u32be()?;
237
                let language = ctxt.read_u32be()?;
238
                let start_char_code = ctxt.read_u32be()?;
239
                let num_chars = usize::try_from(ctxt.read_u32be()?)?;
240
                let glyph_id_array = ctxt.read_array::<U16Be>(num_chars)?;
241
                Ok(CmapSubtable::Format10 {
242
                    language,
243
                    start_char_code,
244
                    glyph_id_array,
245
                })
246
            }
247
            12 => {
248
1620
                let reserved = ctxt.read_u16be()?;
249
1620
                ctxt.check(reserved == 0)?;
250
1620
                let _length = ctxt.read_u32be()?;
251
1620
                let language = ctxt.read_u32be()?;
252
1620
                let num_groups = usize::try_from(ctxt.read_u32be()?)?;
253
1620
                let groups = ctxt.read_array::<SequentialMapGroup>(num_groups)?;
254
1620
                Ok(CmapSubtable::Format12 { language, groups })
255
            }
256
            _ => Err(ParseError::BadVersion),
257
        }
258
22464
    }
259
}
260

            
261
impl<'a> WriteBinary<&Self> for CmapSubtable<'a> {
262
    type Output = ();
263

            
264
    fn write<C: WriteContext>(ctxt: &mut C, table: &CmapSubtable<'a>) -> Result<(), WriteError> {
265
        match table {
266
            CmapSubtable::Format0 {
267
                language,
268
                glyph_id_array,
269
            } => {
270
                U16Be::write(ctxt, 0u16)?; // format
271
                U16Be::write(ctxt, u16::try_from(3 * size::U16 + glyph_id_array.len())?)?; // length
272
                U16Be::write(ctxt, *language)?;
273
                <&ReadArray<'_, _>>::write(ctxt, glyph_id_array)?;
274
            }
275
            CmapSubtable::Format2 { .. } => {
276
                // Not implemented for now. Format 2 is rarely seen in the wild and would generally
277
                // not be generated for a subset font (the most common path for font writing)
278
                return Err(WriteError::NotImplemented);
279
            }
280
            CmapSubtable::Format4(CmapSubtableFormat4 {
281
                language,
282
                end_codes,
283
                start_codes,
284
                id_deltas,
285
                id_range_offsets,
286
                glyph_id_array,
287
            }) => {
288
                let start = ctxt.bytes_written();
289
                let calc = Format4Calculator {
290
                    seg_count: u16::try_from(start_codes.len())?,
291
                };
292

            
293
                U16Be::write(ctxt, 4u16)?; // format
294
                let length = ctxt.placeholder::<U16Be, _>()?;
295
                U16Be::write(ctxt, *language)?;
296
                U16Be::write(ctxt, calc.seg_count_x2())?;
297
                U16Be::write(ctxt, calc.search_range())?;
298
                U16Be::write(ctxt, calc.entry_selector())?;
299
                U16Be::write(ctxt, calc.range_shift())?;
300
                <&ReadArray<'_, _>>::write(ctxt, end_codes)?;
301
                U16Be::write(ctxt, 0u16)?; // reserved_pad
302
                <&ReadArray<'_, _>>::write(ctxt, start_codes)?;
303
                <&ReadArray<'_, _>>::write(ctxt, id_deltas)?;
304
                <&ReadArray<'_, _>>::write(ctxt, id_range_offsets)?;
305
                <&ReadArray<'_, _>>::write(ctxt, glyph_id_array)?;
306
                ctxt.write_placeholder(length, u16::try_from(ctxt.bytes_written() - start)?)?;
307
            }
308
            CmapSubtable::Format6 {
309
                language,
310
                first_code,
311
                glyph_id_array,
312
            } => {
313
                let start = ctxt.bytes_written();
314

            
315
                U16Be::write(ctxt, 6u16)?; // format
316
                let length = ctxt.placeholder::<U16Be, _>()?;
317
                U16Be::write(ctxt, *language)?;
318
                U16Be::write(ctxt, *first_code)?;
319
                U16Be::write(ctxt, u16::try_from(glyph_id_array.len())?)?;
320
                <&ReadArray<'_, _>>::write(ctxt, glyph_id_array)?;
321
                ctxt.write_placeholder(length, u16::try_from(ctxt.bytes_written() - start)?)?;
322
            }
323
            CmapSubtable::Format10 {
324
                language,
325
                start_char_code,
326
                glyph_id_array,
327
            } => {
328
                let start = ctxt.bytes_written();
329

            
330
                U16Be::write(ctxt, 10u16)?; // format
331
                U16Be::write(ctxt, 0u16)?; // reserved
332
                let length = ctxt.placeholder::<U32Be, _>()?;
333
                U32Be::write(ctxt, *language)?;
334
                U32Be::write(ctxt, *start_char_code)?;
335
                U32Be::write(ctxt, u32::try_from(glyph_id_array.len())?)?;
336
                <&ReadArray<'_, _>>::write(ctxt, glyph_id_array)?;
337
                ctxt.write_placeholder(length, u32::try_from(ctxt.bytes_written() - start)?)?;
338
            }
339
            CmapSubtable::Format12 { language, groups } => {
340
                let start = ctxt.bytes_written();
341

            
342
                U16Be::write(ctxt, 12u16)?; // format
343
                U16Be::write(ctxt, 0u16)?; // reserved
344
                let length = ctxt.placeholder::<U32Be, _>()?;
345
                U32Be::write(ctxt, *language)?;
346
                U32Be::write(ctxt, u32::try_from(groups.len())?)?;
347
                <&ReadArray<'_, _>>::write(ctxt, groups)?;
348
                ctxt.write_placeholder(length, u32::try_from(ctxt.bytes_written() - start)?)?;
349
            }
350
        }
351

            
352
        Ok(())
353
    }
354
}
355

            
356
impl<'b> Format4 for &CmapSubtableFormat4<'b> {
357
    type U16Iter = ReadArrayIter<'b, U16Be>;
358
    type I16Iter = ReadArrayIter<'b, I16Be>;
359

            
360
    fn end_codes(self) -> Self::U16Iter {
361
        self.end_codes.iter()
362
    }
363

            
364
    fn start_codes(self) -> Self::U16Iter {
365
        self.start_codes.iter()
366
    }
367

            
368
    fn id_deltas(self) -> Self::I16Iter {
369
        self.id_deltas.iter()
370
    }
371

            
372
    fn id_range_offsets(self) -> Self::U16Iter {
373
        self.id_range_offsets.iter()
374
    }
375

            
376
    fn glyph_id_array_get(self, index: usize) -> Result<u16, ParseError> {
377
        self.glyph_id_array
378
            .get_item(index)
379
            .ok_or(ParseError::BadIndex)
380
    }
381
}
382

            
383
impl Format4Calculator {
384
    fn seg_count_x2(self) -> u16 {
385
        2 * self.seg_count
386
    }
387

            
388
    fn search_range(self) -> u16 {
389
        2 * (2u16.pow((self.seg_count as f64).log2().floor() as u32))
390
    }
391

            
392
    fn entry_selector(self) -> u16 {
393
        (self.search_range() as f64 / 2.).log2() as u16
394
    }
395

            
396
    fn range_shift(self) -> u16 {
397
        2 * self.seg_count - self.search_range()
398
    }
399
}
400

            
401
impl ReadFrom for SubHeader {
402
    type ReadType = ((U16Be, U16Be), (I16Be, U16Be));
403
    fn read_from(
404
        ((first_code, entry_count), (id_delta, id_range_offset)): ((u16, u16), (i16, u16)),
405
    ) -> Self {
406
        SubHeader {
407
            first_code,
408
            entry_count,
409
            id_delta,
410
            id_range_offset,
411
        }
412
    }
413
}
414

            
415
impl SubHeader {
416
    fn contains(&self, value: u16) -> bool {
417
        (self.first_code..self.first_code + self.entry_count).contains(&value)
418
    }
419

            
420
    fn glyph_index_sub_array<'a>(
421
        &self,
422
        index: usize,
423
        sub_headers_scope: &ReadScope<'a>,
424
    ) -> Result<ReadArray<'a, U16Be>, ParseError> {
425
        let sub_header_offset = index * SUB_HEADER_SIZE;
426

            
427
        let glyph_index_sub_array = if self.entry_count > 0 {
428
            let entry_count = usize::from(self.entry_count);
429
            // The value of the idRangeOffset is the number of bytes past the actual
430
            // location of the idRangeOffset word where the glyphIndexArray element
431
            // corresponding to firstCode appears.
432
            // https://docs.microsoft.com/en-us/typography/opentype/spec/cmap#format-2-high-byte-mapping-through-table
433
            //
434
            // So we take the size of the SubHeader and subtract 2 for the size of the
435
            // id_range_offset field.
436
            let first_glyph_index_offset =
437
                sub_header_offset + SUB_HEADER_SIZE - 2 + usize::from(self.id_range_offset);
438
            sub_headers_scope
439
                .offset(first_glyph_index_offset)
440
                .ctxt()
441
                .read_array::<U16Be>(entry_count)?
442
        } else {
443
            ReadArray::empty()
444
        };
445

            
446
        Ok(glyph_index_sub_array)
447
    }
448
}
449

            
450
impl ReadFrom for SequentialMapGroup {
451
    type ReadType = (U32Be, U32Be, U32Be);
452
506952
    fn read_from((start_char_code, end_char_code, start_glyph_id): (u32, u32, u32)) -> Self {
453
506952
        SequentialMapGroup {
454
506952
            start_char_code,
455
506952
            end_char_code,
456
506952
            start_glyph_id,
457
506952
        }
458
506952
    }
459
}
460

            
461
impl WriteBinary for SequentialMapGroup {
462
    type Output = ();
463

            
464
    fn write<C: WriteContext>(ctxt: &mut C, group: SequentialMapGroup) -> Result<(), WriteError> {
465
        U32Be::write(ctxt, group.start_char_code)?;
466
        U32Be::write(ctxt, group.end_char_code)?;
467
        U32Be::write(ctxt, group.start_glyph_id)?;
468

            
469
        Ok(())
470
    }
471
}
472

            
473
impl<'a> Cmap<'a> {
474
    /// Find the first encoding record for the given `platform_id`
475
    pub fn find_subtable_for_platform(&self, platform_id: PlatformId) -> Option<EncodingRecord> {
476
        self.encoding_records
477
            .iter()
478
            .find(|record| record.platform_id == platform_id)
479
    }
480

            
481
    /// Find the first encoding record for the given `platform_id` and `encoding_id`
482
43308
    pub fn find_subtable(
483
43308
        &self,
484
43308
        platform_id: PlatformId,
485
43308
        encoding_id: EncodingId,
486
43308
    ) -> Option<EncodingRecord> {
487
43308
        self.encoding_records
488
43308
            .iter()
489
102114
            .find(|record| record.platform_id == platform_id && record.encoding_id == encoding_id)
490
43308
    }
491

            
492
    pub fn encoding_records(&self) -> impl Iterator<Item = EncodingRecord> + 'a {
493
        self.encoding_records.iter()
494
    }
495
}
496

            
497
impl CmapSubtable<'_> {
498
    // NOTE: `owned::CmapSubtable` contains a duplicate of this
499
    pub fn map_glyph(&self, ch: u32) -> Result<Option<u16>, ParseError> {
500
        match *self {
501
            CmapSubtable::Format0 {
502
                ref glyph_id_array, ..
503
            } => {
504
                let index = usize::safe_from(ch);
505
                Ok(glyph_id_array.get_item(index).map(u16::from))
506
            }
507
            CmapSubtable::Format2 {
508
                ref sub_header_keys,
509
                ref sub_headers,
510
                ref sub_headers_scope,
511
                ..
512
            } => {
513
                let high_byte = ((ch >> 8) & 0xff) as u8;
514
                let low_byte = ((ch) & 0xff) as u8;
515

            
516
                let header_index_byte = {
517
                    let low_byte = usize::from(low_byte);
518
                    let low_byte_index = sub_header_keys
519
                        .get_item(low_byte)
520
                        .ok_or(ParseError::BadIndex)?;
521

            
522
                    if high_byte == 0 && low_byte_index == 0 {
523
                        low_byte
524
                    } else {
525
                        usize::from(high_byte)
526
                    }
527
                };
528

            
529
                // value is subHeader index × 8.
530
                let sub_header_key = sub_header_keys
531
                    .get_item(header_index_byte)
532
                    .ok_or(ParseError::BadIndex)
533
                    .map(|key| usize::from(key / 8))?;
534
                let sub_header = sub_headers
535
                    .get_item(sub_header_key)
536
                    .ok_or(ParseError::BadIndex)?;
537

            
538
                if !sub_header.contains(u16::from(low_byte)) {
539
                    return Ok(Some(0));
540
                }
541
                let glyph_id_index = u16::from(low_byte) - sub_header.first_code;
542

            
543
                let glyph_index_sub_array =
544
                    sub_header.glyph_index_sub_array(sub_header_key, sub_headers_scope)?;
545
                let mut glyph_id = glyph_index_sub_array
546
                    .get_item(usize::from(glyph_id_index))
547
                    .ok_or(ParseError::BadIndex)?;
548

            
549
                if glyph_id != 0 {
550
                    // The idDelta arithmetic is modulo 65536.
551
                    glyph_id = ((glyph_id as isize + sub_header.id_delta as isize) & 0xffff) as u16
552
                }
553

            
554
                Ok(Some(glyph_id))
555
            }
556
            CmapSubtable::Format4(ref format4) => format4.map_glyph(ch),
557
            CmapSubtable::Format6 {
558
                first_code,
559
                ref glyph_id_array,
560
                ..
561
            } => {
562
                let first_code = u32::from(first_code);
563
                if first_code <= ch {
564
                    let index = usize::safe_from(ch - first_code);
565
                    Ok(glyph_id_array.get_item(index))
566
                } else {
567
                    Ok(None)
568
                }
569
            }
570
            CmapSubtable::Format10 {
571
                start_char_code,
572
                ref glyph_id_array,
573
                ..
574
            } => {
575
                if ch >= start_char_code {
576
                    let index = usize::safe_from(ch - start_char_code);
577
                    Ok(glyph_id_array.get_item(index))
578
                } else {
579
                    Ok(None)
580
                }
581
            }
582
            CmapSubtable::Format12 { ref groups, .. } => {
583
                for group in groups {
584
                    if group.start_char_code <= ch && ch <= group.end_char_code {
585
                        let glyph_id = group.start_glyph_id + (ch - group.start_char_code);
586
                        return Ok(Some(u16::try_from(glyph_id)?));
587
                    }
588
                }
589
                Ok(None)
590
            }
591
        }
592
    }
593

            
594
    /// Returns an `owned::CmapSubtable`. Will only return `None` if `self` is
595
    /// `CmapSubtable::Format2` as support for converting from this format is not yet implemented.
596
22464
    pub fn to_owned(&self) -> Option<OwnedCmapSubtable> {
597
22464
        match self {
598
            CmapSubtable::Format0 {
599
                language,
600
                glyph_id_array,
601
            } => Some(OwnedCmapSubtable::Format0 {
602
                language: *language,
603
                glyph_id_array: {
604
                    let mut uninitialized = [0_u8; 256];
605
                    for (target, source) in uninitialized.iter_mut().zip(glyph_id_array.iter()) {
606
                        *target = source;
607
                    }
608
                    Box::new(uninitialized)
609
                },
610
            }),
611
            // It's unlikely that a sub-table using format 2 would be selected for mappings as most
612
            // fonts that contain format 2 would probably contain a platform/encoding combination
613
            // that uses a different format, which would be selected first. As a result support
614
            // for it is not yet implemented.
615
            CmapSubtable::Format2 { .. } => None,
616
            CmapSubtable::Format4(CmapSubtableFormat4 {
617
20844
                language,
618
20844
                end_codes,
619
20844
                start_codes,
620
20844
                id_deltas,
621
20844
                id_range_offsets,
622
20844
                glyph_id_array,
623
20844
            }) => Some(OwnedCmapSubtable::Format4(owned::CmapSubtableFormat4 {
624
20844
                language: *language,
625
20844
                end_codes: end_codes.to_vec(),
626
20844
                start_codes: start_codes.to_vec(),
627
20844
                id_deltas: id_deltas.to_vec(),
628
20844
                id_range_offsets: id_range_offsets.to_vec(),
629
20844
                glyph_id_array: glyph_id_array.to_vec(),
630
20844
            })),
631
            CmapSubtable::Format6 {
632
                language,
633
                first_code,
634
                glyph_id_array,
635
            } => Some(OwnedCmapSubtable::Format6 {
636
                language: *language,
637
                first_code: *first_code,
638
                glyph_id_array: glyph_id_array.to_vec(),
639
            }),
640
            CmapSubtable::Format10 {
641
                language,
642
                start_char_code,
643
                glyph_id_array,
644
            } => Some(OwnedCmapSubtable::Format10 {
645
                language: *language,
646
                start_char_code: *start_char_code,
647
                glyph_id_array: glyph_id_array.to_vec(),
648
            }),
649
1620
            CmapSubtable::Format12 { language, groups } => {
650
1620
                Some(OwnedCmapSubtable::Format12(owned::CmapSubtableFormat12 {
651
1620
                    language: *language,
652
1620
                    groups: groups.to_vec(),
653
1620
                }))
654
            }
655
        }
656
22464
    }
657

            
658
    /// Extract all the mappings from the sub-table.
659
    ///
660
    /// The returned `HashMap` maps glyph indexes to char codes. If more than one char code maps to
661
    /// the same glyph, the `HashMap` will contain the **first** mapping encountered. Also note that
662
    /// the char code is not necessarily Unicode. It depends on the encoding of the cmap
663
    /// sub-table.
664
    ///
665
    /// This method primarily exists to support [GlyphNames](crate::glyph_info::GlyphNames).
666
    pub fn mappings(&self) -> Result<HashMap<u16, u32>, ParseError> {
667
        let mut mappings = HashMap::with_capacity(self.size_hint());
668
        self.mappings_fn(|ch, gid| {
669
            mappings.entry(gid).or_insert(ch);
670
        })?;
671
        Ok(mappings)
672
    }
673

            
674
    /// Extract all the mappings from the sub-table.
675
    pub fn mappings_fn(&self, mut callback: impl FnMut(u32, u16)) -> Result<(), ParseError> {
676
        match self {
677
            CmapSubtable::Format0 {
678
                language: _,
679
                glyph_id_array,
680
            } => {
681
                for (ch, gid) in glyph_id_array.iter().enumerate() {
682
                    // cast is safe as format 0 can only contain 256 glyphs
683
                    callback(ch as u32, u16::from(gid))
684
                }
685
            }
686
            CmapSubtable::Format2 {
687
                sub_header_keys,
688
                sub_headers,
689
                sub_headers_scope,
690
                ..
691
            } => {
692
                for high_byte in 0u8..=255 {
693
                    // value is subHeader index × 8.
694
                    let sub_header_key = sub_header_keys
695
                        .get_item(usize::from(high_byte))
696
                        .map(|val| usize::from(val / 8))
697
                        .ok_or(ParseError::BadIndex)?;
698
                    let sub_header = sub_headers
699
                        .get_item(sub_header_key)
700
                        .ok_or(ParseError::BadIndex)?;
701

            
702
                    // TODO: Reduce duplication
703
                    if sub_header_key == 0 {
704
                        if !sub_header.contains(u16::from(high_byte)) {
705
                            continue; // .notdef
706
                        }
707

            
708
                        let glyph_id_index = u16::from(high_byte) - sub_header.first_code;
709

            
710
                        let glyph_index_sub_array =
711
                            sub_header.glyph_index_sub_array(sub_header_key, sub_headers_scope)?;
712
                        let mut glyph_id = glyph_index_sub_array
713
                            .get_item(usize::from(glyph_id_index))
714
                            .ok_or(ParseError::BadIndex)?;
715

            
716
                        if glyph_id != 0 {
717
                            // The idDelta arithmetic is modulo 65536.
718
                            glyph_id =
719
                                ((glyph_id as isize + sub_header.id_delta as isize) & 0xffff) as u16
720
                        }
721
                        callback(u32::from(high_byte), glyph_id);
722
                    } else {
723
                        for glyph_id_index in 0..sub_header.entry_count {
724
                            // FIXME: u8/u16
725
                            let low_byte = glyph_id_index + sub_header.first_code;
726

            
727
                            let glyph_index_sub_array = sub_header
728
                                .glyph_index_sub_array(sub_header_key, sub_headers_scope)?;
729
                            let mut glyph_id = glyph_index_sub_array
730
                                .get_item(usize::from(glyph_id_index))
731
                                .ok_or(ParseError::BadIndex)?;
732

            
733
                            if glyph_id != 0 {
734
                                // The idDelta arithmetic is modulo 65536.
735
                                glyph_id = ((glyph_id as isize + sub_header.id_delta as isize)
736
                                    & 0xffff) as u16
737
                            }
738
                            callback((u32::from(high_byte) << 8) | u32::from(low_byte), glyph_id);
739
                        }
740
                    }
741
                }
742
            }
743
            CmapSubtable::Format4(format4) => format4.mappings_fn(callback)?,
744
            CmapSubtable::Format6 {
745
                language: _,
746
                first_code,
747
                glyph_id_array,
748
            } => {
749
                for (index, gid) in glyph_id_array.iter().enumerate() {
750
                    // cast is safe as the entryCount of the glyphIdArray is a 16-bit value
751
                    callback(u32::from(*first_code) + index as u32, gid)
752
                }
753
            }
754
            CmapSubtable::Format10 {
755
                language: _,
756
                start_char_code,
757
                glyph_id_array,
758
            } => {
759
                for (index, gid) in glyph_id_array.iter().enumerate() {
760
                    let index = u32::try_from(index)?;
761
                    callback(*start_char_code + index, gid)
762
                }
763
            }
764
            CmapSubtable::Format12 { groups, .. } => {
765
                for record in groups.iter() {
766
                    for (i, ch) in (record.start_char_code..=record.end_char_code).enumerate() {
767
                        callback(
768
                            ch,
769
                            u16::try_from(record.start_glyph_id)? + u16::try_from(i)?,
770
                        )
771
                    }
772
                }
773
            }
774
        }
775

            
776
        Ok(())
777
    }
778

            
779
    /// A hint as to the number of mappings contained in this sub-table.
780
    ///
781
    /// For some formats it will be the exact size, for others it will be underestimated.
782
    pub(crate) fn size_hint(&self) -> usize {
783
        match self {
784
            CmapSubtable::Format0 { glyph_id_array, .. } => glyph_id_array.len(),
785
            CmapSubtable::Format2 { .. } => 0, // TODO: Implement if needed in mappings_fn
786
            CmapSubtable::Format4(CmapSubtableFormat4 { glyph_id_array, .. }) => {
787
                glyph_id_array.len()
788
            }
789
            CmapSubtable::Format6 { glyph_id_array, .. } => glyph_id_array.len(),
790
            CmapSubtable::Format10 { glyph_id_array, .. } => glyph_id_array.len(),
791
            CmapSubtable::Format12 { groups, .. } => groups
792
                .iter()
793
                .map(|group| {
794
                    let start_char_code = group.start_char_code as usize;
795
                    let end_char_code = group.end_char_code as usize;
796
                    end_char_code.saturating_sub(start_char_code)
797
                })
798
                .sum(),
799
        }
800
    }
801
}
802

            
803
trait Format4 {
804
    type U16Iter: ExactSizeIterator<Item = u16>;
805
    type I16Iter: ExactSizeIterator<Item = i16>;
806

            
807
    fn end_codes(self) -> Self::U16Iter;
808
    fn start_codes(self) -> Self::U16Iter;
809
    fn id_deltas(self) -> Self::I16Iter;
810
    fn id_range_offsets(self) -> Self::U16Iter;
811
    fn glyph_id_array_get(self, index: usize) -> Result<u16, ParseError>;
812

            
813
269082
    fn map_glyph(self, ch: u32) -> Result<Option<u16>, ParseError>
814
269082
    where
815
269082
        Self: Sized + Copy,
816
    {
817
        // Format 4 sub-tables can only map a 16-bit character range
818
269082
        let ch = u16::try_from(ch)?;
819
269082
        let zipped = self
820
269082
            .start_codes()
821
269082
            .zip(self.end_codes())
822
269082
            .zip(self.id_deltas())
823
269082
            .zip(self.id_range_offsets())
824
3973428
            .map(|(((start, end), delta), offset)| (start, end, delta, offset));
825
3973428
        for (i, (start_code, end_code, id_delta, id_range_offset)) in zipped.enumerate() {
826
            // Find segment that contains `ch`
827
3973428
            if start_code <= ch && ch <= end_code {
828
                // This segment contains ch
829
268866
                let glyph_id = self.glyph_id_for_id_range_offset(
830
268866
                    id_range_offset,
831
268866
                    ch,
832
268866
                    id_delta,
833
268866
                    i,
834
268866
                    ch - start_code,
835
                )?;
836
268866
                return Ok(Some(glyph_id));
837
3704562
            }
838
        }
839
216
        Ok(None)
840
269082
    }
841

            
842
    fn mappings_fn(self, mut callback: impl FnMut(u32, u16)) -> Result<(), ParseError>
843
    where
844
        Self: Sized + Copy,
845
    {
846
        let zipped = self
847
            .start_codes()
848
            .zip(self.end_codes())
849
            .zip(self.id_deltas())
850
            .zip(self.id_range_offsets())
851
            .map(|(((start, end), delta), offset)| (start, end, delta, offset));
852
        for (i, (start_code, end_code, id_delta, id_range_offset)) in zipped.enumerate() {
853
            for (offset_from_start, ch) in (start_code..=end_code).enumerate() {
854
                let glyph_id = self.glyph_id_for_id_range_offset(
855
                    id_range_offset,
856
                    ch,
857
                    id_delta,
858
                    i,
859
                    offset_from_start as u16,
860
                )?;
861
                callback(u32::from(ch), glyph_id)
862
            }
863
        }
864

            
865
        Ok(())
866
    }
867

            
868
268866
    fn glyph_id_for_id_range_offset(
869
268866
        self,
870
268866
        mut id_range_offset: u16,
871
268866
        ch: u16,
872
268866
        id_delta: i16,
873
268866
        segment_index: usize,
874
268866
        start_code_offset: u16,
875
268866
    ) -> Result<u16, ParseError>
876
268866
    where
877
268866
        Self: Sized + Copy,
878
    {
879
        // Work around Fontographer bug
880
        // https://github.com/adobe-type-tools/afdko/blob/01a35dacc9e8d1735b7f752f3232d38c34e6f843/c/shared/source/ttread/ttread.c#L1885
881
268866
        if id_range_offset == 0xFFFF {
882
            id_range_offset = 0;
883
268866
        }
884

            
885
268866
        if id_range_offset == 0 {
886
            // If the idRangeOffset is 0, the idDelta value is added directly to
887
            // the character code offset (i.e. idDelta[i] + c) to get the
888
            // corresponding glyph index. The idDelta arithmetic is modulo 65536.
889
73332
            Ok(((i32::from(ch) + i32::from(id_delta)) & 0xFFFF) as u16)
890
        } else {
891
195534
            let index = offset_to_index(
892
195534
                segment_index,
893
195534
                id_range_offset,
894
195534
                start_code_offset,
895
195534
                self.id_range_offsets().len(),
896
            )?;
897
195534
            let glyph_id = self.glyph_id_array_get(index)?;
898
            // If the value obtained from the indexing operation is not 0 (which indicates
899
            // missingGlyph), idDelta[i] is added to it to get the glyph index.
900
195534
            if glyph_id == 0 {
901
                return Ok(0);
902
195534
            }
903
195534
            Ok(((i32::from(glyph_id) + i32::from(id_delta)) & 0xFFFF) as u16)
904
        }
905
268866
    }
906
}
907

            
908
// For converting cmap format 4 offsets to indexes into the glyph id array.
909
195534
fn offset_to_index(
910
195534
    i: usize,
911
195534
    id_range_offset: u16,
912
195534
    start_code_offset: u16,
913
195534
    id_range_offsets_len: usize,
914
195534
) -> Result<usize, ParseError> {
915
    // Offset into `id_range_offsets` that `i` represents, * 2 for 16-bit values in the array.
916
    // cast is safe as i is segment index, which is a u16
917
195534
    let offset_in_id_range_offsets = u32::from(id_range_offset) + i as u32 * 2;
918
    // Offset into `glyph_id_array` is offset from `start_code` of `ch` * 2 for 16-bit glyph ids.
919
195534
    let glyph_id_offset = offset_in_id_range_offsets + u32::from(start_code_offset) * 2;
920
    // Bounds check, cast is safe as id_range_offsets has max segCount items, a 16-bit value.
921
195534
    if glyph_id_offset >= id_range_offsets_len as u32 * 2 && (glyph_id_offset & 1) == 0 {
922
        // Turn the offsets into an index
923
195534
        Ok(((glyph_id_offset >> 1) as usize) - id_range_offsets_len)
924
    } else {
925
        Err(ParseError::BadIndex)
926
    }
927
195534
}
928

            
929
pub mod owned {
930
    use super::{
931
        size, EncodingId, Format4, Format4Calculator, I16Be, ParseError, PlatformId,
932
        SequentialMapGroup, U16Be, U32Be, WriteBinary, WriteContext, WriteError,
933
    };
934

            
935
    #[derive(Debug, Clone, PartialEq)]
936
    pub struct Cmap {
937
        pub encoding_records: Vec<EncodingRecord>,
938
    }
939

            
940
    #[derive(Debug, Clone, PartialEq)]
941
    pub struct EncodingRecord {
942
        pub platform_id: PlatformId,
943
        pub encoding_id: EncodingId,
944
        pub sub_table: CmapSubtable,
945
    }
946

            
947
    #[derive(Debug, Clone, PartialEq)]
948
    pub enum CmapSubtable {
949
        Format0 {
950
            language: u16,
951
            glyph_id_array: Box<[u8; 256]>,
952
        },
953
        Format4(CmapSubtableFormat4),
954
        Format6 {
955
            language: u16,
956
            first_code: u16,
957
            glyph_id_array: Vec<u16>,
958
        },
959
        Format10 {
960
            language: u32,
961
            start_char_code: u32,
962
            glyph_id_array: Vec<u16>,
963
        },
964
        Format12(CmapSubtableFormat12),
965
    }
966

            
967
    #[derive(Debug, Clone, PartialEq)]
968
    pub struct CmapSubtableFormat4 {
969
        pub language: u16,
970
        pub end_codes: Vec<u16>,
971
        pub start_codes: Vec<u16>,
972
        pub id_deltas: Vec<i16>,
973
        pub id_range_offsets: Vec<u16>,
974
        pub glyph_id_array: Vec<u16>,
975
    }
976

            
977
    #[derive(Debug, Clone, PartialEq)]
978
    pub struct CmapSubtableFormat12 {
979
        pub language: u32,
980
        pub groups: Vec<SequentialMapGroup>,
981
    }
982

            
983
    impl CmapSubtable {
984
287604
        pub fn map_glyph(&self, ch: u32) -> Result<Option<u16>, ParseError> {
985
            // NOTE: Currently a duplicate of `super::CmapSubtable::map_glyph`
986
287604
            match *self {
987
                CmapSubtable::Format0 {
988
                    ref glyph_id_array, ..
989
                } => {
990
                    let index = usize::try_from(ch)?;
991
                    if index < glyph_id_array.len() {
992
                        let glyph_id = glyph_id_array[index];
993
                        Ok(Some(u16::from(glyph_id)))
994
                    } else {
995
                        Ok(None)
996
                    }
997
                }
998
269082
                CmapSubtable::Format4(ref format4) => format4.map_glyph(ch),
999
                CmapSubtable::Format6 {
                    first_code,
                    ref glyph_id_array,
                    ..
                } => {
                    let first_code = u32::from(first_code);
                    if first_code <= ch {
                        let index = usize::try_from(ch - first_code)?;
                        if index < glyph_id_array.len() {
                            let glyph_id = glyph_id_array[index];
                            Ok(Some(glyph_id))
                        } else {
                            Ok(None)
                        }
                    } else {
                        Ok(None)
                    }
                }
                CmapSubtable::Format10 {
                    start_char_code,
                    ref glyph_id_array,
                    ..
                } => {
                    if ch >= start_char_code {
                        let index = usize::try_from(ch - start_char_code)?;
                        if index < glyph_id_array.len() {
                            let glyph_id = glyph_id_array[index];
                            Ok(Some(glyph_id))
                        } else {
                            Ok(None)
                        }
                    } else {
                        Ok(None)
                    }
                }
18522
                CmapSubtable::Format12(CmapSubtableFormat12 { ref groups, .. }) => {
2697084
                    for group in groups {
2695734
                        if group.start_char_code <= ch && ch <= group.end_char_code {
17172
                            let glyph_id = group.start_glyph_id + (ch - group.start_char_code);
17172
                            return Ok(Some(u16::try_from(glyph_id)?));
2678562
                        }
                    }
1350
                    Ok(None)
                }
            }
287604
        }
    }
    impl<'a> Format4 for &'a CmapSubtableFormat4 {
        type U16Iter = std::iter::Copied<std::slice::Iter<'a, u16>>;
        type I16Iter = std::iter::Copied<std::slice::Iter<'a, i16>>;
269082
        fn end_codes(self) -> Self::U16Iter {
269082
            self.end_codes.iter().copied()
269082
        }
269082
        fn start_codes(self) -> Self::U16Iter {
269082
            self.start_codes.iter().copied()
269082
        }
269082
        fn id_deltas(self) -> Self::I16Iter {
269082
            self.id_deltas.iter().copied()
269082
        }
464616
        fn id_range_offsets(self) -> Self::U16Iter {
464616
            self.id_range_offsets.iter().copied()
464616
        }
195534
        fn glyph_id_array_get(self, index: usize) -> Result<u16, ParseError> {
195534
            self.glyph_id_array
195534
                .get(index)
195534
                .copied()
195534
                .ok_or(ParseError::BadIndex)
195534
        }
    }
    impl WriteBinary<Self> for Cmap {
        type Output = ();
        fn write<C: WriteContext>(ctxt: &mut C, table: Cmap) -> Result<(), WriteError> {
            let start = ctxt.bytes_written();
            U16Be::write(ctxt, 0u16)?; // version
            U16Be::write(ctxt, u16::try_from(table.encoding_records.len())?)?;
            // encoding records
            let mut offsets = Vec::with_capacity(table.encoding_records.len());
            for record in &table.encoding_records {
                U16Be::write(ctxt, record.platform_id.0)?;
                U16Be::write(ctxt, record.encoding_id.0)?;
                let offset = ctxt.placeholder::<U32Be, _>()?;
                offsets.push(offset);
            }
            // sub-tables
            for (record, placeholder) in table.encoding_records.into_iter().zip(offsets.into_iter())
            {
                let offset = u32::try_from(ctxt.bytes_written() - start)?;
                CmapSubtable::write(ctxt, record.sub_table)?;
                ctxt.write_placeholder(placeholder, offset)?;
            }
            Ok(())
        }
    }
    impl WriteBinary<Self> for CmapSubtable {
        type Output = ();
        fn write<C: WriteContext>(ctxt: &mut C, table: CmapSubtable) -> Result<(), WriteError> {
            match table {
                CmapSubtable::Format0 {
                    language,
                    glyph_id_array,
                } => {
                    U16Be::write(ctxt, 0u16)?; // format
                    U16Be::write(ctxt, u16::try_from(3 * size::U16 + glyph_id_array.len())?)?; // length
                    U16Be::write(ctxt, language)?;
                    ctxt.write_bytes(glyph_id_array.as_ref())?;
                }
                CmapSubtable::Format4(CmapSubtableFormat4 {
                    language,
                    end_codes,
                    start_codes,
                    id_deltas,
                    id_range_offsets,
                    glyph_id_array,
                }) => {
                    let start = ctxt.bytes_written();
                    let calc = Format4Calculator {
                        seg_count: u16::try_from(start_codes.len())?,
                    };
                    U16Be::write(ctxt, 4u16)?; // format
                    let length = ctxt.placeholder::<U16Be, _>()?;
                    U16Be::write(ctxt, language)?;
                    U16Be::write(ctxt, calc.seg_count_x2())?;
                    U16Be::write(ctxt, calc.search_range())?;
                    U16Be::write(ctxt, calc.entry_selector())?;
                    U16Be::write(ctxt, calc.range_shift())?;
                    ctxt.write_vec::<U16Be, _>(end_codes)?;
                    U16Be::write(ctxt, 0u16)?; // reserved_pad
                    ctxt.write_vec::<U16Be, _>(start_codes)?;
                    ctxt.write_vec::<I16Be, _>(id_deltas)?;
                    ctxt.write_vec::<U16Be, _>(id_range_offsets)?;
                    ctxt.write_vec::<U16Be, _>(glyph_id_array)?;
                    ctxt.write_placeholder(length, u16::try_from(ctxt.bytes_written() - start)?)?;
                }
                CmapSubtable::Format6 {
                    language,
                    first_code,
                    glyph_id_array,
                } => {
                    let start = ctxt.bytes_written();
                    U16Be::write(ctxt, 6u16)?; // format
                    let length = ctxt.placeholder::<U16Be, _>()?;
                    U16Be::write(ctxt, language)?;
                    U16Be::write(ctxt, first_code)?;
                    U16Be::write(ctxt, u16::try_from(glyph_id_array.len())?)?;
                    ctxt.write_vec::<U16Be, _>(glyph_id_array)?;
                    ctxt.write_placeholder(length, u16::try_from(ctxt.bytes_written() - start)?)?;
                }
                CmapSubtable::Format10 {
                    language,
                    start_char_code,
                    glyph_id_array,
                } => {
                    let start = ctxt.bytes_written();
                    U16Be::write(ctxt, 10u16)?; // format
                    U16Be::write(ctxt, 0u16)?; // reserved
                    let length = ctxt.placeholder::<U32Be, _>()?;
                    U32Be::write(ctxt, language)?;
                    U32Be::write(ctxt, start_char_code)?;
                    U32Be::write(ctxt, u32::try_from(glyph_id_array.len())?)?;
                    ctxt.write_vec::<U16Be, _>(glyph_id_array)?;
                    ctxt.write_placeholder(length, u32::try_from(ctxt.bytes_written() - start)?)?;
                }
                CmapSubtable::Format12(CmapSubtableFormat12 { language, groups }) => {
                    let start = ctxt.bytes_written();
                    U16Be::write(ctxt, 12u16)?; // format
                    U16Be::write(ctxt, 0u16)?; // reserved
                    let length = ctxt.placeholder::<U32Be, _>()?;
                    U32Be::write(ctxt, language)?;
                    U32Be::write(ctxt, u32::try_from(groups.len())?)?;
                    ctxt.write_vec::<SequentialMapGroup, _>(groups)?;
                    ctxt.write_placeholder(length, u32::try_from(ctxt.bytes_written() - start)?)?;
                }
            }
            Ok(())
        }
    }
}
#[cfg(test)]
mod tests {
    use super::*;
    use crate::font_data::FontData;
    use crate::tables::FontTableProvider;
    use crate::tag;
    use crate::tests::read_fixture;
    use std::path::Path;
    #[test]
    fn test_calculator() {
        let calc = Format4Calculator { seg_count: 39 };
        assert_eq!(calc.seg_count_x2(), 78);
        assert_eq!(calc.search_range(), 64);
        assert_eq!(calc.entry_selector(), 5);
        assert_eq!(calc.range_shift(), 14);
    }
    #[test]
    fn offset_to_index_start() {
        let i = 0;
        let id_range_offset = 4;
        let start_code_offset = 0;
        let id_range_offsets_len = 2;
        let index = offset_to_index(i, id_range_offset, start_code_offset, id_range_offsets_len);
        assert_eq!(index, Ok(0));
    }
    #[test]
    fn offset_to_index_near_end() {
        let i = 0;
        let id_range_offset = 4;
        let start_code_offset = 222;
        let id_range_offsets_len = 2;
        let index = offset_to_index(i, id_range_offset, start_code_offset, id_range_offsets_len);
        assert_eq!(index, Ok(222));
    }
    fn with_cmap_subtable<P: AsRef<Path>>(
        path: P,
        platform: PlatformId,
        encoding: EncodingId,
        f: impl Fn(CmapSubtable<'_>),
    ) {
        let font_buffer = read_fixture(path);
        let otf = ReadScope::new(&font_buffer).read::<FontData<'_>>().unwrap();
        let table_provider = otf.table_provider(0).expect("error reading font file");
        let data = table_provider.read_table_data(tag::CMAP).unwrap();
        let cmap = ReadScope::new(&data).read::<Cmap<'_>>().unwrap();
        let encoding_record = cmap.find_subtable(platform, encoding).unwrap();
        let cmap_subtable = cmap
            .scope
            .offset(usize::try_from(encoding_record.offset).unwrap())
            .read::<CmapSubtable<'_>>()
            .unwrap();
        f(cmap_subtable);
    }
    #[test]
    fn test_mappings_format0() {
        with_cmap_subtable(
            "tests/fonts/opentype/TwitterColorEmoji-SVGinOT.ttf",
            PlatformId::MACINTOSH,
            EncodingId::MACINTOSH_APPLE_ROMAN,
            |cmap_subtable| {
                match cmap_subtable {
                    CmapSubtable::Format0 { .. } => {}
                    _ => {
                        panic!("expected CmapSubtable::Format0");
                    }
                };
                let mappings = cmap_subtable.mappings().unwrap();
                let copyright = cmap_subtable.map_glyph('©' as u32).unwrap().unwrap();
                assert_eq!(mappings[&copyright], '©' as u32);
            },
        );
    }
    #[test]
    #[cfg(feature = "prince")]
    fn test_mappings_format2() {
        with_cmap_subtable(
            "../../../tests/data/fonts/HardGothicNormal.ttf",
            PlatformId::WINDOWS,
            EncodingId::WINDOWS_BIG5,
            |cmap_subtable| {
                if !matches!(cmap_subtable, CmapSubtable::Format2 { .. }) {
                    panic!("expected CmapSubtable::Format2");
                }
                let mappings = cmap_subtable.mappings().unwrap();
                // This would be a good place for non-ASCII idents when we're on 1.53 or newer
                // let 世 = cmap_subtable.map_glyph(12).unwrap().unwrap(); // 世 U+4E16
                // let 丈 = cmap_subtable.map_glyph(6).unwrap().unwrap(); // 丈 U+4E08
                let a = cmap_subtable.map_glyph(13).unwrap().unwrap(); // 丕 U+4E15
                let b = cmap_subtable.map_glyph(44).unwrap().unwrap(); // 乾 U+4E7E
                assert_eq!(mappings[&a], 13);
                assert_eq!(mappings[&b], 44);
            },
        );
    }
    #[test]
    #[cfg(feature = "prince")]
    fn test_mappings_format2_chao_yan_ze_cu_hei_tif() {
        with_cmap_subtable(
            "../../../tests/data/fonts/big5/ChaoYanZeCuHeiTif-1.ttf",
            PlatformId::WINDOWS,
            EncodingId::WINDOWS_BIG5,
            |cmap_subtable| {
                if !matches!(cmap_subtable, CmapSubtable::Format2 { .. }) {
                    panic!("expected CmapSubtable::Format2");
                }
                // This is checking that one and two-byte characters work as well as certain
                // entries that should not be present are absent (E.g. 0x220). This test was
                // added after `mappings_fn` for cmap format 2 was written. Prior to the change
                // many entries (such as 0x220) were included that should not have been.
                let mappings = cmap_subtable.mappings().unwrap();
                assert_eq!(mappings[&85], 0x54);
                assert_eq!(mappings[&461], 0xA26F);
                assert!(mappings.values().find(|&&ch| ch == 0x220).is_none());
            },
        );
    }
    #[test]
    fn test_mappings_format4() {
        with_cmap_subtable(
            "tests/fonts/opentype/TwitterColorEmoji-SVGinOT.ttf",
            PlatformId::UNICODE,
            EncodingId(3),
            |cmap_subtable| {
                match cmap_subtable {
                    CmapSubtable::Format4 { .. } => {}
                    _ => {
                        panic!("expected CmapSubtable::Format4");
                    }
                };
                let mappings = cmap_subtable.mappings().unwrap();
                // Format 4 can only represent 16-bit chars (Basic Multilingual Plane)
                let soccer_ball = cmap_subtable.map_glyph('⚽' as u32).unwrap().unwrap();
                let double_exclamation = cmap_subtable.map_glyph('‼' as u32).unwrap().unwrap();
                assert_eq!(mappings[&soccer_ball], '⚽' as u32);
                assert_eq!(mappings[&double_exclamation], '‼' as u32);
            },
        );
    }
    // This font was extracted from the SVG in this issue:
    // https://github.com/terrastruct/d2/issues/1252
    // It has an odd number of remaining bytes, which is not meant to happen. We
    // previously rejected it due to this, but relaxed the code to accept it.
    #[test]
    fn test_mappings_format4_odd_remaining() {
        with_cmap_subtable(
            "tests/fonts/woff1/d2-33857867-font-bold.woff",
            PlatformId::WINDOWS,
            EncodingId::WINDOWS_UNICODE_BMP_UCS2,
            |cmap_subtable| {
                match cmap_subtable {
                    CmapSubtable::Format4 { .. } => {}
                    _ => {
                        panic!("expected CmapSubtable::Format4");
                    }
                };
            },
        );
    }
    #[test]
    fn test_mappings_format6() {
        with_cmap_subtable(
            "tests/fonts/opentype/Klei.otf",
            PlatformId::MACINTOSH,
            EncodingId::MACINTOSH_APPLE_ROMAN,
            |cmap_subtable| {
                match cmap_subtable {
                    CmapSubtable::Format6 { .. } => {}
                    _ => {
                        panic!("expected CmapSubtable::Format6");
                    }
                };
                let mappings = cmap_subtable.mappings().unwrap();
                let a = cmap_subtable.map_glyph('a' as u32).unwrap().unwrap();
                let caron = cmap_subtable.map_glyph(255).unwrap().unwrap();
                assert_eq!(mappings[&a], 'a' as u32);
                assert_eq!(mappings[&caron], 255);
            },
        );
    }
    #[test]
    fn test_mappings_format12() {
        with_cmap_subtable(
            "tests/fonts/opentype/TwitterColorEmoji-SVGinOT.ttf",
            PlatformId::WINDOWS,
            EncodingId::WINDOWS_UNICODE_UCS4,
            |cmap_subtable| {
                match cmap_subtable {
                    CmapSubtable::Format12 { .. } => {}
                    _ => {
                        panic!("expected CmapSubtable::Format12");
                    }
                };
                let mappings = cmap_subtable.mappings().unwrap();
                // Format 12 uses 32-bit chars so can map all of Unicode
                let dove = cmap_subtable.map_glyph('🕊' as u32).unwrap().unwrap();
                let nerd_face = cmap_subtable.map_glyph('🤓' as u32).unwrap().unwrap();
                assert_eq!(mappings[&dove], '🕊' as u32);
                assert_eq!(mappings[&nerd_face], '🤓' as u32);
            },
        );
    }
}