zpl_forge/engine/
font.rs

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