libyaff/
lib.rs

1//! # LibYAFF: Yet Another Font Format Library
2//!
3//! A Rust library for parsing, manipulating, and generating bitmap fonts in the YAFF format.
4//!
5//! ## Features
6//!
7//! - **Complete YAFF format support**: Parse and generate YAFF 1.0.x format files
8//! - **Unicode and legacy encoding**: Support for Unicode, codepoint, and tag-based glyph labeling
9//! - **Advanced typography**: Kerning, bearing adjustments, and font metrics
10//! - **Robust parsing**: Handles format variations and provides detailed error messages
11//! - **Memory efficient**: Optimized for embedded and resource-constrained environments
12//!
13//! ## Quick Start
14//!
15//! ```rust,no_run
16//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
17//! # #[cfg(feature = "parsing")]
18//! # {
19//! use libyaff::{YaffFont, to_yaff_string};
20//!
21//! // Load a YAFF font from file
22//! let font = YaffFont::from_path("my_font.yaff")?;
23//! println!("Loaded font: {}", font.name.as_ref().unwrap_or(&"".to_string()));
24//!
25//! // Access font metrics
26//! if let Some(ascent) = font.ascent {
27//!     println!("Font ascent: {}", ascent);
28//! }
29//!
30//! // Convert back to YAFF format
31//! let yaff_content = to_yaff_string(&font);
32//! std::fs::write("output.yaff", yaff_content)?;
33//! # }
34//! # Ok(())
35//! # }
36//! ```
37//!
38//! ## Font Structure
39//!
40//! A YAFF font consists of:
41//! - **Metadata**: Font name, family, size, style information
42//! - **Metrics**: Ascent, descent, line height, kerning data
43//! - **Glyphs**: Individual character bitmaps with labels and metrics
44//!
45//! ## Error Handling
46//!
47//! All parsing operations return `Result<T, ParseError>` with detailed error information
48//! including line numbers and context for debugging malformed YAFF files.
49
50#[cfg(feature = "encoding")]
51mod encoder;
52mod models;
53#[cfg(feature = "parsing")]
54mod parser;
55mod utils;
56
57#[cfg(feature = "encoding")]
58pub use crate::encoder::to_yaff_string;
59pub use crate::models::*;
60#[cfg(feature = "parsing")]
61pub use crate::parser::{classify_line, parse_key_as_label};
62pub use crate::utils::{
63    calculate_ascent, convert_codepoint_to_unicode_labels, minimize_all_bounding_boxes,
64    minimize_glyph_bounding_box, set_ascent,
65};
66#[cfg(feature = "parsing")]
67use std::fs::File;
68#[cfg(feature = "parsing")]
69use std::io::BufReader;
70#[cfg(feature = "parsing")]
71use std::path::Path;
72
73impl YaffFont {
74    pub fn new() -> Self {
75        Self::default()
76    }
77}
78
79#[cfg(feature = "parsing")]
80use std::str::FromStr;
81
82#[cfg(feature = "parsing")]
83impl FromStr for YaffFont {
84    type Err = ParseError;
85
86    fn from_str(s: &str) -> Result<Self, Self::Err> {
87        parser::from_str(s)
88    }
89}
90
91#[cfg(feature = "parsing")]
92impl YaffFont {
93
94    pub fn from_reader<R: std::io::Read>(mut reader: R) -> Result<YaffFont, ParseError> {
95        let mut buffer = String::new();
96        reader.read_to_string(&mut buffer)?;
97        Self::from_str(&buffer)
98    }
99
100    pub fn from_path<P: AsRef<Path>>(path: P) -> Result<Self, ParseError> {
101        let file = File::open(path).map_err(ParseError::Io)?;
102        let reader = BufReader::new(file);
103        Self::from_reader(reader)
104    }
105}
106
107#[cfg(test)]
108mod tests {
109    use super::*;
110
111    #[test]
112    fn test_font_creation_and_defaults() {
113        let font = YaffFont::new();
114        assert!(font.name.is_none());
115        assert!(font.glyphs.is_empty());
116        assert_eq!(font.yaff_version, None);
117    }
118
119    #[test]
120    fn test_font_property_setting() {
121        let mut font = YaffFont::new();
122        font.name = Some("Test Font".to_string());
123        font.ascent = Some(10);
124        font.descent = Some(-2);
125
126        assert_eq!(font.name, Some("Test Font".to_string()));
127        assert_eq!(font.ascent, Some(10));
128        assert_eq!(font.descent, Some(-2));
129    }
130
131    #[test]
132    fn test_glyph_creation() {
133        let glyph = GlyphDefinition {
134            labels: vec![Label::Unicode(vec![65])], // 'A'
135            bitmap: Bitmap::default(),
136            left_bearing: Some(-1),
137            right_bearing: Some(2),
138            ..Default::default()
139        };
140
141        assert_eq!(glyph.labels.len(), 1);
142        assert_eq!(glyph.left_bearing, Some(-1));
143        assert_eq!(glyph.right_bearing, Some(2));
144        assert!(glyph.bitmap.pixels.is_empty());
145    }
146
147    #[test]
148    fn test_label_variants() {
149        let unicode_label = Label::Unicode(vec![65, 66]); // "AB"
150        let codepoint_label = Label::Codepoint(vec![65, 66]);
151        let tag_label = Label::Tag("my-tag".to_string());
152        let anonymous_label = Label::Anonymous;
153
154        assert!(matches!(unicode_label, Label::Unicode(_)));
155        assert!(matches!(codepoint_label, Label::Codepoint(_)));
156        assert!(matches!(tag_label, Label::Tag(_)));
157        assert!(matches!(anonymous_label, Label::Anonymous));
158    }
159
160    #[test]
161    fn test_bitmap_creation() {
162        let bitmap = Bitmap {
163            pixels: vec![vec![true, false, true], vec![false, true, false]],
164            width: 3,
165            height: 2,
166        };
167
168        assert_eq!(bitmap.width, 3);
169        assert_eq!(bitmap.height, 2);
170        assert_eq!(bitmap.pixels.len(), 2);
171        assert_eq!(bitmap.pixels[0].len(), 3);
172    }
173}