font_map_core/
font.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
//! This module contains the font enumeration and glyph data structures
//!
//! The `Font` struct contains all the glyphs in a font, along with any stored strings
//!
//! The `Glyph` struct contains information about a single glyph in a font:
//! - Unicode codepoint
//! - Postscript name
//! - Outline data
//!
#![allow(clippy::match_on_vec_items)]
#![allow(clippy::cast_possible_truncation)]
pub use crate::raw::ttf::NameKind as StringKind;
use crate::{
    error::ParseResult,
    raw::ttf::{GlyfOutline, SimpleGlyf, TrueTypeFont},
    svg::SvgExt,
};
use std::{borrow::Cow, collections::HashMap};

/// A parsed font, with access to its glyphs and stored strings
#[derive(Debug, Clone)]
pub struct Font {
    glyphs: Vec<Glyph>,
    strings: HashMap<StringKind, String>,
}
impl Font {
    /// Creates a new font from the given font data
    ///
    /// # Errors
    /// Returns an error if the font data is invalid or cannot be parsed
    pub fn new(font_data: &[u8]) -> ParseResult<Self> {
        let font = TrueTypeFont::new(font_data)?;
        Ok(font.into())
    }

    /// Creates a new font from the font file at the specified path
    ///
    /// # Errors
    /// Returns an error if the font data is invalid or cannot be parsed
    pub fn from_file(path: impl AsRef<std::path::Path>) -> ParseResult<Self> {
        let font_data = std::fs::read(path)?;
        Self::new(&font_data)
    }

    /// Returns the string with the specified kind, if it exists
    #[must_use]
    pub fn string(&self, kind: StringKind) -> Option<&str> {
        self.strings.get(&kind).map(String::as_str)
    }

    /// Returns all the strings in the font
    #[must_use]
    pub fn strings(&self) -> &HashMap<StringKind, String> {
        &self.strings
    }

    /// Returns the glyph with the specified unicode codepoint, if it exists
    #[must_use]
    pub fn glyph(&self, codepoint: u32) -> Option<&Glyph> {
        self.glyphs.iter().find(|g| g.codepoint == codepoint)
    }

    /// Returns the glyph with the specified postscript name, if it exists
    #[must_use]
    pub fn glyph_named(&self, name: &str) -> Option<&Glyph> {
        self.glyphs.iter().find(|g| g.name == name)
    }

    /// Returns the glyphs in the font
    #[must_use]
    pub fn glyphs(&self) -> &[Glyph] {
        &self.glyphs
    }
}

impl From<TrueTypeFont> for Font {
    fn from(value: TrueTypeFont) -> Self {
        let cmap = value.cmap_table;
        let post = value.post_table;
        let name = value.name_table;
        let glyf = value.glyf_table;

        let mut strings = HashMap::new();
        for record in name.records {
            strings.insert(record.name_id, record.name);
        }

        let mut glyphs = Vec::new();
        for (glyph_index, name) in post.glyph_names.into_iter().enumerate() {
            let name = Cow::Owned(name);
            let glyph_index = glyph_index as u16;

            // Find unicode codepoint, skipping unmapped glyphs
            let codepoint = cmap.unicode_subtable.get_codepoint(glyph_index);
            let codepoint = match codepoint {
                Some(c) if glyph_index == 0 => c,
                Some(c) if c != 0xFFFF => c,
                _ => continue,
            };

            // Get the glyph outline data
            let outline = match glyf[glyph_index as usize] {
                GlyfOutline::Simple(ref outline) => outline.clone(),
                GlyfOutline::Compound(ref outline) => outline.as_simple(&glyf),
            };
            let preview = GlyphPreview::Ttf(outline);

            glyphs.push(Glyph {
                codepoint,
                name,
                preview,
            });
        }

        Self { glyphs, strings }
    }
}

/// A preview of a glyph, either as a TTF outline or SVG image
#[derive(Debug, Clone)]
pub enum GlyphPreview {
    /// TTF formatted glyph data - converted to simple fmt if needed
    Ttf(SimpleGlyf),

    /// SVG formatted glyph data, as a string
    Svg(Cow<'static, str>),
}
impl SvgExt for GlyphPreview {
    fn to_svg(&self) -> String {
        match self {
            Self::Ttf(outline) => outline.to_svg(),
            Self::Svg(svg) => svg.to_string(),
        }
    }
}

/// A single glyph in a font
#[derive(Debug, Clone)]
pub struct Glyph {
    codepoint: u32,
    name: Cow<'static, str>,
    preview: GlyphPreview,
}
impl Glyph {
    /// Creates a new glyph with the specified codepoint, name, and preview data
    #[must_use]
    pub const fn new(codepoint: u32, name: &'static str, preview: GlyphPreview) -> Self {
        Self {
            codepoint,
            name: Cow::Borrowed(name),
            preview,
        }
    }

    /// Returns the unicode range for the glyph
    #[must_use]
    pub fn unicode_range(&self) -> &str {
        crate::unicode_range::unicode_range(self.codepoint)
    }

    /// Returns the unicode codepoint for the glyph
    #[must_use]
    pub fn codepoint(&self) -> u32 {
        self.codepoint
    }

    /// Returns the character for the glyph
    #[must_use]
    pub fn char(&self) -> char {
        std::char::from_u32(self.codepoint).unwrap_or(char::REPLACEMENT_CHARACTER)
    }

    /// Returns the postscript name of the glyph
    #[must_use]
    pub fn name(&self) -> &str {
        &self.name
    }

    /// Returns the raw visual data of this glyph  
    /// Compound glyphs will be simplified to a single outline
    #[must_use]
    pub fn outline(&self) -> &GlyphPreview {
        &self.preview
    }

    /// Returns the SVG data of this glyph's outline  
    #[must_use]
    pub fn svg_preview(&self) -> String {
        self.preview.to_svg()
    }

    /// Returns the gzip compressed SVGZ data of this glyph
    ///
    /// # Errors
    /// Returns an error if the data cannot be compressed
    #[cfg(feature = "extended-svg")]
    #[cfg_attr(docsrs, doc(cfg(feature = "extended-svg")))]
    pub fn svgz_preview(&self) -> std::io::Result<Vec<u8>> {
        self.preview.to_svgz()
    }

    /// Generates a `data:image` link containing the svg data for this glyph  
    ///
    /// # Errors
    /// Returns an error if the data cannot be encoded properly
    #[cfg(feature = "extended-svg")]
    #[cfg_attr(docsrs, doc(cfg(feature = "extended-svg")))]
    pub fn svg_dataimage_url(&self) -> std::io::Result<String> {
        self.preview.to_svg_dataimage_url()
    }
}

impl From<Glyph> for char {
    fn from(value: Glyph) -> Self {
        value.char()
    }
}

impl From<&Glyph> for char {
    fn from(value: &Glyph) -> Self {
        value.char()
    }
}

impl From<Glyph> for u32 {
    fn from(value: Glyph) -> Self {
        value.codepoint()
    }
}

impl From<&Glyph> for u32 {
    fn from(value: &Glyph) -> Self {
        value.codepoint()
    }
}

impl std::fmt::Display for Glyph {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "{}", self.char())
    }
}