1
//! Central font handling support.
2

            
3
use std::borrow::Cow;
4
use std::convert::{self};
5
use std::sync::Arc;
6

            
7
use pathfinder_geometry::line_segment::LineSegment2F;
8
use pathfinder_geometry::rect::RectF;
9
use pathfinder_geometry::vector::Vector2F;
10
use tinyvec::tiny_vec;
11

            
12
use crate::big5::unicode_to_big5;
13
use crate::binary::read::ReadScope;
14
use crate::bitmap::cbdt::{CBDTTable, CBLCTable};
15
use crate::bitmap::sbix::Sbix as SbixTable;
16
use crate::bitmap::{BitDepth, BitmapGlyph};
17
use crate::cff::cff2::CFF2;
18
use crate::cff::outline::{CFF2Outlines, CFFOutlines};
19
use crate::cff::{CFFError, CFF};
20
use crate::context::Glyph;
21
use crate::error::{ParseError, ShapingError};
22
use crate::font::tables::ColrCpalTryBuilder;
23
use crate::glyph_info::GlyphNames;
24
use crate::gpos::Info;
25
use crate::gsub::{FeatureInfo, FeatureMask, GlyphOrigin, RawGlyph, RawGlyphFlags};
26
use crate::layout::morx;
27
use crate::layout::{new_layout_cache, GDEFTable, LayoutCache, LayoutTable, GPOS, GSUB};
28
use crate::macroman::char_to_macroman;
29
use crate::outline::{BoundingBoxSink, OutlineBuilder, OutlineSink};
30
use crate::scripts::preprocess_text;
31
use crate::tables::aat::DELETED_GLYPH;
32
use crate::tables::cmap::{Cmap, CmapSubtable, EncodingId, EncodingRecord, PlatformId};
33
use crate::tables::colr::{ColrTable, Painter};
34
use crate::tables::cpal::CpalTable;
35
use crate::tables::glyf::{BoundingBox, GlyfVisitorContext, LocaGlyf};
36
use crate::tables::kern::owned::KernTable;
37
use crate::tables::loca::{owned, LocaTable};
38
use crate::tables::morx::MorxTable;
39
use crate::tables::os2::Os2;
40
use crate::tables::svg::SvgTable;
41
use crate::tables::variable_fonts::fvar::{FvarAxisCount, FvarTable, Tuple, VariationAxisRecord};
42
use crate::tables::{
43
    kern, read_and_box_optional_table, read_and_box_table, FontTableProvider, HeadTable, HheaTable,
44
    MaxpTable,
45
};
46
use crate::unicode::{self, VariationSelector};
47
use crate::variations::{AxisNamesError, NamedAxis};
48
use crate::{cff, glyph_info, tag, variations, GlyphId, SafeFrom};
49
use crate::{gpos, gsub, DOTTED_CIRCLE};
50

            
51
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
52
pub enum Encoding {
53
    Unicode = 1,
54
    Symbol = 2,
55
    AppleRoman = 3,
56
    Big5 = 4,
57
}
58

            
59
#[derive(Copy, Clone, Eq, PartialEq, Debug)]
60
pub enum OutlineFormat {
61
    Glyf,
62
    Cff,
63
    Svg,
64
    None,
65
}
66

            
67
enum LazyLoad<T> {
68
    NotLoaded,
69
    Loaded(Option<T>),
70
}
71

            
72
#[derive(Copy, Clone, Eq, PartialEq, Debug)]
73
pub enum MatchingPresentation {
74
    Required,
75
    NotRequired,
76
}
77

            
78
/// For now `GlyphCache` only stores the index of U+25CC DOTTED CIRCLE. The intention is for this
79
/// to become a more general cache in the future.
80
///
81
/// `None` indicates that dotted circle has never been looked up. A value otherwise is the index of
82
/// the glyph.
83
struct GlyphCache(Option<(u16, VariationSelector)>);
84

            
85
/// Core type for loading a font in order to perform glyph mapping and font shaping.
86
pub struct Font<T: FontTableProvider> {
87
    pub font_table_provider: T,
88
    pub head_table: HeadTable,
89
    cmap_table: Box<[u8]>,
90
    pub maxp_table: MaxpTable,
91
    hmtx_table: Box<[u8]>,
92
    pub hhea_table: HheaTable,
93
    vmtx_table: LazyLoad<Arc<[u8]>>,
94
    vhea_table: LazyLoad<Arc<HheaTable>>,
95
    cmap_subtable_offset: usize,
96
    pub cmap_subtable_encoding: Encoding,
97
    gdef_cache: LazyLoad<Arc<GDEFTable>>,
98
    morx_cache: LazyLoad<Arc<tables::Morx>>,
99
    gsub_cache: LazyLoad<LayoutCache<GSUB>>,
100
    gpos_cache: LazyLoad<LayoutCache<GPOS>>,
101
    kern_cache: LazyLoad<Arc<KernTable>>,
102
    os2_us_first_char_index: LazyLoad<u16>,
103
    glyph_cache: GlyphCache,
104
    pub glyph_table_flags: GlyphTableFlags,
105
    loca_glyf: LocaGlyf,
106
    cff_cache: LazyLoad<Arc<tables::CFF>>,
107
    cff2_cache: LazyLoad<Arc<tables::CFF2>>,
108
    embedded_image_filter: GlyphTableFlags,
109
    embedded_images: LazyLoad<Arc<Images>>,
110
    axis_count: u16,
111
}
112

            
113
pub enum Images {
114
    Embedded {
115
        cblc: tables::CBLC,
116
        cbdt: tables::CBDT,
117
    },
118
    Colr(tables::ColrCpal),
119
    Sbix(tables::Sbix),
120
    Svg(tables::Svg),
121
}
122

            
123
// Inhibit warning: field `data` is never read on CFF and CFF2 structs
124
#[allow(unused)]
125
mod tables {
126
    use ouroboros::self_referencing;
127

            
128
    use crate::bitmap::cbdt::{CBDTTable, CBLCTable};
129
    use crate::bitmap::sbix::Sbix as SbixTable;
130
    use crate::cff::cff2::CFF2 as CFF2Table;
131
    use crate::cff::CFF as CFFTable;
132
    use crate::tables::colr::ColrTable;
133
    use crate::tables::cpal::CpalTable;
134
    use crate::tables::morx::MorxTable;
135
    use crate::tables::svg::SvgTable;
136

            
137
    #[self_referencing(pub_extras)]
138
    pub struct CFF {
139
        data: Box<[u8]>,
140
        #[borrows(data)]
141
        #[not_covariant]
142
        table: CFFTable<'this>,
143
    }
144

            
145
    #[self_referencing(pub_extras)]
146
    pub struct CFF2 {
147
        data: Box<[u8]>,
148
        #[borrows(data)]
149
        #[not_covariant]
150
        table: CFF2Table<'this>,
151
    }
152

            
153
    #[self_referencing(pub_extras)]
154
    pub struct CBLC {
155
        data: Box<[u8]>,
156
        #[borrows(data)]
157
        #[not_covariant]
158
        pub(crate) table: CBLCTable<'this>,
159
    }
160

            
161
    #[self_referencing(pub_extras)]
162
    pub struct CBDT {
163
        data: Box<[u8]>,
164
        #[borrows(data)]
165
        #[covariant]
166
        pub(crate) table: CBDTTable<'this>,
167
    }
168

            
169
    #[self_referencing(pub_extras)]
170
    pub struct ColrCpal {
171
        colr_data: Box<[u8]>,
172
        cpal_data: Box<[u8]>,
173
        #[borrows(colr_data)]
174
        #[not_covariant]
175
        pub(crate) colr: ColrTable<'this>,
176
        #[borrows(cpal_data)]
177
        #[not_covariant]
178
        pub(crate) cpal: CpalTable<'this>,
179
    }
180

            
181
    #[self_referencing(pub_extras)]
182
    pub struct Sbix {
183
        data: Box<[u8]>,
184
        #[borrows(data)]
185
        #[not_covariant]
186
        pub(crate) table: SbixTable<'this>,
187
    }
188

            
189
    #[self_referencing(pub_extras)]
190
    pub struct Svg {
191
        data: Box<[u8]>,
192
        #[borrows(data)]
193
        #[not_covariant]
194
        pub(crate) table: SvgTable<'this>,
195
    }
196

            
197
    #[self_referencing(pub_extras)]
198
    pub struct Morx {
199
        data: Box<[u8]>,
200
        #[borrows(data)]
201
        #[not_covariant]
202
        pub(crate) table: MorxTable<'this>,
203
    }
204
}
205

            
206
#[enumflags2::bitflags]
207
#[repr(u8)]
208
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
209
#[allow(clippy::upper_case_acronyms)]
210
pub enum GlyphTableFlag {
211
    GLYF = 1 << 0,
212
    CFF = 1 << 1,
213
    SVG = 1 << 2,
214
    SBIX = 1 << 3,
215
    CBDT = 1 << 4,
216
    EBDT = 1 << 5,
217
    CFF2 = 1 << 6,
218
    COLR = 1 << 7,
219
}
220

            
221
pub type GlyphTableFlags = enumflags2::BitFlags<GlyphTableFlag>;
222

            
223
impl GlyphTableFlag {
224
    /// Return the OpenType table tag corresponding to this flag.
225
179712
    pub fn tag(self) -> u32 {
226
179712
        match self {
227
22464
            GlyphTableFlag::GLYF => tag::GLYF,
228
22464
            GlyphTableFlag::CFF => tag::CFF,
229
22464
            GlyphTableFlag::CFF2 => tag::CFF2,
230
22464
            GlyphTableFlag::COLR => tag::COLR,
231
22464
            GlyphTableFlag::SVG => tag::SVG,
232
22464
            GlyphTableFlag::SBIX => tag::SBIX,
233
22464
            GlyphTableFlag::CBDT => tag::CBDT,
234
22464
            GlyphTableFlag::EBDT => tag::EBDT,
235
        }
236
179712
    }
237
}
238

            
239
impl<T: FontTableProvider> Font<T> {
240
    /// Construct a new instance from a type that can supply font tables.
241
21892
    pub fn new(provider: T) -> Result<Font<T>, ParseError> {
242
21892
        let cmap_table = read_and_box_table(&provider, tag::CMAP)?;
243

            
244
21892
        match charmap_info(&cmap_table)? {
245
21892
            Some((cmap_subtable_encoding, cmap_subtable_offset)) => {
246
21892
                let head_table =
247
21892
                    ReadScope::new(&provider.read_table_data(tag::HEAD)?).read::<HeadTable>()?;
248
21892
                let maxp_table =
249
21892
                    ReadScope::new(&provider.read_table_data(tag::MAXP)?).read::<MaxpTable>()?;
250
21892
                let hmtx_table = read_and_box_table(&provider, tag::HMTX)?;
251
21892
                let hhea_table =
252
21892
                    ReadScope::new(&provider.read_table_data(tag::HHEA)?).read::<HheaTable>()?;
253
21892
                let fvar_data = provider.table_data(tag::FVAR)?;
254
21892
                let fvar_axis_count = fvar_data
255
21892
                    .as_deref()
256
21892
                    .map(|data| ReadScope::new(data).read::<FvarAxisCount>())
257
21892
                    .transpose()?
258
21892
                    .unwrap_or(0);
259

            
260
21892
                let embedded_image_filter = GlyphTableFlag::SVG
261
21892
                    | GlyphTableFlag::SBIX
262
21892
                    | GlyphTableFlag::CBDT
263
21892
                    | GlyphTableFlag::COLR;
264
21892
                let mut glyph_table_flags = GlyphTableFlags::empty();
265
175136
                for flag in GlyphTableFlags::all() {
266
175136
                    if provider.has_table(flag.tag()) {
267
21892
                        glyph_table_flags |= flag
268
153244
                    }
269
                }
270

            
271
21892
                Ok(Font {
272
21892
                    font_table_provider: provider,
273
21892
                    head_table,
274
21892
                    cmap_table,
275
21892
                    maxp_table,
276
21892
                    hmtx_table,
277
21892
                    hhea_table,
278
21892
                    vmtx_table: LazyLoad::NotLoaded,
279
21892
                    vhea_table: LazyLoad::NotLoaded,
280
21892
                    cmap_subtable_offset: usize::safe_from(cmap_subtable_offset),
281
21892
                    cmap_subtable_encoding,
282
21892
                    gdef_cache: LazyLoad::NotLoaded,
283
21892
                    morx_cache: LazyLoad::NotLoaded,
284
21892
                    gsub_cache: LazyLoad::NotLoaded,
285
21892
                    gpos_cache: LazyLoad::NotLoaded,
286
21892
                    kern_cache: LazyLoad::NotLoaded,
287
21892
                    os2_us_first_char_index: LazyLoad::NotLoaded,
288
21892
                    glyph_cache: GlyphCache::new(),
289
21892
                    glyph_table_flags,
290
21892
                    loca_glyf: LocaGlyf::new(),
291
21892
                    cff_cache: LazyLoad::NotLoaded,
292
21892
                    cff2_cache: LazyLoad::NotLoaded,
293
21892
                    embedded_image_filter,
294
21892
                    embedded_images: LazyLoad::NotLoaded,
295
21892
                    axis_count: fvar_axis_count,
296
21892
                })
297
            }
298
            None => Err(ParseError::UnsuitableCmap),
299
        }
300
21892
    }
301

            
302
    /// Returns the number of glyphs in the font.
303
21892
    pub fn num_glyphs(&self) -> u16 {
304
21892
        self.maxp_table.num_glyphs
305
21892
    }
306

            
307
    /// Set the embedded image table filter.
308
    ///
309
    /// When determining if a font contains embedded images, as well as retrieving images this
310
    /// value is used to set which tables are consulted. By default, it is set to only consult
311
    /// tables that can contain colour images (`CBDT`/`CBLC`, `sbix`, and `SVG`). You can change
312
    /// the value to exclude certain tables or opt into tables that can only contain B&W images
313
    /// (`EBDT`/`EBLC`).
314
    pub fn set_embedded_image_filter(&mut self, flags: GlyphTableFlags) {
315
        self.embedded_image_filter = flags;
316
    }
317

            
318
    /// Look up the glyph index for the supplied character in the font.
319
    pub fn lookup_glyph_index(
320
        &mut self,
321
        ch: char,
322
        match_presentation: MatchingPresentation,
323
        variation_selector: Option<VariationSelector>,
324
    ) -> (u16, VariationSelector) {
325
        self.glyph_cache.get(ch).unwrap_or_else(|| {
326
            let (glyph_index, used_variation) =
327
                self.map_unicode_to_glyph(ch, match_presentation, variation_selector);
328
            self.glyph_cache.put(ch, glyph_index, used_variation);
329
            (glyph_index, used_variation)
330
        })
331
    }
332

            
333
    /// Convenience method to shape the supplied glyphs.
334
    ///
335
    /// The method maps applies glyph substitution (`gsub`) and glyph positioning (`gpos`). Use
336
    /// `map_glyphs` to turn text into glyphs that can be accepted by this method.
337
    ///
338
    /// **Arguments:**
339
    ///
340
    /// * `glyphs`: the glyphs to be shaped in logical order.
341
    /// * `script_tag`: the [OpenType script tag](https://docs.microsoft.com/en-us/typography/opentype/spec/scripttags) of the text.
342
    /// * `opt_lang_tag`: the [OpenType language tag](https://docs.microsoft.com/en-us/typography/opentype/spec/languagetags) of the text.
343
    /// * `features`: the [OpenType features](https://docs.microsoft.com/en-us/typography/opentype/spec/featuretags) to enable.
344
    /// * `kerning`: when applying `gpos` if this argument is `true` the `kern` OpenType feature
345
    ///   is enabled for non-complex scripts. If it is `false` then the `kern` feature is not
346
    ///   enabled for non-complex scripts.
347
    ///
348
    /// **Error Handling:**
349
    ///
350
    /// This method will continue on in the face of errors, applying what it can. If no errors are
351
    /// encountered this method returns `Ok(Vec<Info>)`. If one or more errors are encountered
352
    /// `Err((ShapingError, Vec<Info>))` is returned. The first error encountered is returned
353
    /// as the first item of the tuple. `Vec<Info>` is returned as the second item of the tuple,
354
    /// allowing consumers of the shaping output to proceed even if errors are encountered.
355
    /// However, in the error case the glyphs might not have had GPOS or GSUB applied.
356
    ///
357
    /// ## Example
358
    ///
359
    /// ```
360
    /// use allsorts::binary::read::ReadScope;
361
    /// use allsorts::font::MatchingPresentation;
362
    /// use allsorts::font_data::FontData;
363
    /// use allsorts::gsub::{self, FeatureMask, FeatureMaskExt};
364
    /// use allsorts::DOTTED_CIRCLE;
365
    /// use allsorts::{tag, Font};
366
    ///
367
    /// let script = tag::LATN;
368
    /// let lang = tag::DFLT;
369
    /// let variation_tuple = None;
370
    /// let buffer = std::fs::read("tests/fonts/opentype/Klei.otf").expect("unable to read Klei.otf");
371
    /// let scope = ReadScope::new(&buffer);
372
    /// let font_file = scope.read::<FontData<'_>>().expect("unable to parse font");
373
    /// // Use a different index to access other fonts in a font collection (E.g. TTC)
374
    /// let provider = font_file
375
    ///     .table_provider(0)
376
    ///     .expect("unable to create table provider");
377
    /// let mut font = Font::new(provider).expect("unable to load font tables");
378
    ///
379
    /// // Klei ligates ff
380
    /// let glyphs = font.map_glyphs(
381
    ///     "Shaping in a jiffy.",
382
    ///     script,
383
    ///     MatchingPresentation::NotRequired,
384
    /// );
385
    /// let glyph_infos = font
386
    ///     .shape(
387
    ///         glyphs,
388
    ///         script,
389
    ///         Some(lang),
390
    ///         FeatureMask::default_mask(),
391
    ///         &[],
392
    ///         variation_tuple,
393
    ///         true,
394
    ///     )
395
    ///     .expect("error shaping text");
396
    /// // We expect ff to be ligated so the number of glyphs (18) should be one less than the
397
    /// // number of input characters (19).
398
    /// assert_eq!(glyph_infos.len(), 18);
399
    /// ```
400
    pub fn shape(
401
        &mut self,
402
        mut glyphs: Vec<RawGlyph<()>>,
403
        script_tag: u32,
404
        opt_lang_tag: Option<u32>,
405
        feature_mask: FeatureMask,
406
        custom_features: &[FeatureInfo],
407
        tuple: Option<Tuple<'_>>,
408
        kerning: bool,
409
    ) -> Result<Vec<Info>, (ShapingError, Vec<Info>)> {
410
        // We forge ahead in the face of errors applying what we can, returning the first error
411
        // encountered.
412
        let mut err: Option<ShapingError> = None;
413
        let opt_gsub_cache = check_set_err(self.gsub_cache(), &mut err);
414
        let opt_gpos_cache = check_set_err(self.gpos_cache(), &mut err);
415
        let opt_gdef_table = check_set_err(self.gdef_table(), &mut err);
416
        let opt_morx_table = check_set_err(self.morx_table(), &mut err);
417
        let opt_gdef_table = opt_gdef_table.as_ref().map(Arc::as_ref);
418
        let opt_kern_table = check_set_err(self.kern_table(), &mut err);
419
        let opt_kern_table = opt_kern_table
420
            .as_ref()
421
            .map(|x| kern::KernTable::from(x.as_ref()));
422
        let (dotted_circle_index, _) =
423
            self.lookup_glyph_index(DOTTED_CIRCLE, MatchingPresentation::NotRequired, None);
424

            
425
        let num_glyphs = self.num_glyphs();
426
        let mut applied_morx = false;
427
        if let Some(gsub_cache) = opt_gsub_cache {
428
            // Apply gsub if table is present
429
            let res = gsub::apply(
430
                dotted_circle_index,
431
                &gsub_cache,
432
                opt_gdef_table,
433
                script_tag,
434
                opt_lang_tag,
435
                feature_mask,
436
                custom_features,
437
                tuple,
438
                num_glyphs,
439
                &mut glyphs,
440
            );
441
            check_set_err(res, &mut err);
442
        } else if let Some(morx_cache) = opt_morx_table {
443
            // Otherwise apply morx if table is present
444
            morx_cache.with_table(|morx_table: &MorxTable<'_>| {
445
                let res = morx::apply(morx_table, &mut glyphs, feature_mask, script_tag);
446
                check_set_err(res, &mut err);
447

            
448
                applied_morx = true;
449
            })
450
        }
451

            
452
        // Apply gpos if table is present
453
        let mut infos = Info::init_from_glyphs(opt_gdef_table, glyphs);
454
        if let Some(gpos_cache) = opt_gpos_cache {
455
            // Remove residual DELETED_GLYPHs created during morx processing, _before_ applying
456
            // GPOS. These glyphs interfere with mark-to-base attachment, causing certain Apple
457
            // Color Emoji to be rendered incorrectly.
458
            if applied_morx {
459
                infos.retain(|i| i.get_glyph_index() != DELETED_GLYPH);
460
            }
461

            
462
            let res = gpos::apply(
463
                &gpos_cache,
464
                opt_gdef_table,
465
                opt_kern_table,
466
                kerning,
467
                feature_mask,
468
                custom_features,
469
                tuple,
470
                script_tag,
471
                opt_lang_tag,
472
                &mut infos,
473
            );
474
            check_set_err(res, &mut err);
475
        } else {
476
            let mut has_cross_stream = false;
477
            if let Some(kern_table) = opt_kern_table {
478
                let res = kern::apply(&kern_table, script_tag, &mut infos);
479
                has_cross_stream = check_set_err(res, &mut err);
480
            }
481

            
482
            // Remove residual DELETED_GLYPHs created during morx processing, _after_ applying kern,
483
            // as these glyphs seem to be necessary for correct kerning (e.g. Apple's Geeza Pro and
484
            // Waseem fonts).
485
            if applied_morx {
486
                infos.retain(|i| i.get_glyph_index() != DELETED_GLYPH);
487
            }
488

            
489
            // Positioning glyphs on the cross-axis implies mark positioning of sorts, so disable
490
            // fallback mark positioning in those cases.
491
            if !has_cross_stream {
492
                gpos::apply_fallback_mark_positioning(&mut infos);
493
            }
494
        }
495

            
496
        match err {
497
            Some(err) => Err((err, infos)),
498
            None => Ok(infos),
499
        }
500
    }
501

            
502
    /// Map text to glyphs.
503
    ///
504
    /// This method maps text into glyphs, which can then be passed to `shape`. The text is
505
    /// processed in logical order, there is no need to reorder text before calling this method.
506
    ///
507
    /// The `match_presentation` argument controls glyph mapping in the presence of emoji/text
508
    /// variation selectors. If `MatchingPresentation::NotRequired` is passed then glyph mapping
509
    /// will succeed if the font contains a mapping for a given character, regardless of whether
510
    /// it has the tables necessary to support the requested presentation. If
511
    /// `MatchingPresentation::Required` is passed then a character with emoji presentation,
512
    /// either by default or requested via variation selector will only map to a glyph if the font
513
    /// has mapping for the character, and it has the necessary tables for color emoji.
514
    pub fn map_glyphs(
515
        &mut self,
516
        text: &str,
517
        script_tag: u32,
518
        match_presentation: MatchingPresentation,
519
    ) -> Vec<RawGlyph<()>> {
520
        let mut chars = text.chars().collect();
521
        preprocess_text(&mut chars, script_tag);
522

            
523
        // We look ahead in the char stream for variation selectors. If one is found it is used for
524
        // mapping the current glyph. When a variation selector is reached in the stream it is
525
        // skipped as it was handled as part of the preceding character.
526
        let mut glyphs = Vec::with_capacity(chars.len());
527
        let mut chars_iter = chars.into_iter().peekable();
528
        while let Some(ch) = chars_iter.next() {
529
            match VariationSelector::try_from(ch) {
530
                Ok(_) => {} // filter out variation selectors
531
                Err(()) => {
532
                    let vs = chars_iter
533
                        .peek()
534
                        .and_then(|&next| VariationSelector::try_from(next).ok());
535
                    let (glyph_index, used_variation) =
536
                        self.lookup_glyph_index(ch, match_presentation, vs);
537
                    let glyph = RawGlyph {
538
                        unicodes: tiny_vec![[char; 1] => ch],
539
                        glyph_index,
540
                        liga_component_pos: 0,
541
                        glyph_origin: GlyphOrigin::Char(ch),
542
                        flags: RawGlyphFlags::empty(),
543
                        extra_data: (),
544
                        variation: Some(used_variation),
545
                    };
546
                    glyphs.push(glyph);
547
                }
548
            }
549
        }
550
        glyphs.shrink_to_fit();
551
        glyphs
552
    }
553

            
554
    /// True if the font has one or more variation axes.
555
    pub fn is_variable(&self) -> bool {
556
        self.axis_count > 0
557
    }
558

            
559
    pub fn variation_axes(&self) -> Result<Vec<VariationAxisRecord>, ParseError> {
560
        let table = self.font_table_provider.read_table_data(tag::FVAR)?;
561
        let fvar = ReadScope::new(&table).read::<FvarTable<'_>>()?;
562
        Ok(fvar.axes().collect())
563
    }
564

            
565
    fn map_glyph(&self, char_code: u32) -> u16 {
566
        match ReadScope::new(self.cmap_subtable_data()).read::<CmapSubtable<'_>>() {
567
            // TODO: Cache the parsed CmapSubtable
568
            Ok(cmap_subtable) => match cmap_subtable.map_glyph(char_code) {
569
                Ok(Some(glyph_index)) => glyph_index,
570
                _ => 0,
571
            },
572
            Err(_err) => 0,
573
        }
574
    }
575

            
576
    fn map_unicode_to_glyph(
577
        &mut self,
578
        ch: char,
579
        match_presentation: MatchingPresentation,
580
        variation_selector: Option<VariationSelector>,
581
    ) -> (u16, VariationSelector) {
582
        // If emoji or text presentation are explicitly requested then require matching presentation
583
        let match_presentation = if matches!(
584
            variation_selector,
585
            Some(VariationSelector::VS15 | VariationSelector::VS16)
586
        ) {
587
            MatchingPresentation::Required
588
        } else {
589
            match_presentation
590
        };
591

            
592
        let used_selector = Self::resolve_default_presentation(ch, variation_selector);
593
        let glyph_index = match self.cmap_subtable_encoding {
594
            Encoding::Unicode => {
595
                self.lookup_glyph_index_with_variation(ch as u32, match_presentation, used_selector)
596
            }
597
            Encoding::Symbol => {
598
                let char_code = self.legacy_symbol_char_code(ch);
599
                self.lookup_glyph_index_with_variation(char_code, match_presentation, used_selector)
600
            }
601
            Encoding::AppleRoman => match char_to_macroman(ch) {
602
                Some(char_code) => self.lookup_glyph_index_with_variation(
603
                    u32::from(char_code),
604
                    match_presentation,
605
                    used_selector,
606
                ),
607
                None => {
608
                    let char_code = self.legacy_symbol_char_code(ch);
609
                    self.lookup_glyph_index_with_variation(
610
                        char_code,
611
                        match_presentation,
612
                        used_selector,
613
                    )
614
                }
615
            },
616
            Encoding::Big5 => match unicode_to_big5(ch) {
617
                Some(char_code) => self.lookup_glyph_index_with_variation(
618
                    u32::from(char_code),
619
                    match_presentation,
620
                    used_selector,
621
                ),
622
                None => 0,
623
            },
624
        };
625
        (glyph_index, used_selector)
626
    }
627

            
628
    // The symbol encoding was created to support fonts with arbitrary ornaments or symbols not
629
    // supported in Unicode or other standard encodings. A format 4 subtable would be used,
630
    // typically with up to 224 graphic characters assigned at code positions beginning with
631
    // 0xF020. This corresponds to a sub-range within the Unicode Private-Use Area (PUA), though
632
    // this is not a Unicode encoding. In legacy usage, some applications would represent the
633
    // symbol characters in text using a single-byte encoding, and then map 0x20 to the
634
    // OS/2.usFirstCharIndex value in the font.
635
    // — https://docs.microsoft.com/en-us/typography/opentype/spec/cmap#encoding-records-and-encodings
636
    fn legacy_symbol_char_code(&mut self, ch: char) -> u32 {
637
        let char_code0 = if ch < '\u{F000}' || ch > '\u{F0FF}' {
638
            ch as u32
639
        } else {
640
            ch as u32 - 0xF000
641
        };
642
        let provider = &self.font_table_provider;
643
        let first_char = if let Ok(Some(us_first_char_index)) =
644
            self.os2_us_first_char_index.get_or_load(|| {
645
                load_os2_table(provider)?
646
                    .map(|os2| Ok(os2.us_first_char_index))
647
                    .transpose()
648
            }) {
649
            u32::from(us_first_char_index)
650
        } else {
651
            0x20
652
        };
653
        (char_code0 + first_char) - 0x20 // Perform subtraction last to avoid underflow.
654
    }
655

            
656
    fn lookup_glyph_index_with_variation(
657
        &mut self,
658
        char_code: u32,
659
        match_presentation: MatchingPresentation,
660
        variation_selector: VariationSelector,
661
    ) -> u16 {
662
        if match_presentation == MatchingPresentation::Required {
663
            // This match aims to only return a non-zero index if the font supports the requested
664
            // presentation. So, if you want the glyph index for a code point using emoji presentation,
665
            // the font must have suitable tables. On the flip side, if you want a glyph with text
666
            // presentation then the font must have glyf or CFF outlines.
667
            if (variation_selector == VariationSelector::VS16 && self.has_embedded_images())
668
                || (variation_selector == VariationSelector::VS15 && self.has_glyph_outlines())
669
            {
670
                self.map_glyph(char_code)
671
            } else {
672
                0
673
            }
674
        } else {
675
            self.map_glyph(char_code)
676
        }
677
    }
678

            
679
    fn resolve_default_presentation(
680
        ch: char,
681
        variation_selector: Option<VariationSelector>,
682
    ) -> VariationSelector {
683
        variation_selector.unwrap_or_else(|| {
684
            // `None` indicates no selector present so for emoji determine the default presentation.
685
            if unicode::bool_prop_emoji_presentation(ch) {
686
                VariationSelector::VS16
687
            } else {
688
                VariationSelector::VS15
689
            }
690
        })
691
    }
692

            
693
    pub fn glyph_names<'a>(&self, ids: &[u16]) -> Vec<Cow<'a, str>> {
694
        let post = read_and_box_optional_table(&self.font_table_provider, tag::POST)
695
            .ok()
696
            .and_then(convert::identity);
697
        let cmap = ReadScope::new(self.cmap_subtable_data())
698
            .read::<CmapSubtable<'_>>()
699
            .ok()
700
            .map(|table| (self.cmap_subtable_encoding, table));
701
        let glyph_namer = GlyphNames::new(&cmap, post);
702
        glyph_namer.unique_glyph_names(ids)
703
    }
704

            
705
    /// Returns the names of the variation axes in the font.
706
    pub fn axis_names<'a>(&self) -> Result<Vec<NamedAxis<'a>>, AxisNamesError> {
707
        variations::axis_names(&self.font_table_provider)
708
    }
709

            
710
    /// Find an image matching the supplied criteria.
711
    ///
712
    /// * `glyph_index` is the glyph to lookup.
713
    /// * `target_ppem` is the desired size. If an exact match can't be found the nearest one will
714
    ///    be returned, favouring being oversize vs. undersized.
715
    /// * `max_bit_depth` is the maximum accepted bit depth of the bitmap to return. If you accept
716
    ///   all bit depths then use `BitDepth::ThirtyTwo`.
717
    pub fn lookup_glyph_image(
718
        &mut self,
719
        glyph_index: GlyphId,
720
        target_ppem: u16,
721
        max_bit_depth: BitDepth,
722
    ) -> Result<Option<BitmapGlyph>, ParseError> {
723
        let embedded_bitmaps = match self.embedded_images()? {
724
            Some(embedded_bitmaps) => embedded_bitmaps,
725
            None => return Ok(None),
726
        };
727
        match embedded_bitmaps.as_ref() {
728
            Images::Embedded { cblc, cbdt } => cblc.with_table(|cblc: &CBLCTable<'_>| {
729
                let target_ppem = if target_ppem > u16::from(u8::MAX) {
730
                    u8::MAX
731
                } else {
732
                    target_ppem as u8
733
                };
734
                let bitmap = match cblc.find_strike(glyph_index, target_ppem, max_bit_depth) {
735
                    Some(matching_strike) => {
736
                        let cbdt = cbdt.borrow_table();
737
                        matching_strike.bitmap(cbdt)?.map(|bitmap| {
738
                            BitmapGlyph::try_from((
739
                                &matching_strike.bitmap_size.inner,
740
                                bitmap,
741
                                glyph_index,
742
                            ))
743
                        })
744
                    }
745
                    None => None,
746
                };
747
                bitmap.transpose()
748
            }),
749
            Images::Colr { .. } => Ok(None),
750
            Images::Sbix(sbix) => self.lookup_sbix_glyph_bitmap(
751
                sbix,
752
                false,
753
                false,
754
                glyph_index,
755
                target_ppem,
756
                max_bit_depth,
757
            ),
758
            Images::Svg(svg) => self.lookup_svg_glyph(svg, glyph_index),
759
        }
760
    }
761

            
762
    pub fn visit_colr_glyph<P>(
763
        &mut self,
764
        glyph_id: u16,
765
        palette_index: u16,
766
        painter: &mut P,
767
    ) -> Result<(), P::Error>
768
    where
769
        P: Painter,
770
        P::Error: From<ParseError> + From<cff::CFFError>,
771
    {
772
        let Some(embedded_images) = self.embedded_images()? else {
773
            return Err(ParseError::MissingValue.into());
774
        };
775

            
776
        if self.glyph_table_flags.contains(GlyphTableFlag::CFF2) {
777
            let cff2 = self
778
                .cff2_table()?
779
                .ok_or(ParseError::MissingTable(tag::CFF2))?;
780

            
781
            cff2.with(|cff2| {
782
                let mut cff2_outlines = CFF2Outlines { table: cff2.table };
783
                Self::visit_colr_glyph_inner(
784
                    glyph_id,
785
                    palette_index,
786
                    painter,
787
                    &mut cff2_outlines,
788
                    &embedded_images,
789
                )
790
            })
791
        } else if self.glyph_table_flags.contains(GlyphTableFlag::CFF) {
792
            let cff = self
793
                .cff_table()?
794
                .ok_or(ParseError::MissingTable(tag::CFF))?;
795

            
796
            cff.with(|cff| {
797
                let mut cff_outlines = CFFOutlines { table: cff.table };
798
                Self::visit_colr_glyph_inner(
799
                    glyph_id,
800
                    palette_index,
801
                    painter,
802
                    &mut cff_outlines,
803
                    &embedded_images,
804
                )
805
            })
806
        } else if self.glyph_table_flags.contains(GlyphTableFlag::GLYF) {
807
            self.maybe_load_loca_glyf()?;
808
            let mut context = GlyfVisitorContext::new(&mut self.loca_glyf, None);
809

            
810
            Self::visit_colr_glyph_inner(
811
                glyph_id,
812
                palette_index,
813
                painter,
814
                &mut context,
815
                &embedded_images,
816
            )
817
        } else {
818
            Err(ParseError::MissingValue.into())
819
        }
820
    }
821

            
822
    fn visit_colr_glyph_inner<P, G>(
823
        glyph_id: u16,
824
        palette_index: u16,
825
        painter: &mut P,
826
        glyphs: &mut G,
827
        embedded_images: &Images,
828
    ) -> Result<(), P::Error>
829
    where
830
        P: Painter,
831
        P::Error: From<ParseError> + From<G::Error>,
832
        G: OutlineBuilder,
833
    {
834
        match embedded_images {
835
            Images::Colr(tables) => tables.with(|fields| {
836
                let palette = fields
837
                    .cpal
838
                    .palette(palette_index)
839
                    .ok_or(ParseError::BadIndex)?;
840
                let Some(glyph) = fields.colr.lookup(glyph_id)? else {
841
                    return Ok(());
842
                };
843
                glyph.visit(painter, glyphs, palette)
844
            }),
845
            _ => Err(ParseError::MissingTable(tag::COLR).into()),
846
        }
847
    }
848

            
849
    /// Check if there is COLR data for a glyph
850
    pub fn has_colr(&mut self, glyph_id: u16) -> Result<bool, ParseError> {
851
        let Some(embedded_images) = self.embedded_images()? else {
852
            return Ok(false);
853
        };
854

            
855
        match embedded_images.as_ref() {
856
            Images::Colr(tables) => tables.with_colr(|colr| Ok(colr.lookup(glyph_id)?.is_some())),
857
            _ => Ok(false),
858
        }
859
    }
860

            
861
    /// Retrieve the clip box for a COLR glyph.
862
    ///
863
    /// If the `COLR` table does not supply a clip list or there is no clip box for this
864
    /// glyph, then `Ok(None)` is returned.
865
    pub fn colr_clip_box(&mut self, glyph_id: u16) -> Result<Option<RectF>, ParseError> {
866
        let Some(embedded_images) = self.embedded_images()? else {
867
            return Err(ParseError::MissingValue);
868
        };
869

            
870
        match embedded_images.as_ref() {
871
            Images::Colr(tables) => tables.with_colr(|colr| {
872
                let glyph = colr.lookup(glyph_id)?.ok_or(ParseError::BadIndex)?;
873
                glyph.clip_box()
874
            }),
875
            _ => Err(ParseError::MissingValue),
876
        }
877
    }
878

            
879
    /// Perform sbix lookup with `dupe` and `flip` handling.
880
    ///
881
    /// The `dupe` flag indicates if this is a dupe lookup or not. The (Apple-specific) `flip`
882
    /// flag indicates that a glyph's bitmap data should be flipped horizontally. In both cases,
883
    /// to avoid potential infinite recursion we only follow one level of indirection.
884
    fn lookup_sbix_glyph_bitmap(
885
        &self,
886
        sbix: &tables::Sbix,
887
        dupe: bool,
888
        flip: bool,
889
        glyph_index: GlyphId,
890
        target_ppem: u16,
891
        max_bit_depth: BitDepth,
892
    ) -> Result<Option<BitmapGlyph>, ParseError> {
893
        sbix.with_table(|sbix_table: &SbixTable<'_>| {
894
            match sbix_table.find_strike(glyph_index, target_ppem, max_bit_depth) {
895
                Some(strike) => {
896
                    match strike.read_glyph(glyph_index)? {
897
                        Some(ref glyph) if glyph.graphic_type == tag::DUPE => {
898
                            // The special graphicType of 'dupe' indicates that the data field
899
                            // contains a uint16, big-endian glyph ID. The bitmap data for the
900
                            // indicated glyph should be used for the current glyph.
901
                            // — https://docs.microsoft.com/en-us/typography/opentype/spec/sbix#glyph-data
902
                            if dupe {
903
                                // We're already inside a `dupe` lookup and have encountered another
904
                                Ok(None)
905
                            } else {
906
                                // Try again with the glyph id stored in data
907
                                let dupe_glyph_index =
908
                                    ReadScope::new(glyph.data).ctxt().read_u16be()?;
909
                                self.lookup_sbix_glyph_bitmap(
910
                                    sbix,
911
                                    true,
912
                                    flip,
913
                                    dupe_glyph_index,
914
                                    target_ppem,
915
                                    max_bit_depth,
916
                                )
917
                            }
918
                        }
919
                        Some(ref glyph) if glyph.graphic_type == tag::FLIP => {
920
                            // The special graphicType of 'flip' indicates that the bitmap data
921
                            // for a glyph should be flipped horizontally. This feature is
922
                            // undocumented by Apple, but is already in use in Apple Color Emoji.
923
                            if flip {
924
                                // We're already inside a `flip` lookup and have encountered another
925
                                Ok(None)
926
                            } else {
927
                                // Try again with the glyph id stored in data
928
                                let flip_glyph_index =
929
                                    ReadScope::new(glyph.data).ctxt().read_u16be()?;
930
                                self.lookup_sbix_glyph_bitmap(
931
                                    sbix,
932
                                    dupe,
933
                                    true,
934
                                    flip_glyph_index,
935
                                    target_ppem,
936
                                    max_bit_depth,
937
                                )
938
                            }
939
                        }
940
                        Some(glyph) => {
941
                            Ok(Some(BitmapGlyph::from((strike, &glyph, glyph_index, flip))))
942
                        }
943
                        None => Ok(None),
944
                    }
945
                }
946
                None => Ok(None),
947
            }
948
        })
949
    }
950

            
951
    fn lookup_svg_glyph(
952
        &self,
953
        svg: &tables::Svg,
954
        glyph_index: GlyphId,
955
    ) -> Result<Option<BitmapGlyph>, ParseError> {
956
        svg.with_table(
957
            |svg_table: &SvgTable<'_>| match svg_table.lookup_glyph(glyph_index)? {
958
                Some(svg_record) => BitmapGlyph::try_from((&svg_record, glyph_index)).map(Some),
959
                None => Ok(None),
960
            },
961
        )
962
    }
963

            
964
    fn embedded_images(&mut self) -> Result<Option<Arc<Images>>, ParseError> {
965
        let provider = &self.font_table_provider;
966
        let num_glyphs = usize::from(self.maxp_table.num_glyphs);
967
        let tables_to_check = self.glyph_table_flags & self.embedded_image_filter;
968
        self.embedded_images.get_or_load(|| {
969
            if tables_to_check.contains(GlyphTableFlag::COLR) {
970
                let images = load_colr_cpal(provider).map(Images::Colr)?;
971
                Ok(Some(Arc::new(images)))
972
            } else if tables_to_check.contains(GlyphTableFlag::SVG) {
973
                let images = load_svg(provider).map(Images::Svg)?;
974
                Ok(Some(Arc::new(images)))
975
            } else if tables_to_check.contains(GlyphTableFlag::CBDT) {
976
                let images = load_cblc_cbdt(provider, tag::CBLC, tag::CBDT)
977
                    .map(|(cblc, cbdt)| Images::Embedded { cblc, cbdt })?;
978
                Ok(Some(Arc::new(images)))
979
            } else if tables_to_check.contains(GlyphTableFlag::SBIX) {
980
                let images = load_sbix(provider, num_glyphs).map(Images::Sbix)?;
981
                Ok(Some(Arc::new(images)))
982
            } else if tables_to_check.contains(GlyphTableFlag::EBDT) {
983
                let images =
984
                    load_cblc_cbdt(provider, tag::EBLC, tag::EBDT).map(|(eblc, ebdt)| {
985
                        Images::Embedded {
986
                            cblc: eblc,
987
                            cbdt: ebdt,
988
                        }
989
                    })?;
990
                Ok(Some(Arc::new(images)))
991
            } else {
992
                Ok(None)
993
            }
994
        })
995
    }
996

            
997
    /// Returns `true` if the font contains embedded images in supported tables.
998
    ///
999
    /// Allsorts supports extracting images from `CBDT`/`CBLC`, `sbix`, and `SVG` tables.
    /// If any of these tables are present and parsable then this method returns `true`.
    pub fn has_embedded_images(&mut self) -> bool {
        matches!(self.embedded_images(), Ok(Some(_)))
    }
    /// Returns `true` if the font contains vector glyph outlines in supported tables.
    ///
    /// Supported tables are `glyf`, `CFF`, `CFF2`.
    pub fn has_glyph_outlines(&self) -> bool {
        self.glyph_table_flags
            .intersects(GlyphTableFlag::GLYF | GlyphTableFlag::CFF | GlyphTableFlag::CFF2)
    }
    /// Returns the horizontal advance of the supplied glyph index.
    ///
    /// Will return `None` if there are errors encountered reading the `hmtx` table or there is
    /// no entry for the glyph index.
    pub fn horizontal_advance(&mut self, glyph: GlyphId) -> Option<u16> {
        glyph_info::advance(&self.maxp_table, &self.hhea_table, &self.hmtx_table, glyph).ok()
    }
    pub fn vertical_advance(&mut self, glyph: GlyphId) -> Option<u16> {
        let provider = &self.font_table_provider;
        let vmtx = self
            .vmtx_table
            .get_or_load(|| {
                read_and_box_optional_table(provider, tag::VMTX).map(|ok| ok.map(Arc::from))
            })
            .ok()?;
        let vhea = self.vhea_table().ok()?;
        if let (Some(vhea), Some(vmtx_table)) = (vhea, vmtx) {
            Some(glyph_info::advance(&self.maxp_table, &vhea, &vmtx_table, glyph).unwrap())
        } else {
            None
        }
    }
    /// Calculates the bounding box of a glyph
    ///
    /// Returns `None` if the glyph is empty.
    ///
    /// The error type is `CFFError` because CFF errors can be encountered when traversing
    /// CharStrings. Errors encountered when processing `glyf` based fonts use the
    /// `CFFError::ParseError` variant, with the error contained within.
    ///
    /// **Note:** This method currently only returns the bounding box for the default instance
    /// for variable fonts.
    pub fn bounding_box(&mut self, glyph_id: u16) -> Result<Option<BoundingBox>, CFFError> {
        if self.glyph_table_flags.contains(GlyphTableFlag::CFF2) {
            let cff2 = self
                .cff2_table()?
                .ok_or(ParseError::MissingTable(tag::CFF2))?;
            cff2.with(|cff2| {
                let mut cff2_outlines = CFF2Outlines { table: cff2.table };
                // TODO: Support bounding box of variable font instances
                cff2_outlines.visit(glyph_id, None, &mut NullSink)
            })
        } else if self.glyph_table_flags.contains(GlyphTableFlag::CFF) {
            let cff = self
                .cff_table()?
                .ok_or(ParseError::MissingTable(tag::CFF))?;
            cff.with(|cff| {
                let mut cff_outlines = CFFOutlines { table: cff.table };
                cff_outlines.visit(glyph_id, None, &mut NullSink)
            })
        } else if self.glyph_table_flags.contains(GlyphTableFlag::GLYF) {
            self.maybe_load_loca_glyf()?;
            // TODO: Support bounding box of variable font instances
            let mut context = GlyfVisitorContext::new(&mut self.loca_glyf, None);
            let mut sink = BoundingBoxSink::new();
            context.visit(glyph_id, None, &mut sink)?;
            Ok(sink.to_bounding_box())
        } else {
            Err(ParseError::MissingValue.into())
        }
    }
    pub fn cff_table(&mut self) -> Result<Option<Arc<tables::CFF>>, ParseError> {
        let provider = &self.font_table_provider;
        self.cff_cache.get_or_load(|| {
            let cff_data = provider.read_table_data(tag::CFF).map(Box::from)?;
            let cff = tables::CFFTryBuilder {
                data: cff_data,
                table_builder: |data: &Box<[u8]>| ReadScope::new(data).read::<CFF<'_>>(),
            }
            .try_build()?;
            Ok(Some(Arc::new(cff)))
        })
    }
    pub fn cff2_table(&mut self) -> Result<Option<Arc<tables::CFF2>>, ParseError> {
        let provider = &self.font_table_provider;
        self.cff2_cache.get_or_load(|| {
            let cff_data = provider.read_table_data(tag::CFF2).map(Box::from)?;
            let cff = tables::CFF2TryBuilder {
                data: cff_data,
                table_builder: |data: &Box<[u8]>| ReadScope::new(data).read::<CFF2<'_>>(),
            }
            .try_build()?;
            Ok(Some(Arc::new(cff)))
        })
    }
    pub fn os2_table(&self) -> Result<Option<Os2>, ParseError> {
        load_os2_table(&self.font_table_provider)
    }
    pub fn maybe_load_loca_glyf(&mut self) -> Result<(), ParseError> {
        if !self.loca_glyf.is_loaded() {
            let provider = &self.font_table_provider;
            let loca_data = self.font_table_provider.read_table_data(tag::LOCA)?;
            let loca = ReadScope::new(&loca_data).read_dep::<LocaTable<'_>>((
                self.num_glyphs(),
                self.head_table.index_to_loc_format,
            ))?;
            let loca = owned::LocaTable::from(&loca);
            let glyf = read_and_box_table(provider, tag::GLYF)?;
            self.loca_glyf = LocaGlyf::loaded(loca, glyf);
        }
        Ok(())
    }
21892
    pub fn gdef_table(&mut self) -> Result<Option<Arc<GDEFTable>>, ParseError> {
21892
        let provider = &self.font_table_provider;
21892
        self.gdef_cache.get_or_load(|| {
21892
            if let Some(gdef_data) = provider.table_data(tag::GDEF)? {
7900
                let gdef = ReadScope::new(&gdef_data).read::<GDEFTable>()?;
7900
                Ok(Some(Arc::new(gdef)))
            } else {
13992
                Ok(None)
            }
21892
        })
21892
    }
    pub fn morx_table(&mut self) -> Result<Option<Arc<tables::Morx>>, ParseError> {
        let provider = &self.font_table_provider;
        let num_glyphs = self.num_glyphs();
        self.morx_cache.get_or_load(|| {
            if let Some(morx_data) = provider.table_data(tag::MORX)? {
                let morx = tables::Morx::try_new(morx_data.into(), |data| {
                    ReadScope::new(data).read_dep::<MorxTable<'_>>(num_glyphs)
                })?;
                Ok(Some(Arc::new(morx)))
            } else {
                Ok(None)
            }
        })
    }
    pub fn gsub_cache(&mut self) -> Result<Option<LayoutCache<GSUB>>, ParseError> {
        let provider = &self.font_table_provider;
        self.gsub_cache.get_or_load(|| {
            if let Some(gsub_data) = provider.table_data(tag::GSUB)? {
                let gsub = ReadScope::new(&gsub_data).read::<LayoutTable<GSUB>>()?;
                let cache = new_layout_cache::<GSUB>(gsub);
                Ok(Some(cache))
            } else {
                Ok(None)
            }
        })
    }
    pub fn gpos_cache(&mut self) -> Result<Option<LayoutCache<GPOS>>, ParseError> {
        let provider = &self.font_table_provider;
        self.gpos_cache.get_or_load(|| {
            if let Some(gpos_data) = provider.table_data(tag::GPOS)? {
                let gpos = ReadScope::new(&gpos_data).read::<LayoutTable<GPOS>>()?;
                let cache = new_layout_cache::<GPOS>(gpos);
                Ok(Some(cache))
            } else {
                Ok(None)
            }
        })
    }
21892
    pub fn kern_table(&mut self) -> Result<Option<Arc<KernTable>>, ParseError> {
21892
        let provider = &self.font_table_provider;
21892
        self.kern_cache.get_or_load(|| {
21892
            if let Some(kern_data) = provider.table_data(tag::KERN)? {
14260
                match ReadScope::new(&kern_data).read::<kern::KernTable<'_>>() {
14260
                    Ok(kern) => Ok(Some(Arc::new(kern.to_owned()))),
                    // This error may be encountered because there is a kern 1.0 version defined by
                    // Apple that is not in the OpenType spec. It only works on macOS. We don't
                    // support it so return None instead of returning an error.
                    Err(ParseError::BadVersion) => Ok(None),
                    Err(err) => Err(err),
                }
            } else {
7632
                Ok(None)
            }
21892
        })
21892
    }
    pub fn vhea_table(&mut self) -> Result<Option<Arc<HheaTable>>, ParseError> {
        let provider = &self.font_table_provider;
        self.vhea_table.get_or_load(|| {
            if let Some(vhea_data) = provider.table_data(tag::VHEA)? {
                let vhea = ReadScope::new(&vhea_data).read::<HheaTable>()?;
                Ok(Some(Arc::new(vhea)))
            } else {
                Ok(None)
            }
        })
    }
21892
    pub fn cmap_subtable_data(&self) -> &[u8] {
21892
        &self.cmap_table[self.cmap_subtable_offset..]
21892
    }
}
impl<T> LazyLoad<T> {
    /// Return loaded value, calls the supplied closure if not already loaded.
    ///
    /// It's expected that `T` is cheap to clone, either because it's wrapped in an `Rc`
    /// or is `Copy`.
43784
    fn get_or_load(
43784
        &mut self,
43784
        do_load: impl FnOnce() -> Result<Option<T>, ParseError>,
43784
    ) -> Result<Option<T>, ParseError>
43784
    where
43784
        T: Clone,
    {
        match self {
            LazyLoad::Loaded(Some(ref data)) => Ok(Some(data.clone())),
            LazyLoad::Loaded(None) => Ok(None),
            LazyLoad::NotLoaded => {
43784
                let data = do_load()?;
43784
                *self = LazyLoad::Loaded(data.clone());
43784
                Ok(data)
            }
        }
43784
    }
}
impl GlyphCache {
22464
    fn new() -> Self {
22464
        GlyphCache(None)
22464
    }
    fn get(&self, ch: char) -> Option<(u16, VariationSelector)> {
        if ch == DOTTED_CIRCLE {
            self.0
        } else {
            None
        }
    }
    fn put(&mut self, ch: char, glyph_index: GlyphId, variation_selector: VariationSelector) {
        if ch == DOTTED_CIRCLE {
            match self.0 {
                Some(_) => panic!("duplicate entry"),
                None => self.0 = Some((glyph_index, variation_selector)),
            }
        }
    }
}
struct NullSink;
impl OutlineSink for NullSink {
    fn move_to(&mut self, _to: Vector2F) {}
    fn line_to(&mut self, _to: Vector2F) {}
    fn quadratic_curve_to(&mut self, _ctrl: Vector2F, _to: Vector2F) {}
    fn cubic_curve_to(&mut self, _ctrl: LineSegment2F, _to: Vector2F) {}
    fn close(&mut self) {}
}
fn load_os2_table(provider: &impl FontTableProvider) -> Result<Option<Os2>, ParseError> {
    provider
        .table_data(tag::OS_2)?
        .map(|data| ReadScope::new(&data).read_dep::<Os2>(data.len()))
        .transpose()
}
fn load_cblc_cbdt(
    provider: &impl FontTableProvider,
    bitmap_location_table_tag: u32,
    bitmap_data_table_tag: u32,
) -> Result<(tables::CBLC, tables::CBDT), ParseError> {
    let cblc_data = read_and_box_table(provider, bitmap_location_table_tag)?;
    let cbdt_data = read_and_box_table(provider, bitmap_data_table_tag)?;
    let cblc = tables::CBLC::try_new(cblc_data, |data| {
        ReadScope::new(data).read::<CBLCTable<'_>>()
    })?;
    let cbdt = tables::CBDT::try_new(cbdt_data, |data| {
        ReadScope::new(data).read::<CBDTTable<'_>>()
    })?;
    Ok((cblc, cbdt))
}
fn load_colr_cpal(provider: &impl FontTableProvider) -> Result<tables::ColrCpal, ParseError> {
    let colr_data = read_and_box_table(provider, tag::COLR)?;
    let cpal_data = read_and_box_table(provider, tag::CPAL)?;
    let colr_cpal = ColrCpalTryBuilder {
        colr_data,
        cpal_data,
        colr_builder: |data: &Box<[u8]>| ReadScope::new(data).read::<ColrTable<'_>>(),
        cpal_builder: |data: &Box<[u8]>| ReadScope::new(data).read::<CpalTable<'_>>(),
    }
    .try_build()?;
    Ok(colr_cpal)
}
fn load_sbix(
    provider: &impl FontTableProvider,
    num_glyphs: usize,
) -> Result<tables::Sbix, ParseError> {
    let sbix_data = read_and_box_table(provider, tag::SBIX)?;
    tables::Sbix::try_new(sbix_data, |data| {
        ReadScope::new(data).read_dep::<SbixTable<'_>>(num_glyphs)
    })
}
fn load_svg(provider: &impl FontTableProvider) -> Result<tables::Svg, ParseError> {
    let svg_data = read_and_box_table(provider, tag::SVG)?;
    tables::Svg::try_new(svg_data, |data| ReadScope::new(data).read::<SvgTable<'_>>())
}
22464
fn charmap_info(cmap_buf: &[u8]) -> Result<Option<(Encoding, u32)>, ParseError> {
22464
    let cmap = ReadScope::new(cmap_buf).read::<Cmap<'_>>()?;
22464
    Ok(find_good_cmap_subtable(&cmap)
22464
        .map(|(encoding, encoding_record)| (encoding, encoding_record.offset)))
22464
}
pub fn read_cmap_subtable<'a>(
    cmap: &Cmap<'a>,
) -> Result<Option<(Encoding, CmapSubtable<'a>)>, ParseError> {
    if let Some((encoding, encoding_record)) = find_good_cmap_subtable(cmap) {
        let subtable = cmap
            .scope
            .offset(usize::try_from(encoding_record.offset)?)
            .read::<CmapSubtable<'_>>()?;
        Ok(Some((encoding, subtable)))
    } else {
        Ok(None)
    }
}
22464
pub fn find_good_cmap_subtable(cmap: &Cmap<'_>) -> Option<(Encoding, EncodingRecord)> {
    // MS UNICODE, UCS-4 (32 bit)
1620
    if let Some(encoding_record) =
22464
        cmap.find_subtable(PlatformId::WINDOWS, EncodingId::WINDOWS_UNICODE_UCS4)
    {
1620
        return Some((Encoding::Unicode, encoding_record));
20844
    }
    // MS UNICODE, UCS-2 (16 bit)
20844
    if let Some(encoding_record) =
20844
        cmap.find_subtable(PlatformId::WINDOWS, EncodingId::WINDOWS_UNICODE_BMP_UCS2)
    {
20844
        return Some((Encoding::Unicode, encoding_record));
    }
    // Apple UNICODE, UCS-4 (32 bit)
    if let Some(encoding_record) =
        cmap.find_subtable(PlatformId::UNICODE, EncodingId::MACINTOSH_UNICODE_UCS4)
    {
        return Some((Encoding::Unicode, encoding_record));
    }
    // Any UNICODE table
    if let Some(encoding_record) = cmap.find_subtable_for_platform(PlatformId::UNICODE) {
        return Some((Encoding::Unicode, encoding_record));
    }
    // MS Symbol
    if let Some(encoding_record) =
        cmap.find_subtable(PlatformId::WINDOWS, EncodingId::WINDOWS_SYMBOL)
    {
        return Some((Encoding::Symbol, encoding_record));
    }
    // Apple Roman
    if let Some(encoding_record) =
        cmap.find_subtable(PlatformId::MACINTOSH, EncodingId::MACINTOSH_APPLE_ROMAN)
    {
        return Some((Encoding::AppleRoman, encoding_record));
    }
    // Big5
    if let Some(encoding_record) = cmap.find_subtable(PlatformId::WINDOWS, EncodingId::WINDOWS_BIG5)
    {
        return Some((Encoding::Big5, encoding_record));
    }
    None
22464
}
// Unwrap the supplied result returning `T` if `Ok` or `T::default` otherwise. If `Err` then set
// `err` unless it's already set.
fn check_set_err<T, E>(res: Result<T, E>, err: &mut Option<ShapingError>) -> T
where
    E: Into<ShapingError>,
    T: Default,
{
    match res {
        Ok(table) => table,
        Err(e) => {
            if err.is_none() {
                *err = Some(e.into())
            }
            T::default()
        }
    }
}
#[cfg(test)]
mod tests {
    use super::*;
    use crate::bitmap::{Bitmap, EncapsulatedBitmap};
    use crate::cff::CFFError;
    use crate::font_data::{DynamicFontTableProvider, FontData};
    use crate::tables::OpenTypeFont;
    use crate::tests::read_fixture;
    use std::error::Error;
    #[test]
    fn test_glyph_names() {
        let font_buffer = read_fixture("tests/fonts/opentype/TwitterColorEmoji-SVGinOT.ttf");
        let opentype_file = ReadScope::new(&font_buffer)
            .read::<OpenTypeFont<'_>>()
            .unwrap();
        let font_table_provider = opentype_file
            .table_provider(0)
            .expect("error reading font file");
        let font = Font::new(Box::new(font_table_provider)).expect("error reading font data");
        let names = font.glyph_names(&[0, 5, 45, 71, 1311, 3086]);
        assert_eq!(
            names,
            &[
                Cow::from(".notdef"),
                Cow::from("copyright"),
                Cow::from("uni25B6"),
                Cow::from("smileface"),
                Cow::from("u1FA95"),
                Cow::from("1f468-200d-1f33e")
            ]
        );
    }
    #[test]
    fn test_glyph_names_post_v3() {
        // This font is a CFF font with a version 3 post table (no names in table).
        let font_buffer = read_fixture("tests/fonts/opentype/Klei.otf");
        let opentype_file = ReadScope::new(&font_buffer)
            .read::<OpenTypeFont<'_>>()
            .unwrap();
        let font_table_provider = opentype_file
            .table_provider(0)
            .expect("error reading font file");
        let font = Font::new(Box::new(font_table_provider)).expect("error reading font data");
        let names = font.glyph_names(&[0, 5, 45, 100, 763, 1000 /* out of range */]);
        assert_eq!(
            names,
            &[
                Cow::from(".notdef"),
                Cow::from("dollar"),
                Cow::from("L"),
                Cow::from("yen"),
                Cow::from("uniFB00"),
                Cow::from("g1000") // out of range gid is assigned fallback name
            ]
        );
    }
    #[test]
    fn test_lookup_sbix() {
        let font_buffer = read_fixture("tests/fonts/sbix/sbix-dupe.ttf");
        let opentype_file = ReadScope::new(&font_buffer)
            .read::<OpenTypeFont<'_>>()
            .unwrap();
        let font_table_provider = opentype_file
            .table_provider(0)
            .expect("error reading font file");
        let mut font = Font::new(Box::new(font_table_provider)).expect("error reading font data");
        // Successfully read bitmap
        match font.lookup_glyph_image(1, 100, BitDepth::ThirtyTwo) {
            Ok(Some(BitmapGlyph {
                bitmap: Bitmap::Encapsulated(EncapsulatedBitmap { data, .. }),
                ..
            })) => {
                assert_eq!(data.len(), 224);
            }
            _ => panic!("Expected encapsulated bitmap, got something else."),
        }
        // Successfully read bitmap pointed at by `dupe` record. Should end up returning data for
        // glyph 1.
        match font.lookup_glyph_image(2, 100, BitDepth::ThirtyTwo) {
            Ok(Some(BitmapGlyph {
                bitmap: Bitmap::Encapsulated(EncapsulatedBitmap { data, .. }),
                ..
            })) => {
                assert_eq!(data.len(), 224);
            }
            _ => panic!("Expected encapsulated bitmap, got something else."),
        }
        // Handle recursive `dupe` record. Should return Ok(None) as recursion is stopped at one
        // level.
        match font.lookup_glyph_image(3, 100, BitDepth::ThirtyTwo) {
            Ok(None) => {}
            _ => panic!("Expected Ok(None) got something else"),
        }
    }
    // Test that Font is only tied to the lifetime of the ReadScope and not any
    // intermediate types.
    #[test]
    fn table_provider_independent_of_font() {
        // Prior to code changes this function did not compile
        fn load_font<'a>(
            scope: ReadScope<'a>,
        ) -> Result<Font<DynamicFontTableProvider<'a>>, Box<dyn Error>> {
            let font_file = scope.read::<FontData<'_>>()?;
            let provider = font_file.table_provider(0)?;
            Font::new(provider).map_err(Box::from)
        }
        let buffer =
            std::fs::read("tests/fonts/opentype/Klei.otf").expect("unable to read Klei.otf");
        let scope = ReadScope::new(&buffer);
        assert!(load_font(scope).is_ok());
    }
    #[test]
    fn bounding_box_cff() -> Result<(), CFFError> {
        let buffer = std::fs::read("tests/fonts/opentype/Klei.otf").unwrap();
        let opentype_file = ReadScope::new(&buffer).read::<OpenTypeFont<'_>>()?;
        let font_table_provider = opentype_file
            .table_provider(0)
            .expect("error reading font file");
        let mut font = Font::new(Box::new(font_table_provider))?;
        // Values verified in FontForge
        let bbox = font.bounding_box(1)?; // 1 is space
        assert_eq!(bbox, None);
        let bbox = font.bounding_box(80)?; // 80 is 'o'
        assert_eq!(
            bbox,
            Some(BoundingBox {
                x_min: 40,
                x_max: 488,
                y_min: -11,
                y_max: 511
            })
        );
        let bbox = font.bounding_box(109)?; // 109 is U+00AF MACRON
        assert_eq!(
            bbox,
            Some(BoundingBox {
                x_min: 67,
                x_max: 341,
                y_min: 506,
                y_max: 550
            })
        );
        Ok(())
    }
}