font_map_core/
font.rs

1//! This module contains the font enumeration and glyph data structures
2//!
3//! The `Font` struct contains all the glyphs in a font, along with any stored strings
4//!
5//! The `Glyph` struct contains information about a single glyph in a font:
6//! - Unicode codepoint
7//! - Postscript name
8//! - Outline data
9//!
10#![allow(clippy::match_on_vec_items)]
11#![allow(clippy::cast_possible_truncation)]
12pub use crate::raw::ttf::NameKind as StringKind;
13use crate::{
14    error::ParseResult,
15    raw::ttf::{GlyfOutline, SimpleGlyf, TrueTypeFont},
16    svg::SvgExt,
17};
18use std::{
19    borrow::Cow,
20    collections::{HashMap, HashSet},
21};
22
23/// A parsed font, with access to its glyphs and stored strings
24#[derive(Debug, Clone)]
25pub struct Font {
26    glyphs: Vec<Glyph>,
27    strings: HashMap<StringKind, String>,
28}
29impl Font {
30    /// Creates a new font from the given font data
31    ///
32    /// # Errors
33    /// Returns an error if the font data is invalid or cannot be parsed
34    pub fn new(font_data: &[u8]) -> ParseResult<Self> {
35        let font = TrueTypeFont::new(font_data)?;
36        Ok(font.into())
37    }
38
39    /// Creates a new font from the font file at the specified path
40    ///
41    /// # Errors
42    /// Returns an error if the font data is invalid or cannot be parsed
43    pub fn from_file(path: impl AsRef<std::path::Path>) -> ParseResult<Self> {
44        let font_data = std::fs::read(path)?;
45        Self::new(&font_data)
46    }
47
48    /// Returns the string with the specified kind, if it exists
49    #[must_use]
50    pub fn string(&self, kind: StringKind) -> Option<&str> {
51        self.strings.get(&kind).map(String::as_str)
52    }
53
54    /// Returns all the strings in the font
55    #[must_use]
56    pub fn strings(&self) -> &HashMap<StringKind, String> {
57        &self.strings
58    }
59
60    /// Returns the glyph with the specified unicode codepoint, if it exists
61    #[must_use]
62    pub fn glyph(&self, codepoint: u32) -> Option<&Glyph> {
63        self.glyphs.iter().find(|g| g.codepoint == codepoint)
64    }
65
66    /// Returns the glyph with the specified postscript name, if it exists
67    #[must_use]
68    pub fn glyph_named(&self, name: &str) -> Option<&Glyph> {
69        self.glyphs.iter().find(|g| g.name == name)
70    }
71
72    /// Returns the glyphs in the font
73    #[must_use]
74    pub fn glyphs(&self) -> &[Glyph] {
75        &self.glyphs
76    }
77}
78
79impl From<TrueTypeFont> for Font {
80    fn from(value: TrueTypeFont) -> Self {
81        let cmap = value.cmap_table;
82        let post = value.post_table;
83        let name = value.name_table;
84        let glyf = value.glyf_table;
85
86        let mut strings = HashMap::new();
87        for record in name.records {
88            strings.insert(record.name_id, record.name);
89        }
90
91        let mut glyphs = Vec::new();
92        let mut codepoint_hash = HashSet::new();
93        for (glyph_index, name) in post.glyph_names.into_iter().enumerate() {
94            let name = Cow::Owned(name);
95            let glyph_index = glyph_index as u16;
96
97            // Find unicode codepoint, skipping unmapped glyphs
98            let codepoint = cmap.get_codepoint(glyph_index);
99            let codepoint = match codepoint {
100                Some(c) if glyph_index == 0 => c,
101                Some(c) if c != 0xFFFF => c,
102                _ => continue,
103            };
104
105            // Skip duplicate codepoints
106            if !codepoint_hash.insert(codepoint) {
107                continue;
108            }
109
110            // Get the glyph outline data
111            let outline = match glyf[glyph_index as usize] {
112                GlyfOutline::Simple(ref outline) => outline.clone(),
113                GlyfOutline::Compound(ref outline) => outline.as_simple(&glyf),
114            };
115            let preview = GlyphPreview::Ttf(outline);
116
117            glyphs.push(Glyph {
118                codepoint,
119                name,
120                preview,
121            });
122        }
123
124        Self { glyphs, strings }
125    }
126}
127
128/// A preview of a glyph, either as a TTF outline or SVG image
129#[derive(Debug, Clone)]
130pub enum GlyphPreview {
131    /// TTF formatted glyph data - converted to simple fmt if needed
132    Ttf(SimpleGlyf),
133
134    /// SVG formatted glyph data, as a string
135    Svg(Cow<'static, str>),
136}
137impl SvgExt for GlyphPreview {
138    fn to_svg(&self) -> String {
139        match self {
140            Self::Ttf(outline) => outline.to_svg(),
141            Self::Svg(svg) => svg.to_string(),
142        }
143    }
144}
145
146/// A single glyph in a font
147#[derive(Debug, Clone)]
148pub struct Glyph {
149    codepoint: u32,
150    name: Cow<'static, str>,
151    preview: GlyphPreview,
152}
153impl Glyph {
154    /// Creates a new glyph with the specified codepoint, name, and preview data
155    #[must_use]
156    pub const fn new(codepoint: u32, name: &'static str, preview: GlyphPreview) -> Self {
157        Self {
158            codepoint,
159            name: Cow::Borrowed(name),
160            preview,
161        }
162    }
163
164    /// Returns the unicode range for the glyph
165    #[must_use]
166    pub fn unicode_range(&self) -> &'static str {
167        crate::unicode_range::unicode_range(self.codepoint)
168    }
169
170    /// Returns the unicode codepoint for the glyph
171    #[must_use]
172    pub fn codepoint(&self) -> u32 {
173        self.codepoint
174    }
175
176    /// Returns the character for the glyph
177    #[must_use]
178    pub fn char(&self) -> char {
179        std::char::from_u32(self.codepoint).unwrap_or(char::REPLACEMENT_CHARACTER)
180    }
181
182    /// Returns the postscript name of the glyph
183    #[must_use]
184    pub fn name(&self) -> &str {
185        &self.name
186    }
187
188    /// Returns the raw visual data of this glyph  
189    /// Compound glyphs will be simplified to a single outline
190    #[must_use]
191    pub fn outline(&self) -> &GlyphPreview {
192        &self.preview
193    }
194
195    /// Returns the SVG data of this glyph's outline  
196    #[must_use]
197    pub fn svg_preview(&self) -> String {
198        self.preview.to_svg()
199    }
200
201    /// Returns the gzip compressed SVGZ data of this glyph
202    ///
203    /// # Errors
204    /// Returns an error if the data cannot be compressed
205    #[cfg(feature = "extended-svg")]
206    #[cfg_attr(docsrs, doc(cfg(feature = "extended-svg")))]
207    pub fn svgz_preview(&self) -> std::io::Result<Vec<u8>> {
208        self.preview.to_svgz()
209    }
210
211    /// Generates a `data:image` link containing the svg data for this glyph  
212    ///
213    /// # Errors
214    /// Returns an error if the data cannot be encoded properly
215    #[cfg(feature = "extended-svg")]
216    #[cfg_attr(docsrs, doc(cfg(feature = "extended-svg")))]
217    pub fn svg_dataimage_url(&self) -> std::io::Result<String> {
218        self.preview.to_svg_dataimage_url()
219    }
220}
221
222impl From<Glyph> for char {
223    fn from(value: Glyph) -> Self {
224        value.char()
225    }
226}
227
228impl From<&Glyph> for char {
229    fn from(value: &Glyph) -> Self {
230        value.char()
231    }
232}
233
234impl From<Glyph> for u32 {
235    fn from(value: Glyph) -> Self {
236        value.codepoint()
237    }
238}
239
240impl From<&Glyph> for u32 {
241    fn from(value: &Glyph) -> Self {
242        value.codepoint()
243    }
244}
245
246impl std::fmt::Display for Glyph {
247    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
248        write!(f, "{}", self.char())
249    }
250}