Skip to main content

zpl_forge/engine/
font.rs

1use std::collections::HashMap;
2
3use crate::{ZplError, ZplResult};
4use ab_glyph::FontArc;
5
6/// Default fallback font bytes embedded in the binary.
7/// This guarantees the library runs on any OS/Platform without C dependencies.
8const DEFAULT_FONT_BYTES: &[u8] = include_bytes!("../assets/Oswald-Regular.ttf");
9
10/// List of valid ZPL font identifiers (A-Z and 0-9).
11const FONT_MAP: &[char] = &[
12    'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S',
13    'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
14];
15
16/// Manages fonts and their mapping to ZPL font identifiers.
17///
18/// This structure tracks registered fonts and maps them to the single-character
19/// identifiers used in ZPL commands (e.g., '^A0', '^AA').
20#[derive(Debug, Clone)]
21pub struct FontManager {
22    /// Maps ZPL font identifiers (as Strings) to internal font names.
23    font_map: HashMap<String, String>,
24    /// Stores the actual font data indexed by internal font names.
25    font_index: HashMap<String, FontArc>,
26    /// Stores the raw TTF/OTF bytes indexed by internal font names.
27    font_bytes: HashMap<String, Vec<u8>>,
28}
29
30impl Default for FontManager {
31    /// Creates a `FontManager` with a lightweight open-source default font
32    /// registered for all identifiers ('A' to '9').
33    ///
34    /// Uses Inconsolata (SIL Open Font License) embedded directly in the binary,
35    /// ensuring zero native dependencies on the host OS.
36    fn default() -> Self {
37        let mut current = Self {
38            font_map: HashMap::new(),
39            font_index: HashMap::new(),
40            font_bytes: HashMap::new(),
41        };
42
43        // Register the embedded font for all alphanumeric ZPL identifiers
44        let _ = current.register_font("Oswald", DEFAULT_FONT_BYTES, 'A', '9');
45
46        current
47    }
48}
49
50impl FontManager {
51    /// Retrieves the raw TTF/OTF bytes for a font by its ZPL identifier.
52    ///
53    /// This is used by backends that need the raw font data (e.g., PDF embedding).
54    pub fn get_font_bytes(&self, name: &str) -> Option<&[u8]> {
55        let font_name = self.font_map.get(name)?;
56        self.font_bytes.get(font_name).map(|v| v.as_slice())
57    }
58
59    /// Returns the internal font name mapped to a ZPL identifier.
60    pub fn get_font_name(&self, name: &str) -> Option<&str> {
61        self.font_map.get(name).map(|s| s.as_str())
62    }
63
64    /// Retrieves a font by its ZPL identifier.
65    ///
66    /// # Arguments
67    /// * `name` - The ZPL font identifier (e.g., "0", "A").
68    pub fn get_font(&self, name: &str) -> Option<&FontArc> {
69        let font_name = self.font_map.get(name);
70        if let Some(font_name) = font_name {
71            self.font_index.get(font_name)
72        } else {
73            None
74        }
75    }
76
77    /// Registers a new font and maps it to a range of ZPL identifiers.
78    ///
79    /// Custom fonts must be in TrueType (`.ttf`) or OpenType (`.otf`) format.
80    /// Once registered, the font can be used in ZPL commands like `^A` or `^CF`
81    /// by referencing the assigned identifiers.
82    ///
83    /// # Arguments
84    /// * `name` - An internal name for the font.
85    /// * `bytes` - The raw TrueType/OpenType font data.
86    /// * `from` - The starting ZPL identifier in the range (A-Z, 0-9).
87    /// * `to` - The ending ZPL identifier in the range (A-Z, 0-9).
88    ///
89    /// # Errors
90    /// Returns an error if the font data is invalid.
91    ///
92    /// # Example
93    ///
94    /// ```rust
95    /// use zpl_forge::FontManager;
96    ///
97    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
98    /// let mut font_manager = FontManager::default();
99    ///
100    /// // Load your font file bytes
101    /// // let font_bytes = std::fs::read("fonts/Oswald-Regular.ttf")?;
102    ///
103    /// // Register it for a range of ZPL identifiers (e.g., from 'A' to 'Z')
104    /// // font_manager.register_font("Oswald", &font_bytes, 'A', 'Z')?;
105    /// # Ok(())
106    /// # }
107    /// ```
108    pub fn register_font(
109        &mut self,
110        name: &str,
111        bytes: &[u8],
112        from: char,
113        to: char,
114    ) -> ZplResult<()> {
115        let font = FontArc::try_from_vec(bytes.to_vec())
116            .map_err(|_| ZplError::FontError("Invalid font data".into()))?;
117        self.font_index.insert(name.to_string(), font);
118        self.font_bytes.insert(name.to_string(), bytes.to_vec());
119        self.assign_font(name, from, to);
120        Ok(())
121    }
122
123    /// Internal helper to assign a registered font to a range of ZPL identifiers.
124    fn assign_font(&mut self, name: &str, from: char, to: char) {
125        let from_idx = FONT_MAP.iter().position(|&x| x == from);
126        let to_idx = FONT_MAP.iter().position(|&x| x == to);
127
128        if from_idx.is_none() || to_idx.is_none() {
129            return;
130        }
131
132        if let (Some(start), Some(end)) = (from_idx, to_idx)
133            && start <= end
134        {
135            for key in &FONT_MAP[start..=end] {
136                self.font_map.insert(key.to_string(), name.to_string());
137            }
138        }
139    }
140}