allsorts_subset_browser/
font.rs

1//! Central font handling support.
2
3use std::borrow::Cow;
4use std::convert::{self, TryFrom};
5use std::rc::Rc;
6
7use bitflags::bitflags;
8use tinyvec::tiny_vec;
9
10use crate::big5::unicode_to_big5;
11use crate::binary::read::ReadScope;
12use crate::bitmap::cbdt::{self, CBDTTable, CBLCTable};
13use crate::bitmap::sbix::Sbix as SbixTable;
14use crate::bitmap::{BitDepth, BitmapGlyph};
15use crate::error::{ParseError, ShapingError};
16use crate::glyph_info::GlyphNames;
17use crate::gpos::Info;
18use crate::gsub::{Features, GlyphOrigin, RawGlyph, RawGlyphFlags};
19use crate::layout::{new_layout_cache, GDEFTable, LayoutCache, LayoutTable, GPOS, GSUB};
20use crate::macroman::char_to_macroman;
21use crate::scripts::preprocess_text;
22use crate::tables::cmap::{Cmap, CmapSubtable, EncodingId, EncodingRecord, PlatformId};
23use crate::tables::os2::Os2;
24use crate::tables::svg::SvgTable;
25use crate::tables::variable_fonts::fvar::{FvarAxisCount, FvarTable, Tuple, VariationAxisRecord};
26use crate::tables::{FontTableProvider, HeadTable, HheaTable, MaxpTable};
27use crate::unicode::{self, VariationSelector};
28use crate::variations::{AxisNamesError, NamedAxis};
29use crate::{glyph_info, tag, variations};
30use crate::{gpos, gsub, DOTTED_CIRCLE};
31
32#[derive(Copy, Clone, Debug, Eq, PartialEq)]
33pub enum Encoding {
34    Unicode = 1,
35    Symbol = 2,
36    AppleRoman = 3,
37    Big5 = 4,
38}
39
40#[derive(Copy, Clone, Eq, PartialEq, Debug)]
41pub enum OutlineFormat {
42    Glyf,
43    Cff,
44    Svg,
45    None,
46}
47
48enum LazyLoad<T> {
49    NotLoaded,
50    Loaded(Option<T>),
51}
52
53#[derive(Copy, Clone, Eq, PartialEq, Debug)]
54pub enum MatchingPresentation {
55    Required,
56    NotRequired,
57}
58
59/// For now `GlyphCache` only stores the index of U+25CC DOTTED CIRCLE. The intention is for this
60/// to become a more general cache in the future.
61///
62/// `None` indicates that dotted circle has never been looked up. A value otherwise is the index of
63/// the glyph.
64struct GlyphCache(Option<(u16, VariationSelector)>);
65
66/// Core type for loading a font in order to perform glyph mapping and font shaping.
67pub struct Font<T: FontTableProvider> {
68    pub font_table_provider: T,
69    cmap_table: Box<[u8]>,
70    pub maxp_table: MaxpTable,
71    hmtx_table: Box<[u8]>,
72    pub hhea_table: HheaTable,
73    vmtx_table: LazyLoad<Rc<[u8]>>,
74    vhea_table: LazyLoad<Rc<HheaTable>>,
75    cmap_subtable_offset: usize,
76    pub cmap_subtable_encoding: Encoding,
77    gdef_cache: LazyLoad<Rc<GDEFTable>>,
78    gsub_cache: LazyLoad<LayoutCache<GSUB>>,
79    gpos_cache: LazyLoad<LayoutCache<GPOS>>,
80    os2_us_first_char_index: LazyLoad<u16>,
81    glyph_cache: GlyphCache,
82    pub glyph_table_flags: GlyphTableFlags,
83    embedded_image_filter: GlyphTableFlags,
84    embedded_images: LazyLoad<Rc<Images>>,
85    axis_count: u16,
86}
87
88pub enum Images {
89    Embedded {
90        cblc: tables::CBLC,
91        cbdt: tables::CBDT,
92    },
93    Sbix(tables::Sbix),
94    Svg(tables::Svg),
95}
96
97mod tables {
98    use super::*;
99    use ouroboros::self_referencing;
100
101    #[self_referencing(pub_extras)]
102    pub struct CBLC {
103        data: Box<[u8]>,
104        #[borrows(data)]
105        #[not_covariant]
106        pub(crate) table: CBLCTable<'this>,
107    }
108
109    #[self_referencing(pub_extras)]
110    pub struct CBDT {
111        data: Box<[u8]>,
112        #[borrows(data)]
113        #[covariant]
114        pub(crate) table: CBDTTable<'this>,
115    }
116
117    #[self_referencing(pub_extras)]
118    pub struct Sbix {
119        data: Box<[u8]>,
120        #[borrows(data)]
121        #[not_covariant]
122        pub(crate) table: SbixTable<'this>,
123    }
124
125    #[self_referencing(pub_extras)]
126    pub struct Svg {
127        data: Box<[u8]>,
128        #[borrows(data)]
129        #[not_covariant]
130        pub(crate) table: SvgTable<'this>,
131    }
132}
133
134bitflags! {
135    pub struct GlyphTableFlags: u8 {
136        const GLYF = 1 << 0;
137        const CFF  = 1 << 1;
138        const SVG  = 1 << 2;
139        const SBIX = 1 << 3;
140        const CBDT = 1 << 4;
141        const EBDT = 1 << 5;
142        const CFF2 = 1 << 6;
143    }
144}
145
146const TABLE_TAG_FLAGS: &[(u32, GlyphTableFlags)] = &[
147    (tag::GLYF, GlyphTableFlags::GLYF),
148    (tag::CFF, GlyphTableFlags::CFF),
149    (tag::CFF2, GlyphTableFlags::CFF2),
150    (tag::SVG, GlyphTableFlags::SVG),
151    (tag::SBIX, GlyphTableFlags::SBIX),
152    (tag::CBDT, GlyphTableFlags::CBDT),
153    (tag::EBDT, GlyphTableFlags::EBDT),
154];
155
156impl<T: FontTableProvider> Font<T> {
157    /// Construct a new instance from a type that can supply font tables.
158    ///
159    /// Returns `None` if the font was able to be read but no supported `cmap` sub-table was
160    /// able to be found. The lack of such a table prevents glyph mapping.
161    pub fn new(provider: T) -> Result<Font<T>, ParseError> {
162        let cmap_table = read_and_box_table(&provider, tag::CMAP)?;
163
164        match charmap_info(&cmap_table)? {
165            Some((cmap_subtable_encoding, cmap_subtable_offset)) => {
166                let maxp_table =
167                    ReadScope::new(&provider.read_table_data(tag::MAXP)?).read::<MaxpTable>()?;
168                let hmtx_table = read_and_box_table(&provider, tag::HMTX)?;
169                let hhea_table =
170                    ReadScope::new(&provider.read_table_data(tag::HHEA)?).read::<HheaTable>()?;
171                let fvar_data = provider.table_data(tag::FVAR)?;
172                let fvar_axis_count = fvar_data
173                    .as_deref()
174                    .map(|data| ReadScope::new(data).read::<FvarAxisCount>())
175                    .transpose()?
176                    .unwrap_or(0);
177
178                let embedded_image_filter =
179                    GlyphTableFlags::SVG | GlyphTableFlags::SBIX | GlyphTableFlags::CBDT;
180                let mut glyph_table_flags = GlyphTableFlags::empty();
181                for &(table, flag) in TABLE_TAG_FLAGS {
182                    if provider.has_table(table) {
183                        glyph_table_flags |= flag
184                    }
185                }
186
187                Ok(Font {
188                    font_table_provider: provider,
189                    cmap_table,
190                    maxp_table,
191                    hmtx_table,
192                    hhea_table,
193                    vmtx_table: LazyLoad::NotLoaded,
194                    vhea_table: LazyLoad::NotLoaded,
195                    cmap_subtable_offset: usize::try_from(cmap_subtable_offset)?,
196                    cmap_subtable_encoding,
197                    gdef_cache: LazyLoad::NotLoaded,
198                    gsub_cache: LazyLoad::NotLoaded,
199                    gpos_cache: LazyLoad::NotLoaded,
200                    os2_us_first_char_index: LazyLoad::NotLoaded,
201                    glyph_cache: GlyphCache::new(),
202                    glyph_table_flags,
203                    embedded_image_filter,
204                    embedded_images: LazyLoad::NotLoaded,
205                    axis_count: fvar_axis_count,
206                })
207            }
208            None => Err(ParseError::UnsuitableCmap),
209        }
210    }
211
212    /// Returns the number of glyphs in the font.
213    pub fn num_glyphs(&self) -> u16 {
214        self.maxp_table.num_glyphs
215    }
216
217    /// Set the embedded image table filter.
218    ///
219    /// When determining if a font contains embedded images, as well as retrieving images this
220    /// value it used to set which tables are consulted. By default it is set to only consult
221    /// tables that can contain colour images (`CBDT`/`CBLC`, `sbix`, and `SVG`). You can change
222    /// the value to exclude certain tables or opt into tables that can only contain B&W images
223    /// (`EBDT`/`EBLC`).
224    pub fn set_embedded_image_filter(&mut self, flags: GlyphTableFlags) {
225        self.embedded_image_filter = flags;
226    }
227
228    /// Look up the glyph index for the supplied character in the font.
229    pub fn lookup_glyph_index(
230        &mut self,
231        ch: char,
232        match_presentation: MatchingPresentation,
233        variation_selector: Option<VariationSelector>,
234    ) -> (u16, VariationSelector) {
235        self.glyph_cache.get(ch).unwrap_or_else(|| {
236            let (glyph_index, used_variation) =
237                self.map_unicode_to_glyph(ch, match_presentation, variation_selector);
238            self.glyph_cache.put(ch, glyph_index, used_variation);
239            (glyph_index, used_variation)
240        })
241    }
242
243    /// Convenience method to shape the supplied glyphs.
244    ///
245    /// The method maps applies glyph substitution (`gsub`) and glyph positioning (`gpos`). Use
246    /// `map_glyphs` to turn text into glyphs that can be accepted by this method.
247    ///
248    /// **Arguments:**
249    ///
250    /// * `glyphs`: the glyphs to be shaped in logical order.
251    /// * `script_tag`: the [OpenType script tag](https://docs.microsoft.com/en-us/typography/opentype/spec/scripttags) of the text.
252    /// * `opt_lang_tag`: the [OpenType language tag](https://docs.microsoft.com/en-us/typography/opentype/spec/languagetags) of the text.
253    /// * `features`: the [OpenType features](https://docs.microsoft.com/en-us/typography/opentype/spec/featuretags) to enable.
254    /// * `kerning`: when applying `gpos` if this argument is `true` the `kern` OpenType feature
255    ///   is enabled for non-complex scripts. If it is `false` then the `kern` feature is not
256    ///   enabled for non-complex scripts.
257    ///
258    /// **Error Handling:**
259    ///
260    /// This method will continue on in the face of errors, applying what it can. If no errors are
261    /// encountered this method returns `Ok(Vec<Info>)`. If one or more errors are encountered
262    /// `Err((ShapingError, Vec<Info>))` is returned. The first error encountered is returned
263    /// as the first item of the tuple. `Vec<Info>` is returned as the second item of the tuple,
264    /// allowing consumers of the shaping output to proceed even if errors are encountered.
265    /// However, in the error case the glyphs might not have had GPOS or GSUB applied.
266    ///
267    /// ## Example
268    ///
269    /// ```
270    /// use allsorts::binary::read::ReadScope;
271    /// use allsorts::font::MatchingPresentation;
272    /// use allsorts::font_data::FontData;
273    /// use allsorts::gsub::{self, Features, FeatureMask};
274    /// use allsorts::DOTTED_CIRCLE;
275    /// use allsorts::{tag, Font};
276    ///
277    /// let script = tag::LATN;
278    /// let lang = tag::DFLT;
279    /// let variation_tuple = None;
280    /// let buffer = std::fs::read("tests/fonts/opentype/Klei.otf")
281    ///     .expect("unable to read Klei.otf");
282    /// let scope = ReadScope::new(&buffer);
283    /// let font_file = scope.read::<FontData<'_>>().expect("unable to parse font");
284    /// // Use a different index to access other fonts in a font collection (E.g. TTC)
285    /// let provider = font_file
286    ///     .table_provider(0)
287    ///     .expect("unable to create table provider");
288    /// let mut font = Font::new(provider)
289    ///     .expect("unable to load font tables");
290    ///
291    /// // Klei ligates ff
292    /// let glyphs = font.map_glyphs("Shaping in a jiffy.", script, MatchingPresentation::NotRequired);
293    /// let glyph_infos = font
294    ///     .shape(
295    ///         glyphs,
296    ///         script,
297    ///         Some(lang),
298    ///         &Features::Mask(FeatureMask::default()),
299    ///         variation_tuple,
300    ///         true,
301    ///     )
302    ///     .expect("error shaping text");
303    /// // We expect ff to be ligated so the number of glyphs (18) should be one less than the
304    /// // number of input characters (19).
305    /// assert_eq!(glyph_infos.len(), 18);
306    /// ```
307    pub fn shape(
308        &mut self,
309        mut glyphs: Vec<RawGlyph<()>>,
310        script_tag: u32,
311        opt_lang_tag: Option<u32>,
312        features: &Features,
313        tuple: Option<Tuple<'_>>,
314        kerning: bool,
315    ) -> Result<Vec<Info>, (ShapingError, Vec<Info>)> {
316        // We forge ahead in the face of errors applying what we can, returning the first error
317        // encountered.
318        let mut err: Option<ShapingError> = None;
319        let opt_gsub_cache = check_set_err(self.gsub_cache(), &mut err);
320        let opt_gpos_cache = check_set_err(self.gpos_cache(), &mut err);
321        let opt_gdef_table = check_set_err(self.gdef_table(), &mut err);
322        let opt_gdef_table = opt_gdef_table.as_ref().map(Rc::as_ref);
323        let (dotted_circle_index, _) =
324            self.lookup_glyph_index(DOTTED_CIRCLE, MatchingPresentation::NotRequired, None);
325
326        // Apply gsub if table is present
327        let num_glyphs = self.num_glyphs();
328        if let Some(gsub_cache) = opt_gsub_cache {
329            let res = gsub::apply(
330                dotted_circle_index,
331                &gsub_cache,
332                opt_gdef_table,
333                script_tag,
334                opt_lang_tag,
335                features,
336                tuple,
337                num_glyphs,
338                &mut glyphs,
339            );
340            check_set_err(res, &mut err);
341        }
342
343        // Apply gpos if table is present
344        let mut infos = Info::init_from_glyphs(opt_gdef_table, glyphs);
345        if let Some(gpos_cache) = opt_gpos_cache {
346            let res = gpos::apply(
347                &gpos_cache,
348                opt_gdef_table,
349                kerning,
350                features,
351                tuple,
352                script_tag,
353                opt_lang_tag,
354                &mut infos,
355            );
356            check_set_err(res, &mut err);
357        } else {
358            gpos::apply_fallback(&mut infos);
359        }
360
361        match err {
362            Some(err) => Err((err, infos)),
363            None => Ok(infos),
364        }
365    }
366
367    /// Map text to glyphs.
368    ///
369    /// This method maps text into glyphs, which can then be passed to `shape`. The text is
370    /// processed in logical order, there is no need to reorder text before calling this method.
371    ///
372    /// The `match_presentation` argument controls glyph mapping in the presence of emoji/text
373    /// variation selectors. If `MatchingPresentation::NotRequired` is passed then glyph mapping
374    /// will succeed if the font contains a mapping for a given character, regardless of whether
375    /// it has the tables necessary to support the requested presentation. If
376    /// `MatchingPresentation::Required` is passed then a character with emoji presentation,
377    /// either by default or requested via variation selector will only map to a glyph if the font
378    /// has mapping for the character, and it has the necessary tables for color emoji.
379    pub fn map_glyphs(
380        &mut self,
381        text: &str,
382        script_tag: u32,
383        match_presentation: MatchingPresentation,
384    ) -> Vec<RawGlyph<()>> {
385        let mut chars = text.chars().collect();
386        preprocess_text(&mut chars, script_tag);
387
388        // We look ahead in the char stream for variation selectors. If one is found it is used for
389        // mapping the current glyph. When a variation selector is reached in the stream it is
390        // skipped as it was handled as part of the preceding character.
391        let mut glyphs = Vec::with_capacity(chars.len());
392        let mut chars_iter = chars.into_iter().peekable();
393        while let Some(ch) = chars_iter.next() {
394            match VariationSelector::try_from(ch) {
395                Ok(_) => {} // filter out variation selectors
396                Err(()) => {
397                    let vs = chars_iter
398                        .peek()
399                        .and_then(|&next| VariationSelector::try_from(next).ok());
400                    let (glyph_index, used_variation) =
401                        self.lookup_glyph_index(ch, match_presentation, vs);
402                    let glyph = RawGlyph {
403                        unicodes: tiny_vec![[char; 1] => ch],
404                        glyph_index,
405                        liga_component_pos: 0,
406                        glyph_origin: GlyphOrigin::Char(ch),
407                        flags: RawGlyphFlags::empty(),
408                        extra_data: (),
409                        variation: Some(used_variation),
410                    };
411                    glyphs.push(glyph);
412                }
413            }
414        }
415        glyphs.shrink_to_fit();
416        glyphs
417    }
418
419    /// True if the font has one or more variation axes.
420    pub fn is_variable(&self) -> bool {
421        self.axis_count > 0
422    }
423
424    pub fn variation_axes(&self) -> Result<Vec<VariationAxisRecord>, ParseError> {
425        let table = self.font_table_provider.read_table_data(tag::FVAR)?;
426        let fvar = ReadScope::new(&table).read::<FvarTable<'_>>()?;
427        Ok(fvar.axes().collect())
428    }
429
430    fn map_glyph(&self, char_code: u32) -> u16 {
431        match ReadScope::new(self.cmap_subtable_data()).read::<CmapSubtable<'_>>() {
432            // TODO: Cache the parsed CmapSubtable
433            Ok(cmap_subtable) => match cmap_subtable.map_glyph(char_code) {
434                Ok(Some(glyph_index)) => glyph_index,
435                _ => 0,
436            },
437            Err(_err) => 0,
438        }
439    }
440
441    fn map_unicode_to_glyph(
442        &mut self,
443        ch: char,
444        match_presentation: MatchingPresentation,
445        variation_selector: Option<VariationSelector>,
446    ) -> (u16, VariationSelector) {
447        let used_selector = Self::resolve_default_presentation(ch, variation_selector);
448        let glyph_index = match self.cmap_subtable_encoding {
449            Encoding::Unicode => {
450                self.lookup_glyph_index_with_variation(ch as u32, match_presentation, used_selector)
451            }
452            Encoding::Symbol => {
453                let char_code = self.legacy_symbol_char_code(ch);
454                self.lookup_glyph_index_with_variation(char_code, match_presentation, used_selector)
455            }
456            Encoding::AppleRoman => match char_to_macroman(ch) {
457                Some(char_code) => self.lookup_glyph_index_with_variation(
458                    u32::from(char_code),
459                    match_presentation,
460                    used_selector,
461                ),
462                None => {
463                    let char_code = self.legacy_symbol_char_code(ch);
464                    self.lookup_glyph_index_with_variation(
465                        char_code,
466                        match_presentation,
467                        used_selector,
468                    )
469                }
470            },
471            Encoding::Big5 => match unicode_to_big5(ch) {
472                Some(char_code) => self.lookup_glyph_index_with_variation(
473                    u32::from(char_code),
474                    match_presentation,
475                    used_selector,
476                ),
477                None => 0,
478            },
479        };
480        (glyph_index, used_selector)
481    }
482
483    // The symbol encoding was created to support fonts with arbitrary ornaments or symbols not
484    // supported in Unicode or other standard encodings. A format 4 subtable would be used,
485    // typically with up to 224 graphic characters assigned at code positions beginning with
486    // 0xF020. This corresponds to a sub-range within the Unicode Private-Use Area (PUA), though
487    // this is not a Unicode encoding. In legacy usage, some applications would represent the
488    // symbol characters in text using a single-byte encoding, and then map 0x20 to the
489    // OS/2.usFirstCharIndex value in the font.
490    // — https://docs.microsoft.com/en-us/typography/opentype/spec/cmap#encoding-records-and-encodings
491    fn legacy_symbol_char_code(&mut self, ch: char) -> u32 {
492        let char_code0 = if ch < '\u{F000}' || ch > '\u{F0FF}' {
493            ch as u32
494        } else {
495            ch as u32 - 0xF000
496        };
497        let provider = &self.font_table_provider;
498        let first_char = if let Ok(Some(us_first_char_index)) =
499            self.os2_us_first_char_index.get_or_load(|| {
500                load_os2_table(provider)?
501                    .map(|os2| Ok(os2.us_first_char_index))
502                    .transpose()
503            }) {
504            u32::from(us_first_char_index)
505        } else {
506            0x20
507        };
508        (char_code0 + first_char) - 0x20 // Perform subtraction last to avoid underflow.
509    }
510
511    fn lookup_glyph_index_with_variation(
512        &mut self,
513        char_code: u32,
514        match_presentation: MatchingPresentation,
515        variation_selector: VariationSelector,
516    ) -> u16 {
517        if match_presentation == MatchingPresentation::Required {
518            // This match aims to only return a non-zero index if the font supports the requested
519            // presentation. So, if you want the glyph index for a code point using emoji presentation,
520            // the font must have suitable tables. On the flip side, if you want a glyph with text
521            // presentation then the font must have glyf or CFF outlines.
522            if (variation_selector == VariationSelector::VS16 && self.has_embedded_images())
523                || (variation_selector == VariationSelector::VS15 && self.has_glyph_outlines())
524            {
525                self.map_glyph(char_code)
526            } else {
527                0
528            }
529        } else {
530            self.map_glyph(char_code)
531        }
532    }
533
534    fn resolve_default_presentation(
535        ch: char,
536        variation_selector: Option<VariationSelector>,
537    ) -> VariationSelector {
538        variation_selector.unwrap_or_else(|| {
539            // `None` indicates no selector present so for emoji determine the default presentation.
540            if unicode::bool_prop_emoji_presentation(ch) {
541                VariationSelector::VS16
542            } else {
543                VariationSelector::VS15
544            }
545        })
546    }
547
548    pub fn glyph_names<'a>(&self, ids: &[u16]) -> Vec<Cow<'a, str>> {
549        let post = read_and_box_optional_table(&self.font_table_provider, tag::POST)
550            .ok()
551            .and_then(convert::identity);
552        let cmap = ReadScope::new(self.cmap_subtable_data())
553            .read::<CmapSubtable<'_>>()
554            .ok()
555            .map(|table| (self.cmap_subtable_encoding, table));
556        let glyph_namer = GlyphNames::new(&cmap, post);
557        glyph_namer.unique_glyph_names(ids)
558    }
559
560    /// Returns the names of the variation axes in the font.
561    pub fn axis_names<'a>(&self) -> Result<Vec<NamedAxis<'a>>, AxisNamesError> {
562        variations::axis_names(&self.font_table_provider)
563    }
564
565    /// Find an image matching the supplied criteria.
566    ///
567    /// * `glyph_index` is the glyph to lookup.
568    /// * `target_ppem` is the desired size. If an exact match can't be found the nearest one will
569    ///    be returned, favouring being oversize vs. undersized.
570    /// * `max_bit_depth` is the maximum accepted bit depth of the bitmap to return. If you accept
571    ///   all bit depths then use `BitDepth::ThirtyTwo`.
572    pub fn lookup_glyph_image(
573        &mut self,
574        glyph_index: u16,
575        target_ppem: u16,
576        max_bit_depth: BitDepth,
577    ) -> Result<Option<BitmapGlyph>, ParseError> {
578        let embedded_bitmaps = match self.embedded_images()? {
579            Some(embedded_bitmaps) => embedded_bitmaps,
580            None => return Ok(None),
581        };
582        match embedded_bitmaps.as_ref() {
583            Images::Embedded { cblc, cbdt } => cblc.with_table(|cblc: &CBLCTable<'_>| {
584                let target_ppem = if target_ppem > u16::from(std::u8::MAX) {
585                    std::u8::MAX
586                } else {
587                    target_ppem as u8
588                };
589                let bitmap = match cblc.find_strike(glyph_index, target_ppem, max_bit_depth) {
590                    Some(matching_strike) => {
591                        let cbdt = cbdt.borrow_table();
592                        cbdt::lookup(glyph_index, &matching_strike, cbdt)?.map(|bitmap| {
593                            BitmapGlyph::try_from((&matching_strike.bitmap_size.inner, bitmap))
594                        })
595                    }
596                    None => None,
597                };
598                bitmap.transpose()
599            }),
600            Images::Sbix(sbix) => {
601                self.lookup_sbix_glyph_bitmap(sbix, false, glyph_index, target_ppem, max_bit_depth)
602            }
603            Images::Svg(svg) => self.lookup_svg_glyph(svg, glyph_index),
604        }
605    }
606
607    /// Perform sbix lookup with `dupe` handling.
608    ///
609    /// The `dupe` flag indicates if this this a dupe lookup or not. To avoid potential infinite
610    /// recursion we only follow one level of `dupe` indirection.
611    fn lookup_sbix_glyph_bitmap(
612        &self,
613        sbix: &tables::Sbix,
614        dupe: bool,
615        glyph_index: u16,
616        target_ppem: u16,
617        max_bit_depth: BitDepth,
618    ) -> Result<Option<BitmapGlyph>, ParseError> {
619        sbix.with_table(|sbix_table: &SbixTable<'_>| {
620            match sbix_table.find_strike(glyph_index, target_ppem, max_bit_depth) {
621                Some(strike) => {
622                    match strike.read_glyph(glyph_index)? {
623                        Some(ref glyph) if glyph.graphic_type == tag::DUPE => {
624                            // The special graphicType of 'dupe' indicates that the data field
625                            // contains a uint16, big-endian glyph ID. The bitmap data for the
626                            // indicated glyph should be used for the current glyph.
627                            // — https://docs.microsoft.com/en-us/typography/opentype/spec/sbix#glyph-data
628                            if dupe {
629                                // We're already inside a `dupe` lookup and have encountered another
630                                Ok(None)
631                            } else {
632                                // Try again with the glyph id stored in data
633                                let dupe_glyph_index =
634                                    ReadScope::new(glyph.data).ctxt().read_u16be()?;
635                                self.lookup_sbix_glyph_bitmap(
636                                    sbix,
637                                    true,
638                                    dupe_glyph_index,
639                                    target_ppem,
640                                    max_bit_depth,
641                                )
642                            }
643                        }
644                        Some(glyph) => Ok(Some(BitmapGlyph::from((strike, &glyph)))),
645                        None => Ok(None),
646                    }
647                }
648                None => Ok(None),
649            }
650        })
651    }
652
653    fn lookup_svg_glyph(
654        &self,
655        svg: &tables::Svg,
656        glyph_index: u16,
657    ) -> Result<Option<BitmapGlyph>, ParseError> {
658        svg.with_table(
659            |svg_table: &SvgTable<'_>| match svg_table.lookup_glyph(glyph_index)? {
660                Some(svg_record) => BitmapGlyph::try_from(&svg_record).map(Some),
661                None => Ok(None),
662            },
663        )
664    }
665
666    fn embedded_images(&mut self) -> Result<Option<Rc<Images>>, ParseError> {
667        let provider = &self.font_table_provider;
668        let num_glyphs = usize::from(self.maxp_table.num_glyphs);
669        let tables_to_check = self.glyph_table_flags & self.embedded_image_filter;
670        self.embedded_images.get_or_load(|| {
671            if tables_to_check.contains(GlyphTableFlags::SVG) {
672                let images = load_svg(provider).map(Images::Svg)?;
673                Ok(Some(Rc::new(images)))
674            } else if tables_to_check.contains(GlyphTableFlags::CBDT) {
675                let images = load_cblc_cbdt(provider, tag::CBLC, tag::CBDT)
676                    .map(|(cblc, cbdt)| Images::Embedded { cblc, cbdt })?;
677                Ok(Some(Rc::new(images)))
678            } else if tables_to_check.contains(GlyphTableFlags::SBIX) {
679                let images = load_sbix(provider, num_glyphs).map(Images::Sbix)?;
680                Ok(Some(Rc::new(images)))
681            } else if tables_to_check.contains(GlyphTableFlags::EBDT) {
682                let images =
683                    load_cblc_cbdt(provider, tag::EBLC, tag::EBDT).map(|(eblc, ebdt)| {
684                        Images::Embedded {
685                            cblc: eblc,
686                            cbdt: ebdt,
687                        }
688                    })?;
689                Ok(Some(Rc::new(images)))
690            } else {
691                Ok(None)
692            }
693        })
694    }
695
696    /// Returns `true` if the font contains embedded images in supported tables.
697    ///
698    /// Allsorts supports extracting images from `CBDT`/`CBLC`, `sbix`, and `SVG` tables.
699    /// If any of these tables are present and parsable then this method returns `true`.
700    pub fn has_embedded_images(&mut self) -> bool {
701        matches!(self.embedded_images(), Ok(Some(_)))
702    }
703
704    /// Returns `true` if the font contains vector glyph outlines in supported tables.
705    ///
706    /// Supported tables are `glyf`, `CFF`, `CFF2`.
707    pub fn has_glyph_outlines(&self) -> bool {
708        self.glyph_table_flags
709            .intersects(GlyphTableFlags::GLYF | GlyphTableFlags::CFF | GlyphTableFlags::CFF2)
710    }
711
712    /// Returns the horizontal advance of the supplied glyph index.
713    ///
714    /// Will return `None` if there are errors encountered reading the `hmtx` table or there is
715    /// no entry for the glyph index.
716    pub fn horizontal_advance(&mut self, glyph: u16) -> Option<u16> {
717        glyph_info::advance(&self.maxp_table, &self.hhea_table, &self.hmtx_table, glyph).ok()
718    }
719
720    pub fn vertical_advance(&mut self, glyph: u16) -> Option<u16> {
721        let provider = &self.font_table_provider;
722        let vmtx = self
723            .vmtx_table
724            .get_or_load(|| {
725                read_and_box_optional_table(provider, tag::VMTX).map(|ok| ok.map(Rc::from))
726            })
727            .ok()?;
728        let vhea = self.vhea_table().ok()?;
729
730        if let (Some(vhea), Some(vmtx_table)) = (vhea, vmtx) {
731            Some(glyph_info::advance(&self.maxp_table, &vhea, &vmtx_table, glyph).unwrap())
732        } else {
733            None
734        }
735    }
736
737    pub fn head_table(&self) -> Result<Option<HeadTable>, ParseError> {
738        self.font_table_provider
739            .table_data(tag::HEAD)?
740            .map(|data| ReadScope::new(&data).read::<HeadTable>())
741            .transpose()
742    }
743
744    pub fn os2_table(&self) -> Result<Option<Os2>, ParseError> {
745        load_os2_table(&self.font_table_provider)
746    }
747
748    pub fn gdef_table(&mut self) -> Result<Option<Rc<GDEFTable>>, ParseError> {
749        let provider = &self.font_table_provider;
750        self.gdef_cache.get_or_load(|| {
751            if let Some(gdef_data) = provider.table_data(tag::GDEF)? {
752                let gdef = ReadScope::new(&gdef_data).read::<GDEFTable>()?;
753                Ok(Some(Rc::new(gdef)))
754            } else {
755                Ok(None)
756            }
757        })
758    }
759
760    pub fn gsub_cache(&mut self) -> Result<Option<LayoutCache<GSUB>>, ParseError> {
761        let provider = &self.font_table_provider;
762        self.gsub_cache.get_or_load(|| {
763            if let Some(gsub_data) = provider.table_data(tag::GSUB)? {
764                let gsub = ReadScope::new(&gsub_data).read::<LayoutTable<GSUB>>()?;
765                let cache = new_layout_cache::<GSUB>(gsub);
766                Ok(Some(cache))
767            } else {
768                Ok(None)
769            }
770        })
771    }
772
773    pub fn gpos_cache(&mut self) -> Result<Option<LayoutCache<GPOS>>, ParseError> {
774        let provider = &self.font_table_provider;
775        self.gpos_cache.get_or_load(|| {
776            if let Some(gpos_data) = provider.table_data(tag::GPOS)? {
777                let gpos = ReadScope::new(&gpos_data).read::<LayoutTable<GPOS>>()?;
778                let cache = new_layout_cache::<GPOS>(gpos);
779                Ok(Some(cache))
780            } else {
781                Ok(None)
782            }
783        })
784    }
785
786    pub fn vhea_table(&mut self) -> Result<Option<Rc<HheaTable>>, ParseError> {
787        let provider = &self.font_table_provider;
788        self.vhea_table.get_or_load(|| {
789            if let Some(vhea_data) = provider.table_data(tag::VHEA)? {
790                let vhea = ReadScope::new(&vhea_data).read::<HheaTable>()?;
791                Ok(Some(Rc::new(vhea)))
792            } else {
793                Ok(None)
794            }
795        })
796    }
797
798    pub fn cmap_subtable_data(&self) -> &[u8] {
799        &self.cmap_table[self.cmap_subtable_offset..]
800    }
801}
802
803impl<T> LazyLoad<T> {
804    /// Return loaded value, calls the supplied closure if not already loaded.
805    ///
806    /// It's expected that `T` is cheap to clone, either because it's wrapped in an `Rc`
807    /// or is `Copy`.
808    fn get_or_load(
809        &mut self,
810        do_load: impl FnOnce() -> Result<Option<T>, ParseError>,
811    ) -> Result<Option<T>, ParseError>
812    where
813        T: Clone,
814    {
815        match self {
816            LazyLoad::Loaded(Some(ref data)) => Ok(Some(data.clone())),
817            LazyLoad::Loaded(None) => Ok(None),
818            LazyLoad::NotLoaded => {
819                let data = do_load()?;
820                *self = LazyLoad::Loaded(data.clone());
821                Ok(data)
822            }
823        }
824    }
825}
826
827impl GlyphCache {
828    fn new() -> Self {
829        GlyphCache(None)
830    }
831
832    fn get(&self, ch: char) -> Option<(u16, VariationSelector)> {
833        if ch == DOTTED_CIRCLE {
834            self.0
835        } else {
836            None
837        }
838    }
839
840    fn put(&mut self, ch: char, glyph_index: u16, variation_selector: VariationSelector) {
841        if ch == DOTTED_CIRCLE {
842            match self.0 {
843                Some(_) => panic!("duplicate entry"),
844                None => self.0 = Some((glyph_index, variation_selector)),
845            }
846        }
847    }
848}
849
850fn read_and_box_table(
851    provider: &impl FontTableProvider,
852    tag: u32,
853) -> Result<Box<[u8]>, ParseError> {
854    provider
855        .read_table_data(tag)
856        .map(|table| Box::from(table.into_owned()))
857}
858
859fn read_and_box_optional_table(
860    provider: &impl FontTableProvider,
861    tag: u32,
862) -> Result<Option<Box<[u8]>>, ParseError> {
863    Ok(provider
864        .table_data(tag)?
865        .map(|table| Box::from(table.into_owned())))
866}
867
868fn load_os2_table(provider: &impl FontTableProvider) -> Result<Option<Os2>, ParseError> {
869    provider
870        .table_data(tag::OS_2)?
871        .map(|data| ReadScope::new(&data).read_dep::<Os2>(data.len()))
872        .transpose()
873}
874
875fn load_cblc_cbdt(
876    provider: &impl FontTableProvider,
877    bitmap_location_table_tag: u32,
878    bitmap_data_table_tag: u32,
879) -> Result<(tables::CBLC, tables::CBDT), ParseError> {
880    let cblc_data = read_and_box_table(provider, bitmap_location_table_tag)?;
881    let cbdt_data = read_and_box_table(provider, bitmap_data_table_tag)?;
882
883    let cblc = tables::CBLC::try_new(cblc_data, |data| {
884        ReadScope::new(data).read::<CBLCTable<'_>>()
885    })?;
886    let cbdt = tables::CBDT::try_new(cbdt_data, |data| {
887        ReadScope::new(data).read::<CBDTTable<'_>>()
888    })?;
889
890    Ok((cblc, cbdt))
891}
892
893fn load_sbix(
894    provider: &impl FontTableProvider,
895    num_glyphs: usize,
896) -> Result<tables::Sbix, ParseError> {
897    let sbix_data = read_and_box_table(provider, tag::SBIX)?;
898    tables::Sbix::try_new(sbix_data, |data| {
899        ReadScope::new(data).read_dep::<SbixTable<'_>>(num_glyphs)
900    })
901}
902
903fn load_svg(provider: &impl FontTableProvider) -> Result<tables::Svg, ParseError> {
904    let svg_data = read_and_box_table(provider, tag::SVG)?;
905    tables::Svg::try_new(svg_data, |data| ReadScope::new(data).read::<SvgTable<'_>>())
906}
907
908fn charmap_info(cmap_buf: &[u8]) -> Result<Option<(Encoding, u32)>, ParseError> {
909    let cmap = ReadScope::new(cmap_buf).read::<Cmap<'_>>()?;
910    Ok(find_good_cmap_subtable(&cmap)
911        .map(|(encoding, encoding_record)| (encoding, encoding_record.offset)))
912}
913
914pub fn read_cmap_subtable<'a>(
915    cmap: &Cmap<'a>,
916) -> Result<Option<(Encoding, CmapSubtable<'a>)>, ParseError> {
917    if let Some((encoding, encoding_record)) = find_good_cmap_subtable(cmap) {
918        let subtable = cmap
919            .scope
920            .offset(usize::try_from(encoding_record.offset)?)
921            .read::<CmapSubtable<'_>>()?;
922        Ok(Some((encoding, subtable)))
923    } else {
924        Ok(None)
925    }
926}
927
928pub fn find_good_cmap_subtable(cmap: &Cmap<'_>) -> Option<(Encoding, EncodingRecord)> {
929    // MS UNICODE, UCS-4 (32 bit)
930    if let Some(encoding_record) =
931        cmap.find_subtable(PlatformId::WINDOWS, EncodingId::WINDOWS_UNICODE_UCS4)
932    {
933        return Some((Encoding::Unicode, encoding_record));
934    }
935
936    // MS UNICODE, UCS-2 (16 bit)
937    if let Some(encoding_record) =
938        cmap.find_subtable(PlatformId::WINDOWS, EncodingId::WINDOWS_UNICODE_BMP_UCS2)
939    {
940        return Some((Encoding::Unicode, encoding_record));
941    }
942
943    // Apple UNICODE, UCS-4 (32 bit)
944    if let Some(encoding_record) =
945        cmap.find_subtable(PlatformId::UNICODE, EncodingId::MACINTOSH_UNICODE_UCS4)
946    {
947        return Some((Encoding::Unicode, encoding_record));
948    }
949
950    // Any UNICODE table
951    if let Some(encoding_record) = cmap.find_subtable_for_platform(PlatformId::UNICODE) {
952        return Some((Encoding::Unicode, encoding_record));
953    }
954
955    // MS Symbol
956    if let Some(encoding_record) =
957        cmap.find_subtable(PlatformId::WINDOWS, EncodingId::WINDOWS_SYMBOL)
958    {
959        return Some((Encoding::Symbol, encoding_record));
960    }
961
962    // Apple Roman
963    if let Some(encoding_record) =
964        cmap.find_subtable(PlatformId::MACINTOSH, EncodingId::MACINTOSH_APPLE_ROMAN)
965    {
966        return Some((Encoding::AppleRoman, encoding_record));
967    }
968
969    // Big5
970    if let Some(encoding_record) = cmap.find_subtable(PlatformId::WINDOWS, EncodingId::WINDOWS_BIG5)
971    {
972        return Some((Encoding::Big5, encoding_record));
973    }
974
975    None
976}
977
978// Unwrap the supplied result returning `T` if `Ok` or `T::default` otherwise. If `Err` then set
979// `err` unless it's already set.
980fn check_set_err<T, E>(res: Result<T, E>, err: &mut Option<ShapingError>) -> T
981where
982    E: Into<ShapingError>,
983    T: Default,
984{
985    match res {
986        Ok(table) => table,
987        Err(e) => {
988            if err.is_none() {
989                *err = Some(e.into())
990            }
991            T::default()
992        }
993    }
994}
995
996#[cfg(test)]
997mod tests {
998    use super::*;
999    use crate::bitmap::{Bitmap, EncapsulatedBitmap};
1000    use crate::font_data::{DynamicFontTableProvider, FontData};
1001    use crate::tables::OpenTypeFont;
1002    use crate::tests::read_fixture;
1003    use std::error::Error;
1004
1005    #[test]
1006    fn test_glyph_names() {
1007        let font_buffer = read_fixture("tests/fonts/opentype/TwitterColorEmoji-SVGinOT.ttf");
1008        let opentype_file = ReadScope::new(&font_buffer)
1009            .read::<OpenTypeFont<'_>>()
1010            .unwrap();
1011        let font_table_provider = opentype_file
1012            .table_provider(0)
1013            .expect("error reading font file");
1014        let font = Font::new(Box::new(font_table_provider)).expect("error reading font data");
1015
1016        let names = font.glyph_names(&[0, 5, 45, 71, 1311, 3086]);
1017        assert_eq!(
1018            names,
1019            &[
1020                Cow::from(".notdef"),
1021                Cow::from("copyright"),
1022                Cow::from("uni25B6"),
1023                Cow::from("smileface"),
1024                Cow::from("u1FA95"),
1025                Cow::from("1f468-200d-1f33e")
1026            ]
1027        );
1028    }
1029
1030    #[test]
1031    fn test_glyph_names_post_v3() {
1032        // This font is a CFF font with a version 3 post table (no names in table).
1033        let font_buffer = read_fixture("tests/fonts/opentype/Klei.otf");
1034        let opentype_file = ReadScope::new(&font_buffer)
1035            .read::<OpenTypeFont<'_>>()
1036            .unwrap();
1037        let font_table_provider = opentype_file
1038            .table_provider(0)
1039            .expect("error reading font file");
1040        let font = Font::new(Box::new(font_table_provider)).expect("error reading font data");
1041
1042        let names = font.glyph_names(&[0, 5, 45, 100, 763, 1000 /* out of range */]);
1043        assert_eq!(
1044            names,
1045            &[
1046                Cow::from(".notdef"),
1047                Cow::from("dollar"),
1048                Cow::from("L"),
1049                Cow::from("yen"),
1050                Cow::from("uniFB00"),
1051                Cow::from("g1000") // out of range gid is assigned fallback name
1052            ]
1053        );
1054    }
1055
1056    #[test]
1057    fn test_lookup_sbix() {
1058        let font_buffer = read_fixture("tests/fonts/sbix/sbix-dupe.ttf");
1059        let opentype_file = ReadScope::new(&font_buffer)
1060            .read::<OpenTypeFont<'_>>()
1061            .unwrap();
1062        let font_table_provider = opentype_file
1063            .table_provider(0)
1064            .expect("error reading font file");
1065        let mut font = Font::new(Box::new(font_table_provider)).expect("error reading font data");
1066
1067        // Successfully read bitmap
1068        match font.lookup_glyph_image(1, 100, BitDepth::ThirtyTwo) {
1069            Ok(Some(BitmapGlyph {
1070                bitmap: Bitmap::Encapsulated(EncapsulatedBitmap { data, .. }),
1071                ..
1072            })) => {
1073                assert_eq!(data.len(), 224);
1074            }
1075            _ => panic!("Expected encapsulated bitmap, got something else."),
1076        }
1077
1078        // Successfully read bitmap pointed at by `dupe` record. Should end up returning data for
1079        // glyph 1.
1080        match font.lookup_glyph_image(2, 100, BitDepth::ThirtyTwo) {
1081            Ok(Some(BitmapGlyph {
1082                bitmap: Bitmap::Encapsulated(EncapsulatedBitmap { data, .. }),
1083                ..
1084            })) => {
1085                assert_eq!(data.len(), 224);
1086            }
1087            _ => panic!("Expected encapsulated bitmap, got something else."),
1088        }
1089
1090        // Handle recursive `dupe` record. Should return Ok(None) as recursion is stopped at one
1091        // level.
1092        match font.lookup_glyph_image(3, 100, BitDepth::ThirtyTwo) {
1093            Ok(None) => {}
1094            _ => panic!("Expected Ok(None) got something else"),
1095        }
1096    }
1097
1098    // Test that Font is only tied to the lifetime of the ReadScope and not any
1099    // intermediate types.
1100    #[test]
1101    fn table_provider_independent_of_font() {
1102        // Prior to code changes this function did not compile
1103        fn load_font<'a>(
1104            scope: ReadScope<'a>,
1105        ) -> Result<Font<DynamicFontTableProvider<'a>>, Box<dyn Error>> {
1106            let font_file = scope.read::<FontData<'_>>()?;
1107            let provider = font_file.table_provider(0)?;
1108            Font::new(provider).map_err(Box::from)
1109        }
1110
1111        let buffer =
1112            std::fs::read("tests/fonts/opentype/Klei.otf").expect("unable to read Klei.otf");
1113        let scope = ReadScope::new(&buffer);
1114        assert!(load_font(scope).is_ok());
1115    }
1116}