1
//! OpenType font table parsing and writing.
2

            
3
pub mod aat;
4
pub mod cmap;
5
pub mod colr;
6
pub mod cpal;
7
pub mod gasp;
8
pub mod glyf;
9
pub mod kern;
10
pub mod loca;
11
pub mod morx;
12
pub mod os2;
13
pub mod svg;
14
pub mod variable_fonts;
15

            
16
use std::borrow::Cow;
17
use std::fmt::{self, Formatter};
18

            
19
use encoding_rs::Encoding;
20

            
21
use crate::binary::read::{
22
    CheckIndex, ReadArray, ReadArrayCow, ReadBinary, ReadBinaryDep, ReadCtxt, ReadFrom, ReadScope,
23
    ReadUnchecked,
24
};
25
use crate::binary::write::{Placeholder, WriteBinary, WriteContext};
26
use crate::binary::{I16Be, I32Be, I64Be, U16Be, U32Be};
27
use crate::error::{ParseError, WriteError};
28
use crate::tag;
29
use crate::{size, SafeFrom};
30

            
31
/// Magic value identifying a CFF font (`OTTO`)
32
pub const CFF_MAGIC: u32 = tag::OTTO;
33

            
34
/// Magic number identifying TrueType 1.0
35
///
36
/// The version number 1.0 as a 16.16 fixed-point value, indicating TrueType glyph data.
37
pub const TTF_MAGIC: u32 = 0x00010000;
38

            
39
/// Magic number identifying TrueType (Apple version)
40
pub const TRUE_MAGIC: u32 = tag::TRUE;
41

            
42
/// Magic value identifying a TrueType font collection `ttcf`
43
pub const TTCF_MAGIC: u32 = tag::TTCF;
44

            
45
/// 32-bit signed fixed-point number (16.16)
46
///
47
/// The integer component is a signed 16-bit integer. The fraction is an unsigned
48
/// 16-bit numerator for denominator of 0xFFFF (65635). I.e scale of 1/65535.
49
#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
50
pub struct Fixed(i32);
51

            
52
/// Date represented in number of seconds since 12:00 midnight, January 1, 1904
53
///
54
/// The value is represented as a signed 64-bit integer.
55
type LongDateTime = i64;
56

            
57
pub trait FontTableProvider {
58
    /// Return data for the specified table if present
59
    fn table_data(&self, tag: u32) -> Result<Option<Cow<'_, [u8]>>, ParseError>;
60

            
61
    fn has_table(&self, tag: u32) -> bool;
62

            
63
160564
    fn read_table_data(&self, tag: u32) -> Result<Cow<'_, [u8]>, ParseError> {
64
160564
        self.table_data(tag)?.ok_or(ParseError::MissingTable(tag))
65
160564
    }
66

            
67
    /// The tags of the tables within this font.
68
    ///
69
    /// Returns `None` if the tags cannot be determined.
70
    fn table_tags(&self) -> Option<Vec<u32>>;
71

            
72
}
73

            
74
pub trait SfntVersion {
75
    fn sfnt_version(&self) -> u32;
76
}
77

            
78
/// The F2DOT14 format consists of a signed, 2’s complement integer and an unsigned fraction.
79
///
80
/// To compute the actual value, take the integer and add the fraction.
81
#[repr(transparent)]
82
#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Default)]
83
pub struct F2Dot14(i16);
84

            
85
/// The size of the offsets in the `loca` table
86
///
87
/// <https://docs.microsoft.com/en-us/typography/opentype/spec/loca>
88
#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
89
pub enum IndexToLocFormat {
90
    /// Offsets are 16-bit. The actual local offset divided by 2 is stored.
91
    Short,
92
    /// Offsets are 32-bit. The actual local offset is stored.
93
    Long,
94
}
95

            
96
#[derive(Clone)]
97
pub struct OpenTypeFont<'a> {
98
    pub scope: ReadScope<'a>,
99
    pub data: OpenTypeData<'a>,
100
}
101

            
102
/// An OpenTypeFont containing a single font or a collection of fonts
103
#[derive(Clone)]
104
pub enum OpenTypeData<'a> {
105
    Single(OffsetTable<'a>),
106
    Collection(TTCHeader<'a>),
107
}
108

            
109
/// TrueType collection header
110
#[derive(Clone)]
111
pub struct TTCHeader<'a> {
112
    pub major_version: u16,
113
    pub minor_version: u16,
114
    pub offset_tables: ReadArray<'a, U32Be>,
115
    // TODO add digital signature fields
116
}
117

            
118
/// OpenType Offset Table
119
///
120
/// <https://docs.microsoft.com/en-us/typography/opentype/spec/otff#organization-of-an-opentype-font>
121
#[derive(Clone)]
122
pub struct OffsetTable<'a> {
123
    pub sfnt_version: u32,
124
    pub search_range: u16,
125
    pub entry_selector: u16,
126
    pub range_shift: u16,
127
    pub table_records: ReadArray<'a, TableRecord>,
128
}
129

            
130
pub struct OffsetTableFontProvider<'a> {
131
    scope: ReadScope<'a>,
132
    offset_table: OffsetTable<'a>,
133
}
134

            
135
/// An entry in the Offset Table
136
///
137
/// <https://docs.microsoft.com/en-us/typography/opentype/spec/otff#organization-of-an-opentype-font>
138
#[derive(Debug, Copy, Clone, PartialEq, PartialOrd, Hash)]
139
pub struct TableRecord {
140
    pub table_tag: u32,
141
    pub checksum: u32,
142
    pub offset: u32,
143
    pub length: u32,
144
}
145

            
146
/// `head` table
147
///
148
/// <https://docs.microsoft.com/en-us/typography/opentype/spec/head>
149
#[derive(Debug, Clone, PartialEq, PartialOrd, Hash)]
150
pub struct HeadTable {
151
    pub major_version: u16,
152
    pub minor_version: u16,
153
    pub font_revision: Fixed,
154
    pub check_sum_adjustment: u32,
155
    pub magic_number: u32,
156
    pub flags: u16,
157
    pub units_per_em: u16,
158
    pub created: LongDateTime,
159
    pub modified: LongDateTime,
160
    pub x_min: i16,
161
    pub y_min: i16,
162
    pub x_max: i16,
163
    pub y_max: i16,
164
    pub mac_style: MacStyle,
165
    pub lowest_rec_ppem: u16,
166
    pub font_direction_hint: i16,
167
    pub index_to_loc_format: IndexToLocFormat,
168
    pub glyph_data_format: i16,
169
}
170

            
171
/// macStyle field in `head`
172
#[enumflags2::bitflags]
173
#[repr(u16)]
174
#[derive(Debug, Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Hash)]
175
#[allow(non_camel_case_types)]
176
pub enum MacStyleFlag {
177
    BOLD = 1 << 0,
178
    ITALIC = 1 << 1,
179
    UNDERLINE = 1 << 2,
180
    OUTLINE = 1 << 3,
181
    SHADOW = 1 << 4,
182
    CONDENSED = 1 << 5,
183
    EXTENDED = 1 << 6,
184
    // Bits 7–15: Reserved (set to 0).
185
}
186

            
187
pub type MacStyle = enumflags2::BitFlags<MacStyleFlag>;
188

            
189
/// `hhea` horizontal header table
190
///
191
/// > This table contains information for horizontal layout.
192
///
193
/// <https://docs.microsoft.com/en-us/typography/opentype/spec/hhea>
194
///
195
/// This struct is also used for the `vhea` table.
196
#[derive(Debug, Clone, PartialEq, PartialOrd, Hash)]
197
pub struct HheaTable {
198
    pub ascender: i16,
199
    pub descender: i16,
200
    pub line_gap: i16,
201
    pub advance_width_max: u16,
202
    pub min_left_side_bearing: i16,
203
    pub min_right_side_bearing: i16,
204
    pub x_max_extent: i16,
205
    pub caret_slope_rise: i16,
206
    pub caret_slope_run: i16,
207
    pub caret_offset: i16,
208
    pub num_h_metrics: u16,
209
}
210

            
211
/// `hmtx` horizontal metrics table
212
///
213
/// <https://docs.microsoft.com/en-us/typography/opentype/spec/hmtx>
214
///
215
/// This struct is also used for `vmtx` table.
216
#[derive(Debug)]
217
pub struct HmtxTable<'a> {
218
    pub h_metrics: ReadArrayCow<'a, LongHorMetric>,
219
    pub left_side_bearings: ReadArrayCow<'a, I16Be>,
220
}
221

            
222
/// A `longHorMetric` record in the `hmtx` table.
223
///
224
/// <https://docs.microsoft.com/en-us/typography/opentype/spec/hmtx>
225
///
226
/// This struct is also used for LongVerMetric `vmtx` table.
227
#[derive(Debug, PartialEq, Copy, Clone)]
228
pub struct LongHorMetric {
229
    pub advance_width: u16,
230
    pub lsb: i16,
231
}
232

            
233
/// maxp - Maximum profile
234
///
235
/// This table establishes the memory requirements for this font. Fonts with CFF data must use
236
/// Version 0.5 of this table, specifying only the numGlyphs field. Fonts with TrueType outlines
237
/// must use Version 1.0 of this table, where all data is required.
238
///
239
/// <https://docs.microsoft.com/en-us/typography/opentype/spec/maxp>
240
#[derive(Debug, Clone, PartialEq, PartialOrd, Hash)]
241
pub struct MaxpTable {
242
    pub num_glyphs: u16,
243
    /// Extra fields, present if maxp table is version 1.0, absent if version 0.5.
244
    pub version1_sub_table: Option<MaxpVersion1SubTable>,
245
}
246

            
247
#[derive(Debug, Clone, PartialEq, PartialOrd, Hash)]
248
pub struct MaxpVersion1SubTable {
249
    /// Maximum points in a non-composite glyph.
250
    pub max_points: u16,
251
    /// Maximum contours in a non-composite glyph.
252
    pub max_contours: u16,
253
    /// Maximum points in a composite glyph.
254
    pub max_composite_points: u16,
255
    /// Maximum contours in a composite glyph.
256
    pub max_composite_contours: u16,
257
    /// 1 if instructions do not use the twilight zone (Z0), or 2 if instructions do use Z0; should
258
    /// be set to 2 in most cases.
259
    pub max_zones: u16,
260
    /// Maximum points used in Z0.
261
    pub max_twilight_points: u16,
262
    /// Number of Storage Area locations.
263
    pub max_storage: u16,
264
    /// Number of FDEFs, equal to the highest function number + 1.
265
    pub max_function_defs: u16,
266
    /// Number of IDEFs.
267
    pub max_instruction_defs: u16,
268
    /// Maximum stack depth across Font Program ('fpgm' table), CVT Program ('prep' table) and all
269
    /// glyph instructions (in the 'glyf' table).
270
    pub max_stack_elements: u16,
271
    /// Maximum byte count for glyph instructions.
272
    pub max_size_of_instructions: u16,
273
    /// Maximum number of components referenced at “top level” for any composite glyph.
274
    pub max_component_elements: u16,
275
    /// Maximum levels of recursion; 1 for simple components.
276
    pub max_component_depth: u16,
277
}
278

            
279
/// `name` table
280
///
281
/// <https://docs.microsoft.com/en-us/typography/opentype/spec/name>
282
pub struct NameTable<'a> {
283
    pub string_storage: ReadScope<'a>,
284
    pub name_records: ReadArray<'a, NameRecord>,
285
    /// Language tag records
286
    ///
287
    /// Present if `name` table version is 1 or newer. If present, language ids >= 0x8000 refer to
288
    /// language tag records with 0x8000 the first record, 0x8001 the second, etc.
289
    pub opt_langtag_records: Option<ReadArray<'a, LangTagRecord>>,
290
}
291

            
292
/// Record within the `name` table
293
#[derive(Debug)]
294
pub struct NameRecord {
295
    pub platform_id: u16,
296
    pub encoding_id: u16,
297
    pub language_id: u16,
298
    pub name_id: u16,
299
    pub length: u16,
300
    pub offset: u16,
301
}
302

            
303
/// Language-tag record within the `name` table
304
pub struct LangTagRecord {
305
    pub length: u16,
306
    pub offset: u16,
307
}
308

            
309
/// cvt — Control Value Table
310
///
311
/// <https://learn.microsoft.com/en-us/typography/opentype/spec/cvt>
312
pub struct CvtTable<'a> {
313
    pub values: ReadArrayCow<'a, I16Be>,
314
}
315

            
316
impl<'a> OpenTypeFont<'a> {
317
29214
    pub fn table_provider(&self, index: usize) -> Result<OffsetTableFontProvider<'a>, ParseError> {
318
29214
        self.offset_table(index)
319
29214
            .map(|offset_table| OffsetTableFontProvider {
320
29214
                offset_table: offset_table.into_owned(),
321
29214
                scope: self.scope,
322
29214
            })
323
29214
    }
324

            
325
29214
    pub fn offset_table<'b>(
326
29214
        &'b self,
327
29214
        index: usize,
328
29214
    ) -> Result<Cow<'b, OffsetTable<'a>>, ParseError> {
329
29214
        match &self.data {
330
29214
            OpenTypeData::Single(offset_table) => Ok(Cow::Borrowed(offset_table)),
331
            OpenTypeData::Collection(ttc) => {
332
                let offset = ttc
333
                    .offset_tables
334
                    .get_item(index)
335
                    .map(SafeFrom::safe_from)
336
                    .ok_or(ParseError::BadIndex)?;
337
                let offset_table = self.scope.offset(offset).read::<OffsetTable<'_>>()?;
338
                Ok(Cow::Owned(offset_table))
339
            }
340
        }
341
29214
    }
342
}
343

            
344
impl ReadBinary for OpenTypeFont<'_> {
345
    type HostType<'a> = OpenTypeFont<'a>;
346

            
347
51678
    fn read<'a>(ctxt: &mut ReadCtxt<'a>) -> Result<Self::HostType<'a>, ParseError> {
348
51678
        let scope = ctxt.scope();
349
51678
        let mut peek = ctxt.clone();
350
51678
        let magic = peek.read_u32be()?;
351
51678
        match magic {
352
            TTF_MAGIC | TRUE_MAGIC | CFF_MAGIC => {
353
51678
                let offset_table = ctxt.read::<OffsetTable<'_>>()?;
354
51678
                let font = OpenTypeData::Single(offset_table);
355
51678
                Ok(OpenTypeFont { scope, data: font })
356
            }
357
            TTCF_MAGIC => {
358
                let ttc_header = ctxt.read::<TTCHeader<'_>>()?;
359
                let font = OpenTypeData::Collection(ttc_header);
360
                Ok(OpenTypeFont { scope, data: font })
361
            }
362
            _ => Err(ParseError::BadVersion),
363
        }
364
51678
    }
365
}
366

            
367
impl ReadBinary for TTCHeader<'_> {
368
    type HostType<'a> = TTCHeader<'a>;
369

            
370
    fn read<'a>(ctxt: &mut ReadCtxt<'a>) -> Result<Self::HostType<'a>, ParseError> {
371
        let ttc_tag = ctxt.read_u32be()?;
372
        match ttc_tag {
373
            TTCF_MAGIC => {
374
                let major_version = ctxt.read_u16be()?;
375
                let minor_version = ctxt.read_u16be()?;
376
                ctxt.check(major_version == 1 || major_version == 2)?;
377
                let num_fonts = usize::try_from(ctxt.read_u32be()?)?;
378
                let offset_tables = ctxt.read_array::<U32Be>(num_fonts)?;
379
                // TODO read digital signature fields in TTCHeader version 2
380
                Ok(TTCHeader {
381
                    major_version,
382
                    minor_version,
383
                    offset_tables,
384
                })
385
            }
386
            _ => Err(ParseError::BadVersion),
387
        }
388
    }
389
}
390

            
391
impl ReadBinary for OffsetTable<'_> {
392
    type HostType<'a> = OffsetTable<'a>;
393

            
394
51678
    fn read<'a>(ctxt: &mut ReadCtxt<'a>) -> Result<Self::HostType<'a>, ParseError> {
395
51678
        let sfnt_version = ctxt.read_u32be()?;
396
51678
        match sfnt_version {
397
            TTF_MAGIC | TRUE_MAGIC | CFF_MAGIC => {
398
51678
                let num_tables = ctxt.read_u16be()?;
399
51678
                let search_range = ctxt.read_u16be()?;
400
51678
                let entry_selector = ctxt.read_u16be()?;
401
51678
                let range_shift = ctxt.read_u16be()?;
402
51678
                let table_records = ctxt.read_array::<TableRecord>(usize::from(num_tables))?;
403
51678
                Ok(OffsetTable {
404
51678
                    sfnt_version,
405
51678
                    search_range,
406
51678
                    entry_selector,
407
51678
                    range_shift,
408
51678
                    table_records,
409
51678
                })
410
            }
411
            _ => Err(ParseError::BadVersion),
412
        }
413
51678
    }
414
}
415

            
416
// WEB-LIFT FIX (2026-06-02): plain big-endian reads from a byte slice. Used by the
417
// manual table-directory scan below. The remill/web lift mis-handles the
418
// `ReadArray<TableRecord>` read path (the nested-tuple `TableRecord` `read_dep` returns
419
// `table_tag = 0` for EVERY record — proven via a probe: tags[7]=0x0000 while the bytes
420
// at that offset are 0x68656164 'head' and `tag::HEAD`==0x6164), so the directory lookup
421
// never matches and the font fails to parse → text measures height 0. These hand-rolled
422
// indexed byte reads lift correctly.
423
#[inline]
424
62964
fn be16(d: &[u8], o: usize) -> u32 {
425
62964
    ((d[o] as u32) << 8) | (d[o + 1] as u32)
426
62964
}
427
#[inline]
428
653508
fn be32(d: &[u8], o: usize) -> u32 {
429
653508
    ((d[o] as u32) << 24) | ((d[o + 1] as u32) << 16) | ((d[o + 2] as u32) << 8) | (d[o + 3] as u32)
430
653508
}
431

            
432
impl OffsetTableFontProvider<'_> {
433
    /// Locate the table directory by hand for a single-font sfnt laid out at the start of
434
    /// `self.scope` (TTF/OTTO/'true'): returns `(dir_offset, num_tables)`. Returns `None`
435
    /// for TTC / unrecognised layouts so the caller falls back to the original
436
    /// `ReadArray`-based path. See the `be32` comment for why this exists.
437
62964
    fn manual_dir(&self) -> Option<(usize, usize)> {
438
62964
        let data = self.scope.data();
439
62964
        if data.len() < 12 {
440
            return None;
441
62964
        }
442
62964
        match be32(data, 0) {
443
            // TTF_MAGIC | CFF_MAGIC (OTTO) | TRUE_MAGIC ('true')
444
62964
            0x0001_0000 | 0x4F54_544F | 0x7472_7565 => Some((12, be16(data, 4) as usize)),
445
            _ => None,
446
        }
447
62964
    }
448
}
449

            
450
impl FontTableProvider for OffsetTableFontProvider<'_> {
451
62964
    fn table_data(&self, tag: u32) -> Result<Option<Cow<'_, [u8]>>, ParseError> {
452
62964
        if let Some((dir, num)) = self.manual_dir() {
453
62964
            let data = self.scope.data();
454
62964
            let mut i = 0;
455
464616
            while i < num {
456
464616
                let r = dir + i * 16;
457
464616
                if r + 16 > data.len() {
458
                    break;
459
464616
                }
460
464616
                if be32(data, r) == tag {
461
62964
                    let off = be32(data, r + 8) as usize;
462
62964
                    let len = be32(data, r + 12) as usize;
463
62964
                    return Ok(off
464
62964
                        .checked_add(len)
465
62964
                        .filter(|&e| e <= data.len())
466
62964
                        .map(|e| Cow::Borrowed(&data[off..e])));
467
401652
                }
468
401652
                i += 1;
469
            }
470
            return Ok(None);
471
        }
472
        // Fallback (TTC / unrecognised): original ReadArray path.
473
        self.offset_table
474
            .read_table(&self.scope, tag)
475
            .map(|scope| scope.map(|scope| Cow::Borrowed(scope.data())))
476
62964
    }
477

            
478
13500
    fn has_table(&self, tag: u32) -> bool {
479
13500
        self.table_data(tag).ok().flatten().is_some()
480
13500
    }
481

            
482
    fn table_tags(&self) -> Option<Vec<u32>> {
483
        if let Some((dir, num)) = self.manual_dir() {
484
            let data = self.scope.data();
485
            let mut tags = Vec::with_capacity(num);
486
            let mut i = 0;
487
            while i < num {
488
                let r = dir + i * 16;
489
                if r + 4 > data.len() {
490
                    break;
491
                }
492
                tags.push(be32(data, r));
493
                i += 1;
494
            }
495
            return Some(tags);
496
        }
497
        // Fallback (TTC / unrecognised): original ReadArray path.
498
        let records = &self.offset_table.table_records;
499
        let n = records.len();
500
        let mut tags = Vec::with_capacity(n);
501
        let mut i = 0;
502
        while i < n {
503
            if let Ok(rec) = records.read_item(i) {
504
                tags.push(rec.table_tag);
505
            }
506
            i += 1;
507
        }
508
        Some(tags)
509
    }
510
}
511

            
512
impl SfntVersion for OffsetTableFontProvider<'_> {
513
29214
    fn sfnt_version(&self) -> u32 {
514
29214
        self.offset_table.sfnt_version
515
29214
    }
516
}
517

            
518
impl ReadFrom for TableRecord {
519
    type ReadType = ((U32Be, U32Be), (U32Be, U32Be));
520
    fn read_from(((table_tag, checksum), (offset, length)): ((u32, u32), (u32, u32))) -> Self {
521
        TableRecord {
522
            table_tag,
523
            checksum,
524
            offset,
525
            length,
526
        }
527
    }
528
}
529

            
530
impl WriteBinary<&Self> for TableRecord {
531
    type Output = ();
532

            
533
    fn write<C: WriteContext>(ctxt: &mut C, table: &TableRecord) -> Result<(), WriteError> {
534
        U32Be::write(ctxt, table.table_tag)?;
535
        U32Be::write(ctxt, table.checksum)?;
536
        U32Be::write(ctxt, table.offset)?;
537
        U32Be::write(ctxt, table.length)?;
538

            
539
        Ok(())
540
    }
541
}
542

            
543
impl<'a> OffsetTable<'a> {
544
    pub fn find_table_record(&self, tag: u32) -> Option<TableRecord> {
545
        // Indexed loop instead of `.iter().find(closure)`: on the lifted/web backend the
546
        // ReadArray iterator (ReadArrayIter::next) and/or the find-closure mis-lift and
547
        // yield no match (same class as the css.rs map+collect element-drop). Indexed
548
        // `read_item` lifts correctly (it's what binary_search uses).
549
        let n = self.table_records.len();
550
        let mut i = 0;
551
        while i < n {
552
            if let Ok(rec) = self.table_records.read_item(i) {
553
                if rec.table_tag == tag {
554
                    return Some(rec);
555
                }
556
            }
557
            i += 1;
558
        }
559
        None
560
    }
561

            
562
    pub fn read_table(
563
        &self,
564
        scope: &ReadScope<'a>,
565
        tag: u32,
566
    ) -> Result<Option<ReadScope<'a>>, ParseError> {
567
        if let Some(table_record) = self.find_table_record(tag) {
568
            let table = table_record.read_table(scope)?;
569
            Ok(Some(table))
570
        } else {
571
            Ok(None)
572
        }
573
    }
574
}
575

            
576
impl TableRecord {
577
    pub const SIZE: usize = 4 * size::U32;
578

            
579
    pub fn read_table<'a>(&self, scope: &ReadScope<'a>) -> Result<ReadScope<'a>, ParseError> {
580
        let offset = usize::try_from(self.offset)?;
581
        let length = usize::try_from(self.length)?;
582
        scope.offset_length(offset, length)
583
    }
584
}
585

            
586
impl ReadBinary for HeadTable {
587
    type HostType<'a> = Self;
588

            
589
58104
    fn read(ctxt: &mut ReadCtxt<'_>) -> Result<Self, ParseError> {
590
58104
        let major_version = ctxt.read::<U16Be>()?;
591
58104
        let minor_version = ctxt.read::<U16Be>()?;
592
58104
        let font_revision = ctxt.read::<Fixed>()?;
593
58104
        let check_sum_adjustment = ctxt.read::<U32Be>()?;
594
58104
        let magic_number = ctxt.read::<U32Be>()?;
595
58104
        ctxt.check(magic_number == 0x5F0F3CF5)?;
596
58104
        let flags = ctxt.read::<U16Be>()?;
597
58104
        let units_per_em = ctxt.read::<U16Be>()?;
598
58104
        let created = ctxt.read::<I64Be>()?;
599
58104
        let modified = ctxt.read::<I64Be>()?;
600
58104
        let x_min = ctxt.read::<I16Be>()?;
601
58104
        let y_min = ctxt.read::<I16Be>()?;
602
58104
        let x_max = ctxt.read::<I16Be>()?;
603
58104
        let y_max = ctxt.read::<I16Be>()?;
604
58104
        let mac_style = ctxt.read::<U16Be>().map(MacStyle::from_bits_truncate)?;
605
58104
        let lowest_rec_ppem = ctxt.read::<U16Be>()?;
606
58104
        let font_direction_hint = ctxt.read::<I16Be>()?;
607
58104
        let index_to_loc_format = ctxt.read::<IndexToLocFormat>()?;
608
58104
        let glyph_data_format = ctxt.read::<I16Be>()?;
609

            
610
58104
        Ok(HeadTable {
611
58104
            major_version,
612
58104
            minor_version,
613
58104
            font_revision,
614
58104
            check_sum_adjustment,
615
58104
            magic_number,
616
58104
            flags,
617
58104
            units_per_em,
618
58104
            created,
619
58104
            modified,
620
58104
            x_min,
621
58104
            y_min,
622
58104
            x_max,
623
58104
            y_max,
624
58104
            mac_style,
625
58104
            lowest_rec_ppem,
626
58104
            font_direction_hint,
627
58104
            index_to_loc_format,
628
58104
            glyph_data_format,
629
58104
        })
630
58104
    }
631
}
632

            
633
impl WriteBinary<&Self> for HeadTable {
634
    type Output = Placeholder<U32Be, u32>;
635

            
636
    /// Writes the table to the `WriteContext` and returns a placeholder to the
637
    /// `check_sum_adjustment` field.
638
    ///
639
    /// The `check_sum_adjustment` field requires special handling to calculate. See:
640
    /// <https://docs.microsoft.com/en-us/typography/opentype/spec/head>
641
    fn write<C: WriteContext>(ctxt: &mut C, table: &HeadTable) -> Result<Self::Output, WriteError> {
642
        U16Be::write(ctxt, table.major_version)?;
643
        U16Be::write(ctxt, table.minor_version)?;
644
        Fixed::write(ctxt, table.font_revision)?;
645
        let check_sum_adjustment = ctxt.placeholder()?;
646
        U32Be::write(ctxt, table.magic_number)?;
647
        U16Be::write(ctxt, table.flags)?;
648
        U16Be::write(ctxt, table.units_per_em)?;
649
        I64Be::write(ctxt, table.created)?;
650
        I64Be::write(ctxt, table.modified)?;
651
        I16Be::write(ctxt, table.x_min)?;
652
        I16Be::write(ctxt, table.y_min)?;
653
        I16Be::write(ctxt, table.x_max)?;
654
        I16Be::write(ctxt, table.y_max)?;
655
        U16Be::write(ctxt, table.mac_style.bits())?;
656
        U16Be::write(ctxt, table.lowest_rec_ppem)?;
657
        I16Be::write(ctxt, table.font_direction_hint)?;
658
        IndexToLocFormat::write(ctxt, table.index_to_loc_format)?;
659
        I16Be::write(ctxt, table.glyph_data_format)?;
660

            
661
        Ok(check_sum_adjustment)
662
    }
663
}
664

            
665
impl HeadTable {
666
    // macStyle:
667
    // Bit 0: Bold (if set to 1);
668
    // Bit 1: Italic (if set to 1)
669
    // Bit 2: Underline (if set to 1)
670
    // Bit 3: Outline (if set to 1)
671
    // Bit 4: Shadow (if set to 1)
672
    // Bit 5: Condensed (if set to 1)
673
    // Bit 6: Extended (if set to 1)
674
    // Bits 7–15: Reserved (set to 0).
675
    // https://docs.microsoft.com/en-us/typography/opentype/spec/head
676
    pub fn is_bold(&self) -> bool {
677
        self.mac_style.contains(MacStyleFlag::BOLD)
678
    }
679

            
680
    pub fn is_italic(&self) -> bool {
681
        self.mac_style.contains(MacStyleFlag::ITALIC)
682
    }
683
}
684

            
685
impl ReadBinary for HheaTable {
686
    type HostType<'a> = Self;
687

            
688
45198
    fn read(ctxt: &mut ReadCtxt<'_>) -> Result<Self, ParseError> {
689
45198
        let major_version = ctxt.read_u16be()?;
690
45198
        let _minor_version = ctxt.read_u16be()?;
691
45198
        ctxt.check(major_version == 1)?;
692
45198
        let ascender = ctxt.read_i16be()?;
693
45198
        let descender = ctxt.read_i16be()?;
694
45198
        let line_gap = ctxt.read_i16be()?;
695
45198
        let advance_width_max = ctxt.read_u16be()?;
696
45198
        let min_left_side_bearing = ctxt.read_i16be()?;
697
45198
        let min_right_side_bearing = ctxt.read_i16be()?;
698
45198
        let x_max_extent = ctxt.read_i16be()?;
699
45198
        let caret_slope_rise = ctxt.read_i16be()?;
700
45198
        let caret_slope_run = ctxt.read_i16be()?;
701
45198
        let caret_offset = ctxt.read_i16be()?;
702
45198
        let _reserved1 = ctxt.read_i16be()?;
703
45198
        let _reserved2 = ctxt.read_i16be()?;
704
45198
        let _reserved3 = ctxt.read_i16be()?;
705
45198
        let _reserved4 = ctxt.read_i16be()?;
706
45198
        let metric_data_format = ctxt.read_i16be()?;
707
45198
        ctxt.check(metric_data_format == 0)?;
708
45198
        let num_h_metrics = ctxt.read_u16be()?;
709

            
710
45198
        Ok(HheaTable {
711
45198
            ascender,
712
45198
            descender,
713
45198
            line_gap,
714
45198
            advance_width_max,
715
45198
            min_left_side_bearing,
716
45198
            min_right_side_bearing,
717
45198
            x_max_extent,
718
45198
            caret_slope_rise,
719
45198
            caret_slope_run,
720
45198
            caret_offset,
721
45198
            num_h_metrics,
722
45198
        })
723
45198
    }
724
}
725

            
726
impl WriteBinary<&Self> for HheaTable {
727
    type Output = ();
728

            
729
    fn write<C: WriteContext>(ctxt: &mut C, table: &HheaTable) -> Result<(), WriteError> {
730
        U16Be::write(ctxt, 1u16)?; // major_version
731
        U16Be::write(ctxt, 0u16)?; // minor_version
732

            
733
        I16Be::write(ctxt, table.ascender)?;
734
        I16Be::write(ctxt, table.descender)?;
735
        I16Be::write(ctxt, table.line_gap)?;
736
        U16Be::write(ctxt, table.advance_width_max)?;
737
        I16Be::write(ctxt, table.min_left_side_bearing)?;
738
        I16Be::write(ctxt, table.min_right_side_bearing)?;
739
        I16Be::write(ctxt, table.x_max_extent)?;
740
        I16Be::write(ctxt, table.caret_slope_rise)?;
741
        I16Be::write(ctxt, table.caret_slope_run)?;
742
        I16Be::write(ctxt, table.caret_offset)?;
743

            
744
        I16Be::write(ctxt, 0i16)?; // reserved
745
        I16Be::write(ctxt, 0i16)?; // reserved
746
        I16Be::write(ctxt, 0i16)?; // reserved
747
        I16Be::write(ctxt, 0i16)?; // reserved
748

            
749
        I16Be::write(ctxt, 0i16)?; // metric_data_format
750

            
751
        U16Be::write(ctxt, table.num_h_metrics)?;
752

            
753
        Ok(())
754
    }
755
}
756

            
757
impl ReadBinaryDep for HmtxTable<'_> {
758
    type Args<'a> = (usize, usize); // num_glyphs, num_h_metrics
759
    type HostType<'a> = HmtxTable<'a>;
760

            
761
526122
    fn read_dep<'a>(
762
526122
        ctxt: &mut ReadCtxt<'a>,
763
526122
        (num_glyphs, num_h_metrics): (usize, usize),
764
526122
    ) -> Result<Self::HostType<'a>, ParseError> {
765
526122
        let h_metrics = ctxt.read_array::<LongHorMetric>(num_h_metrics)?;
766
503712
        let left_side_bearings =
767
503712
            ctxt.read_array::<I16Be>(num_glyphs.saturating_sub(num_h_metrics))?;
768
503712
        Ok(HmtxTable {
769
503712
            h_metrics: ReadArrayCow::Borrowed(h_metrics),
770
503712
            left_side_bearings: ReadArrayCow::Borrowed(left_side_bearings),
771
503712
        })
772
526122
    }
773
}
774

            
775
impl<'a> WriteBinary<&Self> for HmtxTable<'a> {
776
    type Output = ();
777

            
778
    fn write<C: WriteContext>(ctxt: &mut C, table: &HmtxTable<'a>) -> Result<(), WriteError> {
779
        ReadArrayCow::write(ctxt, &table.h_metrics)?;
780
        ReadArrayCow::write(ctxt, &table.left_side_bearings)?;
781

            
782
        Ok(())
783
    }
784
}
785

            
786
impl HmtxTable<'_> {
787
    /// Retrieve the horizontal advance for glyph with index `glyph_id`.
788
    pub fn horizontal_advance(&self, glyph_id: u16) -> Result<u16, ParseError> {
789
        if self.h_metrics.is_empty() {
790
            return Err(ParseError::BadIndex);
791
        }
792

            
793
        // This is largely the same as `metric` below but it avoids a lookup in the
794
        // `left_side_bearings` array.
795
        let glyph_id = usize::from(glyph_id);
796
        let num_h_metrics = self.h_metrics.len();
797
        let metric = if glyph_id < num_h_metrics {
798
            self.h_metrics.read_item(glyph_id)
799
        } else {
800
            // As an optimization, the number of records can be less than the number of glyphs, in
801
            // which case the advance width value of the last record applies to all remaining glyph
802
            // IDs.
803
            // https://docs.microsoft.com/en-us/typography/opentype/spec/hmtx
804
            self.h_metrics.read_item(num_h_metrics - 1)
805
        };
806
        metric.map(|metric| metric.advance_width)
807
    }
808

            
809
    /// Retrieve the advance and left-side bearing for glyph with index `glyph_id`.
810
    pub fn metric(&self, glyph_id: u16) -> Result<LongHorMetric, ParseError> {
811
        if self.h_metrics.is_empty() {
812
            return Err(ParseError::BadIndex);
813
        }
814

            
815
        let glyph_id = usize::from(glyph_id);
816
        let num_h_metrics = self.h_metrics.len();
817
        if glyph_id < num_h_metrics {
818
            self.h_metrics.read_item(glyph_id)
819
        } else {
820
            // As an optimization, the number of records can be less than the number of glyphs, in
821
            // which case the advance width value of the last record applies to all remaining glyph
822
            // IDs. If numberOfHMetrics is less than the total number of glyphs, then the hMetrics
823
            // array is followed by an array for the left side bearing values of the remaining
824
            // glyphs.
825
            // https://docs.microsoft.com/en-us/typography/opentype/spec/hmtx
826
            let mut metric = self.h_metrics.read_item(num_h_metrics - 1)?;
827
            let lsb_index = glyph_id - num_h_metrics;
828
            metric.lsb = self
829
                .left_side_bearings
830
                .check_index(lsb_index)
831
                .and_then(|_| self.left_side_bearings.read_item(lsb_index))?;
832
            Ok(metric)
833
        }
834
    }
835
}
836

            
837
impl ReadFrom for LongHorMetric {
838
    type ReadType = (U16Be, I16Be);
839
503712
    fn read_from((advance_width, lsb): (u16, i16)) -> Self {
840
503712
        LongHorMetric { advance_width, lsb }
841
503712
    }
842
}
843

            
844
impl WriteBinary for LongHorMetric {
845
    type Output = ();
846

            
847
    fn write<C: WriteContext>(ctxt: &mut C, metric: LongHorMetric) -> Result<(), WriteError> {
848
        U16Be::write(ctxt, metric.advance_width)?;
849
        I16Be::write(ctxt, metric.lsb)?;
850

            
851
        Ok(())
852
    }
853
}
854

            
855
impl ReadBinary for MaxpTable {
856
    type HostType<'a> = Self;
857

            
858
80568
    fn read(ctxt: &mut ReadCtxt<'_>) -> Result<Self, ParseError> {
859
80568
        let version = ctxt.read_u32be()?;
860
80568
        let num_glyphs = ctxt.read_u16be()?;
861
80568
        let sub_table = if version == 0x00010000 {
862
80568
            Some(ctxt.read::<MaxpVersion1SubTable>()?)
863
        } else {
864
            None
865
        };
866
80568
        Ok(MaxpTable {
867
80568
            num_glyphs,
868
80568
            version1_sub_table: sub_table,
869
80568
        })
870
80568
    }
871
}
872

            
873
impl WriteBinary<&Self> for MaxpTable {
874
    type Output = ();
875

            
876
    fn write<C: WriteContext>(ctxt: &mut C, table: &MaxpTable) -> Result<(), WriteError> {
877
        if let Some(sub_table) = &table.version1_sub_table {
878
            U32Be::write(ctxt, 0x00010000u32)?; // version 1.0
879
            U16Be::write(ctxt, table.num_glyphs)?;
880
            MaxpVersion1SubTable::write(ctxt, sub_table)?;
881
        } else {
882
            U32Be::write(ctxt, 0x00005000u32)?; // version 0.5
883
            U16Be::write(ctxt, table.num_glyphs)?;
884
        }
885
        Ok(())
886
    }
887
}
888

            
889
impl ReadBinary for MaxpVersion1SubTable {
890
    type HostType<'a> = Self;
891

            
892
80568
    fn read(ctxt: &mut ReadCtxt<'_>) -> Result<Self, ParseError> {
893
80568
        let max_points = ctxt.read_u16be()?;
894
80568
        let max_contours = ctxt.read_u16be()?;
895
80568
        let max_composite_points = ctxt.read_u16be()?;
896
80568
        let max_composite_contours = ctxt.read_u16be()?;
897
80568
        let max_zones = ctxt.read_u16be()?;
898
80568
        let max_twilight_points = ctxt.read_u16be()?;
899
80568
        let max_storage = ctxt.read_u16be()?;
900
80568
        let max_function_defs = ctxt.read_u16be()?;
901
80568
        let max_instruction_defs = ctxt.read_u16be()?;
902
80568
        let max_stack_elements = ctxt.read_u16be()?;
903
80568
        let max_size_of_instructions = ctxt.read_u16be()?;
904
80568
        let max_component_elements = ctxt.read_u16be()?;
905
80568
        let max_component_depth = ctxt.read_u16be()?;
906

            
907
80568
        Ok(MaxpVersion1SubTable {
908
80568
            max_points,
909
80568
            max_contours,
910
80568
            max_composite_points,
911
80568
            max_composite_contours,
912
80568
            max_zones,
913
80568
            max_twilight_points,
914
80568
            max_storage,
915
80568
            max_function_defs,
916
80568
            max_instruction_defs,
917
80568
            max_stack_elements,
918
80568
            max_size_of_instructions,
919
80568
            max_component_elements,
920
80568
            max_component_depth,
921
80568
        })
922
80568
    }
923
}
924

            
925
impl WriteBinary<&Self> for MaxpVersion1SubTable {
926
    type Output = ();
927

            
928
    fn write<C: WriteContext>(
929
        ctxt: &mut C,
930
        table: &MaxpVersion1SubTable,
931
    ) -> Result<(), WriteError> {
932
        U16Be::write(ctxt, table.max_points)?;
933
        U16Be::write(ctxt, table.max_contours)?;
934
        U16Be::write(ctxt, table.max_composite_points)?;
935
        U16Be::write(ctxt, table.max_composite_contours)?;
936
        U16Be::write(ctxt, table.max_zones)?;
937
        U16Be::write(ctxt, table.max_twilight_points)?;
938
        U16Be::write(ctxt, table.max_storage)?;
939
        U16Be::write(ctxt, table.max_function_defs)?;
940
        U16Be::write(ctxt, table.max_instruction_defs)?;
941
        U16Be::write(ctxt, table.max_stack_elements)?;
942
        U16Be::write(ctxt, table.max_size_of_instructions)?;
943
        U16Be::write(ctxt, table.max_component_elements)?;
944
        U16Be::write(ctxt, table.max_component_depth)?;
945

            
946
        Ok(())
947
    }
948
}
949

            
950
impl NameTable<'_> {
951
    pub const COPYRIGHT_NOTICE: u16 = 0;
952
    pub const FONT_FAMILY_NAME: u16 = 1;
953
    pub const FONT_SUBFAMILY_NAME: u16 = 2;
954
    pub const UNIQUE_FONT_IDENTIFIER: u16 = 3;
955
    pub const FULL_FONT_NAME: u16 = 4;
956
    pub const VERSION_STRING: u16 = 5;
957
    pub const POSTSCRIPT_NAME: u16 = 6;
958
    pub const TRADEMARK: u16 = 7;
959
    pub const MANUFACTURER_NAME: u16 = 8;
960
    pub const DESIGNER: u16 = 9;
961
    pub const DESCRIPTION: u16 = 10;
962
    pub const URL_VENDOR: u16 = 11;
963
    pub const URL_DESIGNER: u16 = 12;
964
    pub const LICENSE_DESCRIPTION: u16 = 13;
965
    pub const LICENSE_INFO_URL: u16 = 14;
966
    pub const TYPOGRAPHIC_FAMILY_NAME: u16 = 16;
967
    pub const TYPOGRAPHIC_SUBFAMILY_NAME: u16 = 17;
968
    pub const COMPATIBLE_FULL: u16 = 18; // (Macintosh only)
969
    pub const SAMPLE_TEXT: u16 = 19;
970
    pub const POSTSCRIPT_CID_FINDFONT_NAME: u16 = 20;
971
    pub const WWS_FAMILY_NAME: u16 = 21; // WWS = Weight, width, slope
972
    pub const WWS_SUBFAMILY_NAME: u16 = 22;
973
    pub const LIGHT_BACKGROUND_PALETTE: u16 = 23;
974
    pub const DARK_BACKGROUND_PALETTE: u16 = 24;
975
    pub const VARIATIONS_POSTSCRIPT_NAME_PREFIX: u16 = 25;
976

            
977
    /// Return a string for the supplied `name_id`.
978
    ///
979
    /// Returns the first match in this order:
980
    ///
981
    /// 1. Unicode platform
982
    /// 2. Windows platform, English language ids
983
    /// 3. Apple platform, Roman language id
984
22464
    pub fn string_for_id(&self, name_id: u16) -> Option<String> {
985
22464
        self.name_records
986
22464
            .iter()
987
134730
            .find_map(|record| {
988
134730
                if record.name_id != name_id {
989
112266
                    return None;
990
22464
                }
991
                // Match English records
992
22464
                match (record.platform_id, record.encoding_id, record.language_id) {
993
                    // Unicode
994
                    (0, _, _) => Some((record, encoding_rs::UTF_16BE)),
995
                    // Windows Unicode BMP, English language ids
996
                    // https://learn.microsoft.com/en-us/typography/opentype/spec/name#windows-language-ids
997
                    (
998
                        3,
999
                        1,
                        0x0C09 | 0x2809 | 0x1009 | 0x2409 | 0x4009 | 0x1809 | 0x2009 | 0x4409
                        | 0x1409 | 0x3409 | 0x4809 | 0x1C09 | 0x2C09 | 0x0809 | 0x0409 | 0x3009,
7884
                    ) => Some((record, encoding_rs::UTF_16BE)),
                    // Windows Unicode full, English language ids
                    // https://learn.microsoft.com/en-us/typography/opentype/spec/name#windows-language-ids
                    (
                        3,
                        10,
                        0x0C09 | 0x2809 | 0x1009 | 0x2409 | 0x4009 | 0x1809 | 0x2009 | 0x4409
                        | 0x1409 | 0x3409 | 0x4809 | 0x1C09 | 0x2C09 | 0x0809 | 0x0409 | 0x3009,
                    ) => Some((record, encoding_rs::UTF_16BE)),
                    // Apple, Roman Script, English
14580
                    (1, 0, 0) => Some((record, encoding_rs::MACINTOSH)),
                    _ => None,
                }
134730
            })
22464
            .and_then(|(record, encoding)| {
22464
                let offset = usize::from(record.offset);
22464
                let length = usize::from(record.length);
22464
                let name_data = self
22464
                    .string_storage
22464
                    .offset_length(offset, length)
22464
                    .ok()?
22464
                    .data();
22464
                Some(decode(encoding, name_data))
22464
            })
22464
    }
}
22464
pub(crate) fn decode(encoding: &'static Encoding, data: &[u8]) -> String {
22464
    let mut decoder = encoding.new_decoder();
22464
    let size = decoder.max_utf8_buffer_length(data.len()).unwrap();
22464
    let mut s = String::with_capacity(size);
22464
    let (_res, _read, _repl) = decoder.decode_to_string(data, &mut s, true);
22464
    s
22464
}
fn utf16be_encode(string: &str) -> Vec<u8> {
    string
        .encode_utf16()
        .flat_map(|codeunit| codeunit.to_be_bytes())
        .collect()
}
impl ReadBinary for NameTable<'_> {
    type HostType<'a> = NameTable<'a>;
22464
    fn read<'a>(ctxt: &mut ReadCtxt<'a>) -> Result<Self::HostType<'a>, ParseError> {
22464
        let scope = ctxt.scope();
22464
        let format = ctxt.read_u16be()?;
22464
        ctxt.check(format <= 1)?;
22464
        let count = usize::from(ctxt.read_u16be()?);
22464
        let string_offset = usize::from(ctxt.read_u16be()?);
22464
        let string_storage = scope.offset(string_offset);
22464
        let name_records = ctxt.read_array::<NameRecord>(count)?;
22464
        let opt_langtag_records = if format > 0 {
            let langtag_count = usize::from(ctxt.read_u16be()?);
            let langtag_records = ctxt.read_array::<LangTagRecord>(langtag_count)?;
            Some(langtag_records)
        } else {
22464
            None
        };
22464
        Ok(NameTable {
22464
            string_storage,
22464
            name_records,
22464
            opt_langtag_records,
22464
        })
22464
    }
}
impl<'a> WriteBinary<&Self> for NameTable<'a> {
    type Output = ();
    fn write<C: WriteContext>(ctxt: &mut C, name: &NameTable<'a>) -> Result<(), WriteError> {
        let format = name.opt_langtag_records.as_ref().map_or(0u16, |_| 1);
        U16Be::write(ctxt, format)?;
        U16Be::write(ctxt, u16::try_from(name.name_records.len())?)?; // count
        let string_offset = ctxt.placeholder::<U16Be, _>()?;
        <&ReadArray<'a, _>>::write(ctxt, &name.name_records)?;
        if let Some(lang_tag_records) = &name.opt_langtag_records {
            U16Be::write(ctxt, u16::try_from(lang_tag_records.len())?)?; // lang_tag_count
            <&ReadArray<'a, _>>::write(ctxt, lang_tag_records)?;
        }
        ctxt.write_placeholder(string_offset, u16::try_from(ctxt.bytes_written())?)?;
        ctxt.write_bytes(name.string_storage.data())?;
        Ok(())
    }
}
impl ReadFrom for NameRecord {
    type ReadType = ((U16Be, U16Be, U16Be), (U16Be, U16Be, U16Be));
134730
    fn read_from(
134730
        ((platform_id, encoding_id, language_id), (name_id, length, offset)): (
134730
            (u16, u16, u16),
134730
            (u16, u16, u16),
134730
        ),
134730
    ) -> Self {
134730
        NameRecord {
134730
            platform_id,
134730
            encoding_id,
134730
            language_id,
134730
            name_id,
134730
            length,
134730
            offset,
134730
        }
134730
    }
}
impl WriteBinary for NameRecord {
    type Output = ();
    fn write<C: WriteContext>(ctxt: &mut C, record: NameRecord) -> Result<(), WriteError> {
        U16Be::write(ctxt, record.platform_id)?;
        U16Be::write(ctxt, record.encoding_id)?;
        U16Be::write(ctxt, record.language_id)?;
        U16Be::write(ctxt, record.name_id)?;
        U16Be::write(ctxt, record.length)?;
        U16Be::write(ctxt, record.offset)?;
        Ok(())
    }
}
impl ReadFrom for LangTagRecord {
    type ReadType = (U16Be, U16Be);
    fn read_from((length, offset): (u16, u16)) -> Self {
        LangTagRecord { length, offset }
    }
}
impl WriteBinary for LangTagRecord {
    type Output = ();
    fn write<C: WriteContext>(ctxt: &mut C, record: LangTagRecord) -> Result<(), WriteError> {
        U16Be::write(ctxt, record.length)?;
        U16Be::write(ctxt, record.offset)?;
        Ok(())
    }
}
impl ReadBinaryDep for CvtTable<'_> {
    type Args<'a> = u32;
    type HostType<'a> = CvtTable<'a>;
14580
    fn read_dep<'a>(
14580
        ctxt: &mut ReadCtxt<'a>,
14580
        length: u32,
14580
    ) -> Result<Self::HostType<'a>, ParseError> {
14580
        let length = usize::safe_from(length);
        // The table contains 'n' values, where n is just as many values can be read for the
        // size of the table. We assume that `ctxt` is limited to the length of the table
        //
        // > The length of the table must be an integral number of FWORD units.
14580
        ctxt.check(length % I16Be::SIZE == 0)?;
14580
        let n = length / I16Be::SIZE;
14580
        let values = ctxt.read_array(n)?;
14580
        Ok(CvtTable {
14580
            values: ReadArrayCow::Borrowed(values),
14580
        })
14580
    }
}
impl WriteBinary<&Self> for CvtTable<'_> {
    type Output = ();
    fn write<C: WriteContext>(ctxt: &mut C, table: &Self) -> Result<(), WriteError> {
        ReadArrayCow::write(ctxt, &table.values)
    }
}
impl ReadFrom for F2Dot14 {
    type ReadType = I16Be;
    fn read_from(value: i16) -> Self {
        F2Dot14(value)
    }
}
impl WriteBinary for F2Dot14 {
    type Output = ();
    fn write<C: WriteContext>(ctxt: &mut C, val: Self) -> Result<(), WriteError> {
        I16Be::write(ctxt, val.0)
    }
}
impl ReadBinary for IndexToLocFormat {
    type HostType<'a> = Self;
58104
    fn read(ctxt: &mut ReadCtxt<'_>) -> Result<Self, ParseError> {
58104
        let index_to_loc_format = ctxt.read_i16be()?;
58104
        match index_to_loc_format {
14850
            0 => Ok(IndexToLocFormat::Short),
43254
            1 => Ok(IndexToLocFormat::Long),
            _ => Err(ParseError::BadValue),
        }
58104
    }
}
impl WriteBinary for IndexToLocFormat {
    type Output = ();
    fn write<C: WriteContext>(ctxt: &mut C, index_to_loc_format: Self) -> Result<(), WriteError> {
        match index_to_loc_format {
            IndexToLocFormat::Short => I16Be::write(ctxt, 0i16),
            IndexToLocFormat::Long => I16Be::write(ctxt, 1i16),
        }
    }
}
impl Fixed {
    /// Create a new `Fixed` with a raw 16.16 value.
    pub const fn from_raw(value: i32) -> Fixed {
        Fixed(value)
    }
    pub fn raw_value(&self) -> i32 {
        self.0
    }
    pub fn abs(&self) -> Fixed {
        Fixed(self.0.abs())
    }
}
impl std::ops::Add for Fixed {
    type Output = Self;
    fn add(self, rhs: Self) -> Self::Output {
        Fixed(self.0.wrapping_add(rhs.0))
    }
}
impl std::ops::Sub for Fixed {
    type Output = Self;
    fn sub(self, rhs: Self) -> Self::Output {
        Fixed(self.0.wrapping_sub(rhs.0))
    }
}
impl std::ops::Mul for Fixed {
    type Output = Self;
    fn mul(self, rhs: Self) -> Self::Output {
        let a = i64::from(self.0);
        let b = i64::from(rhs.0);
        Fixed(((a * b) >> 16) as i32)
    }
}
impl std::ops::Div for Fixed {
    type Output = Self;
    fn div(self, rhs: Self) -> Self::Output {
        let a = i64::from(self.0);
        let b = i64::from(rhs.0);
        if b == 0 {
            // Closest we have to infinity. Same as what FreeType does
            // https://gitlab.freedesktop.org/freetype/freetype/-/blob/a20de84e1608f9eb1d0391d7322b2e0e0f235aba/src/base/ftcalc.c#L267
            return Fixed(0x7FFFFFFF);
        }
        Fixed(((a << 16) / b) as i32)
    }
}
impl std::ops::Neg for Fixed {
    type Output = Self;
    fn neg(self) -> Self::Output {
        Fixed(-self.0)
    }
}
impl fmt::Debug for Fixed {
    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
        f.debug_tuple("Fixed").field(&f32::from(*self)).finish()
    }
}
impl ReadFrom for Fixed {
    type ReadType = I32Be;
58104
    fn read_from(value: i32) -> Self {
58104
        Fixed(value)
58104
    }
}
impl WriteBinary for Fixed {
    type Output = ();
    fn write<C: WriteContext>(ctxt: &mut C, val: Self) -> Result<(), WriteError> {
        I32Be::write(ctxt, val.0)
    }
}
impl From<Fixed> for f32 {
    fn from(value: Fixed) -> f32 {
        (f64::from(value.0) / 65536.0) as f32
    }
}
// When converting from float or double data types to 16.16, the following method must be used:
//
// 1. Multiply the fractional component by 65536, and round the result to the nearest integer (for
//    fractional values of 0.5 and higher, take the next higher integer; for other fractional
//    values, truncate). Store the result in the low-order word.
// 2. Move the two’s-complement representation of the integer component into the high-order word
//
// https://learn.microsoft.com/en-us/typography/opentype/spec/otvaroverview#coordinate-scales-and-normalization
impl From<f32> for Fixed {
    fn from(value: f32) -> Self {
        let sign = value.signum() as i32;
        let value = value.abs();
        let fract = (value.fract() * 65536.0).round() as i32;
        let int = value.trunc() as i32;
        Fixed::from_raw(((int << 16) | fract) * sign)
    }
}
impl From<f64> for Fixed {
    fn from(value: f64) -> Self {
        let sign = value.signum() as i32;
        let value = value.abs();
        let fract = (value.fract() * 65536.0).round() as i32;
        let int = value.trunc() as i32;
        Fixed::from_raw(((int << 16) | fract) * sign)
    }
}
impl From<i32> for Fixed {
    fn from(value: i32) -> Self {
        Fixed::from_raw(value << 16)
    }
}
impl From<F2Dot14> for Fixed {
    fn from(fixed: F2Dot14) -> Self {
        Fixed(i32::from(fixed.0) << 2)
    }
}
impl F2Dot14 {
    pub fn from_raw(value: i16) -> Self {
        F2Dot14(value)
    }
    pub fn raw_value(&self) -> i16 {
        self.0
    }
}
impl std::ops::Add for F2Dot14 {
    type Output = Self;
    fn add(self, rhs: Self) -> Self::Output {
        F2Dot14(self.0.wrapping_add(rhs.0))
    }
}
impl std::ops::Sub for F2Dot14 {
    type Output = Self;
    fn sub(self, rhs: Self) -> Self::Output {
        F2Dot14(self.0.wrapping_sub(rhs.0))
    }
}
impl std::ops::Mul for F2Dot14 {
    type Output = Self;
    fn mul(self, rhs: Self) -> Self::Output {
        let a = i32::from(self.0);
        let b = i32::from(rhs.0);
        F2Dot14(((a * b) >> 14) as i16)
    }
}
impl std::ops::Div for F2Dot14 {
    type Output = Self;
    fn div(self, rhs: Self) -> Self::Output {
        let a = i32::from(self.0);
        let b = i32::from(rhs.0);
        if b == 0 {
            // Closest we have to infinity.
            return F2Dot14(0x7FFF);
        }
        F2Dot14(((a << 14) / b) as i16)
    }
}
impl std::ops::Neg for F2Dot14 {
    type Output = Self;
    fn neg(self) -> Self::Output {
        F2Dot14(-self.0)
    }
}
impl fmt::Debug for F2Dot14 {
    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
        f.debug_tuple("F2Dot14").field(&f32::from(*self)).finish()
    }
}
impl From<Fixed> for F2Dot14 {
    fn from(fixed: Fixed) -> Self {
        // Convert the final, normalized 16.16 coordinate value to 2.14 by this method: add
        // 0x00000002, and sign-extend shift to the right by 2.
        // https://learn.microsoft.com/en-us/typography/opentype/spec/otvaroverview#coordinate-scales-and-normalization
        F2Dot14(((fixed.0 + 2) >> 2) as i16)
    }
}
impl From<f32> for F2Dot14 {
    fn from(value: f32) -> Self {
        let sign = value.signum() as i16;
        let value = value.abs();
        let fract = (value.fract() * 16384.0).round() as i16;
        let int = value.trunc() as i16;
        F2Dot14::from_raw(((int << 14) | fract).wrapping_mul(sign))
    }
}
impl From<i16> for F2Dot14 {
    fn from(value: i16) -> Self {
        F2Dot14::from_raw(value << 14)
    }
}
impl From<F2Dot14> for f32 {
    fn from(value: F2Dot14) -> Self {
        f32::from(value.0) / 16384.
    }
}
impl From<F2Dot14> for f64 {
    fn from(value: F2Dot14) -> Self {
        f64::from(value.0) / 16384.
    }
}
impl<T: FontTableProvider> FontTableProvider for Box<T> {
    fn table_data(&self, tag: u32) -> Result<Option<Cow<'_, [u8]>>, ParseError> {
        self.as_ref().table_data(tag)
    }
    fn has_table(&self, tag: u32) -> bool {
        self.as_ref().has_table(tag)
    }
    fn table_tags(&self) -> Option<Vec<u32>> {
        self.as_ref().table_tags()
    }
}
impl<T: SfntVersion> SfntVersion for Box<T> {
    fn sfnt_version(&self) -> u32 {
        self.as_ref().sfnt_version()
    }
}
49935
pub(crate) fn read_and_box_table(
49935
    provider: &impl FontTableProvider,
49935
    tag: u32,
49935
) -> Result<Box<[u8]>, ParseError> {
49935
    provider
49935
        .read_table_data(tag)
49935
        .map(|table| Box::from(table.into_owned()))
49935
}
pub(crate) fn read_and_box_optional_table(
    provider: &impl FontTableProvider,
    tag: u32,
) -> Result<Option<Box<[u8]>>, ParseError> {
    Ok(provider
        .table_data(tag)?
        .map(|table| Box::from(table.into_owned())))
}
pub mod owned {
    //! Owned versions of tables.
    use std::borrow::Cow;
    use super::utf16be_encode;
    use crate::binary::write::{Placeholder, WriteBinary, WriteContext};
    use crate::binary::U16Be;
    use crate::error::{ParseError, WriteError};
    /// An owned `name` table.
    ///
    /// Can be created from [super::NameTable] using `TryFrom`.
    ///
    /// <https://docs.microsoft.com/en-us/typography/opentype/spec/name>
    pub struct NameTable<'a> {
        pub name_records: Vec<NameRecord<'a>>,
        /// UTF-16BE encoded language tag strings
        pub langtag_records: Vec<Cow<'a, [u8]>>,
    }
    /// Record within the `name` table.
    pub struct NameRecord<'a> {
        pub platform_id: u16,
        pub encoding_id: u16,
        pub language_id: u16,
        pub name_id: u16,
        pub string: Cow<'a, [u8]>,
    }
    impl NameTable<'_> {
        /// Replace all instances of `name_id` with a Unicode entry with the value `string`.
        pub fn replace_entries(&mut self, name_id: u16, string: &str) {
            self.remove_entries(name_id);
            let replacement = NameRecord {
                platform_id: 0, // Unicode
                encoding_id: 4, // full repertoire
                language_id: 0,
                name_id,
                string: Cow::from(utf16be_encode(string)),
            };
            self.name_records.push(replacement);
        }
        pub fn remove_entries(&mut self, name_id: u16) {
            self.name_records.retain(|record| record.name_id != name_id);
        }
    }
    impl<'a> TryFrom<&super::NameTable<'a>> for NameTable<'a> {
        type Error = ParseError;
        fn try_from(name: &super::NameTable<'a>) -> Result<NameTable<'a>, ParseError> {
            let name_records = name
                .name_records
                .iter()
                .map(|record| {
                    let string = name
                        .string_storage
                        .offset_length(usize::from(record.offset), usize::from(record.length))
                        .map(|scope| Cow::from(scope.data()))?;
                    Ok(NameRecord {
                        platform_id: record.platform_id,
                        encoding_id: record.encoding_id,
                        language_id: record.language_id,
                        name_id: record.name_id,
                        string,
                    })
                })
                .collect::<Result<Vec<_>, ParseError>>()?;
            let langtag_records = name
                .opt_langtag_records
                .as_ref()
                .map(|langtag_records| {
                    langtag_records
                        .iter()
                        .map(|record| {
                            name.string_storage
                                .offset_length(
                                    usize::from(record.offset),
                                    usize::from(record.length),
                                )
                                .map(|scope| Cow::from(scope.data()))
                        })
                        .collect::<Result<Vec<_>, _>>()
                })
                .transpose()?
                .unwrap_or_else(Vec::new);
            Ok(NameTable {
                name_records,
                langtag_records,
            })
        }
    }
    impl<'a> WriteBinary<&Self> for NameTable<'a> {
        type Output = ();
        fn write<C: WriteContext>(ctxt: &mut C, name: &NameTable<'a>) -> Result<(), WriteError> {
            let format = if name.langtag_records.is_empty() {
                0u16
            } else {
                1
            };
            U16Be::write(ctxt, format)?;
            U16Be::write(ctxt, u16::try_from(name.name_records.len())?)?; // count
            let string_offset = ctxt.placeholder::<U16Be, _>()?;
            let name_record_offsets = name
                .name_records
                .iter()
                .map(|record| NameRecord::write(ctxt, record))
                .collect::<Result<Vec<_>, _>>()?;
            if !name.langtag_records.is_empty() {
                // langtag count
                U16Be::write(ctxt, u16::try_from(name.langtag_records.len())?)?;
            }
            let langtag_record_offsets = name
                .langtag_records
                .iter()
                .map(|record| {
                    U16Be::write(ctxt, u16::try_from(record.len())?)?;
                    ctxt.placeholder::<U16Be, _>()
                })
                .collect::<Result<Vec<_>, _>>()?;
            let string_start = ctxt.bytes_written();
            ctxt.write_placeholder(string_offset, u16::try_from(string_start)?)?;
            // Write the string data
            let lang_tags = name.langtag_records.iter().zip(langtag_record_offsets);
            let records = name
                .name_records
                .iter()
                .map(|rec| &rec.string)
                .zip(name_record_offsets)
                .chain(lang_tags);
            for (string, placeholder) in records {
                ctxt.write_placeholder(
                    placeholder,
                    u16::try_from(ctxt.bytes_written() - string_start)?,
                )?;
                ctxt.write_bytes(string)?;
            }
            Ok(())
        }
    }
    impl WriteBinary<&Self> for NameRecord<'_> {
        type Output = Placeholder<U16Be, u16>;
        fn write<C: WriteContext>(ctxt: &mut C, record: &Self) -> Result<Self::Output, WriteError> {
            U16Be::write(ctxt, record.platform_id)?;
            U16Be::write(ctxt, record.encoding_id)?;
            U16Be::write(ctxt, record.language_id)?;
            U16Be::write(ctxt, record.name_id)?;
            U16Be::write(ctxt, u16::try_from(record.string.len())?)?;
            let offset = ctxt.placeholder()?;
            Ok(offset)
        }
    }
}
#[cfg(test)]
mod tests {
    use super::{
        owned, F2Dot14, Fixed, HeadTable, HmtxTable, NameTable, OpenTypeData, OpenTypeFont,
    };
    use crate::assert_close;
    use crate::binary::read::ReadScope;
    use crate::binary::write::{WriteBinary, WriteBuffer, WriteContext};
    use crate::tests::{assert_close, assert_f2dot14_close, assert_fixed_close, read_fixture};
    const NAME_DATA: &[u8] = include_bytes!("../tests/fonts/opentype/name.bin");
    #[test]
    fn test_write_head_table() {
        // Read a head table in, then write it back out and compare it
        let head_data = include_bytes!("../tests/fonts/opentype/head.bin");
        let head = ReadScope::new(head_data).read::<HeadTable>().unwrap();
        let checksum_adjustment = head.check_sum_adjustment;
        let mut ctxt = WriteBuffer::new();
        let placeholder = HeadTable::write(&mut ctxt, &head).unwrap();
        ctxt.write_placeholder(placeholder, checksum_adjustment)
            .unwrap();
        assert_eq!(ctxt.bytes(), &head_data[..]);
    }
    #[test]
    fn test_write_hmtx_table() {
        // Read a hmtx table in, then write it back out and compare it
        let hmtx_data = include_bytes!("../tests/fonts/opentype/hmtx.bin");
        let num_glyphs = 1264;
        let num_h_metrics = 1264;
        let hmtx = ReadScope::new(hmtx_data)
            .read_dep::<HmtxTable<'_>>((num_glyphs, num_h_metrics))
            .unwrap();
        let mut ctxt = WriteBuffer::new();
        HmtxTable::write(&mut ctxt, &hmtx).unwrap();
        assert_eq!(ctxt.bytes(), &hmtx_data[..]);
    }
    #[test]
    fn test_write_name_table() {
        // Read a name table in, then write it back out and compare it
        let name = ReadScope::new(NAME_DATA).read::<NameTable<'_>>().unwrap();
        let mut ctxt = WriteBuffer::new();
        NameTable::write(&mut ctxt, &name).unwrap();
        assert_eq!(ctxt.bytes(), &NAME_DATA[..]);
    }
    #[test]
    fn roundtrip_owned_name_table() {
        // Test that NameTable can be converted to owned variant, written, and read back the same
        let name = ReadScope::new(NAME_DATA).read::<NameTable<'_>>().unwrap();
        let owned = owned::NameTable::try_from(&name).unwrap();
        let mut ctxt = WriteBuffer::new();
        owned::NameTable::write(&mut ctxt, &owned).unwrap();
        assert_eq!(ctxt.bytes(), &NAME_DATA[..]);
    }
    #[test]
    fn f32_from_f2dot14() {
        // Examples from https://docs.microsoft.com/en-us/typography/opentype/spec/otff#data-types
        assert_close(f32::from(F2Dot14(0x7fff)), 1.999939);
        assert_close(f32::from(F2Dot14(0x7000)), 1.75);
        assert_close(f32::from(F2Dot14(0x0001)), 0.000061);
        assert_close(f32::from(F2Dot14(0x0000)), 0.0);
        assert_close(f32::from(F2Dot14(-1 /* 0xFFFF */)), -0.000061);
        assert_close(f32::from(F2Dot14(-32768 /* 0x8000 */)), -2.0);
    }
    #[test]
    fn f2dot14_from_f32() {
        // Examples from https://docs.microsoft.com/en-us/typography/opentype/spec/otff#data-types
        assert_eq!(F2Dot14::from(1.999939), F2Dot14::from_raw(0x7fff));
        assert_eq!(F2Dot14::from(1.75), F2Dot14::from_raw(0x7000));
        assert_eq!(F2Dot14::from(0.000061), F2Dot14::from_raw(0x0001));
        assert_eq!(F2Dot14::from(0.0), F2Dot14::from_raw(0x0000));
        assert_eq!(F2Dot14::from(-0.000061), F2Dot14::from_raw(-1 /* 0xffff */));
        assert_close!(f32::from(F2Dot14::from(-1.4)), -1.4, 1. / 16384.);
        assert_eq!(F2Dot14::from(-2.0), F2Dot14::from_raw(-32768 /* 0x8000 */));
    }
    #[test]
    fn f2dot14_from_fixed() {
        // Examples from https://docs.microsoft.com/en-us/typography/opentype/spec/otff#data-types
        assert_eq!(
            F2Dot14::from(Fixed::from(1.999939)),
            F2Dot14::from_raw(0x7fff)
        );
        assert_eq!(F2Dot14::from(Fixed::from(1.75)), F2Dot14::from_raw(0x7000));
        assert_eq!(
            F2Dot14::from(Fixed::from(0.000061)),
            F2Dot14::from_raw(0x0001)
        );
        assert_eq!(F2Dot14::from(Fixed::from(0.0)), F2Dot14::from_raw(0x0000));
        assert_eq!(
            F2Dot14::from(Fixed::from(-0.000061)),
            F2Dot14::from_raw(-1 /* 0xffff */)
        );
        assert_eq!(
            F2Dot14::from(Fixed::from(-2.0)),
            F2Dot14::from_raw(-32768 /* 0x8000 */)
        );
    }
    #[test]
    fn f32_from_fixed() {
        assert_close(f32::from(Fixed(0x7fff_0000)), 32767.);
        assert_close(f32::from(Fixed(0x7000_0001)), 28672.0001);
        assert_close(f32::from(Fixed(0x0001_0000)), 1.0);
        assert_close(f32::from(Fixed(0x0000_0000)), 0.0);
        assert_close(
            f32::from(Fixed(i32::from_be_bytes([0xff; 4]))),
            -0.000015259,
        );
        assert_close(f32::from(Fixed(0x7fff_ffff)), 32768.0);
    }
    #[test]
    fn fixed_from_f32() {
        assert_eq!(Fixed::from(32767.0_f32), Fixed(0x7fff_0000));
        assert_eq!(Fixed::from(28672.0001_f32), Fixed(0x7000_0000));
        assert_eq!(Fixed::from(1.0_f32), Fixed(0x0001_0000));
        assert_eq!(Fixed::from(-1.0_f32), Fixed(-65536));
        assert_eq!(Fixed::from(0.0_f32), Fixed(0x0000_0000));
        assert_eq!(Fixed::from(0.000015259_f32), Fixed(1));
        assert_eq!(Fixed::from(32768.0_f32), Fixed(-0x8000_0000));
        assert_eq!(Fixed::from(1.23_f32), Fixed(0x0001_3ae1));
        assert_close!(f32::from(Fixed::from(-1.4_f32)), -1.4, 1. / 65536.);
    }
    #[test]
    fn fixed_from_i32() {
        assert_eq!(Fixed::from(32767), Fixed(0x7fff_0000));
        assert_eq!(Fixed::from(28672), Fixed(0x7000_0000));
        assert_eq!(Fixed::from(1), Fixed(0x0001_0000));
        assert_eq!(Fixed::from(0), Fixed(0x0000_0000));
        assert_eq!(Fixed::from(-0), Fixed(0));
        assert_eq!(Fixed::from(32768), Fixed(-0x8000_0000));
    }
    #[test]
    fn fixed_from_f2dot14() {
        assert_eq!(Fixed::from(F2Dot14::from(0.5)), Fixed(0x0000_8000));
    }
    #[test]
    fn fixed_add() {
        assert_eq!(Fixed(10) + Fixed(20), Fixed(30));
        assert_fixed_close(Fixed::from(0.1) + Fixed::from(0.2), 0.3);
        assert_fixed_close(Fixed::from(-0.1) + Fixed::from(0.4), 0.3);
        assert_eq!(Fixed(i32::MAX) + Fixed(1), Fixed(-0x80000000)); // overflow
    }
    #[test]
    fn fixed_sub() {
        assert_eq!(Fixed(10) - Fixed(20), Fixed(-10));
        assert_fixed_close(Fixed::from(0.1) - Fixed::from(0.2), -0.1);
        assert_fixed_close(Fixed::from(-0.1) - Fixed::from(0.4), -0.5);
        assert_eq!(Fixed(i32::MIN) - Fixed(1), Fixed(0x7fffffff)); // underflow
    }
    #[test]
    fn fixed_mul() {
        assert_eq!(Fixed(0x2_0000) * Fixed(0x4_0000), Fixed(0x8_0000));
        assert_fixed_close(Fixed::from(0.1) * Fixed::from(0.2), 0.02);
        assert_fixed_close(Fixed::from(-0.1) * Fixed::from(0.4), -0.04);
    }
    #[test]
    fn fixed_div() {
        assert_eq!(Fixed(0x4_0000) / Fixed(0x2_0000), Fixed(0x2_0000));
        assert_fixed_close(Fixed::from(0.1) / Fixed::from(0.2), 0.5);
        assert_fixed_close(Fixed::from(-0.1) / Fixed::from(0.4), -0.25);
        assert_eq!(Fixed(0x4_0000) / Fixed(0), Fixed(0x7FFFFFFF)); // div 0
    }
    #[test]
    fn fixed_neg() {
        assert_eq!(-Fixed(0x4_0000), Fixed(-0x4_0000));
        assert_fixed_close(-Fixed::from(0.1), -0.1);
        assert_fixed_close(-Fixed::from(-0.25), 0.25);
        assert_eq!(-Fixed(0x7FFFFFFF), Fixed(-0x7FFFFFFF));
    }
    #[test]
    fn fixed_abs() {
        assert_fixed_close(Fixed::from(-1.0).abs(), 1.0);
        assert_fixed_close(Fixed::from(1.0).abs(), 1.0);
        assert_eq!(Fixed(-0x7FFFFFFF).abs(), Fixed(0x7FFFFFFF));
    }
    #[test]
    fn f2dot14_add() {
        assert_eq!(Fixed(10) + Fixed(20), Fixed(30));
        assert_f2dot14_close(F2Dot14::from(0.1) + F2Dot14::from(0.2), 0.3);
        assert_f2dot14_close(F2Dot14::from(-0.1) + F2Dot14::from(0.4), 0.3);
        assert_eq!(F2Dot14(i16::MAX) + F2Dot14(1), F2Dot14(-0x8000)); // overflow
    }
    #[test]
    fn f2dot14_sub() {
        assert_eq!(F2Dot14(10) - F2Dot14(20), F2Dot14(-10));
        assert_f2dot14_close(F2Dot14::from(0.1) - F2Dot14::from(0.2), -0.1);
        assert_f2dot14_close(F2Dot14::from(-0.1) - F2Dot14::from(0.4), -0.5);
        assert_eq!(F2Dot14(i16::MIN) - F2Dot14(1), F2Dot14(0x7fff)); // underflow
    }
    #[test]
    fn f2dot14_mul() {
        assert_f2dot14_close(F2Dot14::from(0.1) * F2Dot14::from(0.2), 0.02);
        assert_f2dot14_close(F2Dot14::from(-0.1) * F2Dot14::from(0.4), -0.04);
    }
    #[test]
    fn f2dot14_div() {
        assert_f2dot14_close(F2Dot14::from(0.1) / F2Dot14::from(0.2), 0.5);
        assert_f2dot14_close(F2Dot14::from(-0.1) / F2Dot14::from(0.4), -0.25);
        assert_eq!(F2Dot14(0x4_000) / F2Dot14(0), F2Dot14(0x7FFF)); // div 0
    }
    #[test]
    fn f2dot14_neg() {
        assert_eq!(-F2Dot14(0x1_000), F2Dot14(-0x1_000));
        assert_f2dot14_close(-F2Dot14::from(0.1), -0.1);
        assert_f2dot14_close(-F2Dot14::from(-0.25), 0.25);
        assert_eq!(-F2Dot14(0x7FFF), F2Dot14(-0x7FFF));
    }
    #[test]
    fn read_true_magic() {
        let buffer = read_fixture("tests/fonts/variable/Zycon.ttf");
        let fontfile = ReadScope::new(&buffer)
            .read::<OpenTypeFont<'_>>()
            .expect("error reading OpenTypeFile");
        let offset_table = match fontfile.data {
            OpenTypeData::Single(font) => font,
            OpenTypeData::Collection(_) => unreachable!(),
        };
        assert_eq!(offset_table.table_records.len(), 12);
    }
}