Skip to main content

read_fonts/model/
font.rs

1//! Font representation.
2
3mod blob;
4mod format;
5mod instance;
6mod source;
7mod tables;
8
9pub use blob::FontBlob;
10pub use format::FontFormat;
11pub use instance::{
12    FontFeatureVariations, FontInstance, FontInstanceBuilder, FontVariation, NormalizedCoord,
13};
14pub use source::FontSource;
15pub use tables::{FontTableFunction, FontTables};
16
17// Do our best to not expose this to users through docs or rust-analyzer.
18#[doc(hidden)]
19#[rust_analyzer::completions(hidden_from_completion)]
20pub mod interop;
21
22use super::once::Once;
23use crate::{ps::type1::Type1Font, ReadError};
24use alloc::{boxed::Box, sync::Arc};
25use core::any::Any;
26
27/// An OpenType or PostScript font.
28///
29/// This type is internally reference counted, cheaply cloneable and thread
30/// safe.
31#[derive(Clone)]
32pub struct Font(Arc<FontRepr>);
33
34impl Font {
35    /// Creates a new font from the given source and font index.
36    ///
37    /// The index parameter specifies the desired font in a font collection
38    /// (ttc or otc) file. It is ignored if the data source is not a blob.
39    pub fn new(source: impl Into<FontSource>, index: u32) -> Result<Self, ReadError> {
40        let source = source.into();
41        let kind = if let Ok(tables) = FontTables::new(source.clone(), index) {
42            Some(FontKindRepr::Sfnt(tables, index))
43        } else if let FontSource::Blob(blob) = &source {
44            match FontFormat::new(blob) {
45                Some(FontFormat::Type1) => Type1Font::new(blob).ok().map(FontKindRepr::Type1),
46                // TODO: pure CFF fonts
47                _ => None,
48            }
49        } else {
50            None
51        };
52        let kind = kind.ok_or(ReadError::MalformedData("Data isn't a font"))?;
53        let repr = FontRepr {
54            source,
55            kind,
56            shaping_data: Once::new(),
57        };
58        Ok(Self(Arc::new(repr)))
59    }
60
61    /// Returns the underlying source of font data.
62    pub fn source(&self) -> &FontSource {
63        &self.0.source
64    }
65
66    /// Returns the underlying kind of the font.
67    pub fn kind(&self) -> FontKind<'_> {
68        match &self.0.kind {
69            FontKindRepr::Sfnt(tables, index) => FontKind::Sfnt(tables, *index),
70            FontKindRepr::Type1(font) => FontKind::Type1(font),
71        }
72    }
73
74    /// Returns an object that provides access to individual font tables.
75    ///
76    /// For non-SFNT fonts, this will return an empty set of tables.
77    pub fn tables(&self) -> &FontTables {
78        if let FontKindRepr::Sfnt(tables, _) = &self.0.kind {
79            tables
80        } else {
81            &tables::EMPTY_FONT_TABLES
82        }
83    }
84}
85
86struct FontRepr {
87    source: FontSource,
88    kind: FontKindRepr,
89    // Storage cell for lazily loaded HarfRust shaping data.
90    shaping_data: Once<Box<dyn Any + Send + Sync>>,
91}
92
93/// The underlying type of a font.
94#[derive(Clone)]
95pub enum FontKind<'a> {
96    /// An SFNT-based font represented by a set of tables and an index.
97    Sfnt(&'a FontTables, u32),
98    /// An Adobe Type1 font.
99    Type1(&'a Type1Font),
100}
101
102/// The underlying type of a font.
103#[expect(clippy::large_enum_variant)]
104enum FontKindRepr {
105    Sfnt(FontTables, u32),
106    Type1(Type1Font),
107}