pub mod cid_mapper;
pub mod cmap_utils;
pub mod embedder;
pub mod font_cache;
pub mod font_descriptor;
pub mod font_metrics;
pub mod loader;
pub mod standard_14;
pub mod ttf_parser;
pub mod type0;
pub mod type0_parsing;
pub use cid_mapper::{analyze_unicode_ranges, CidMapping, UnicodeRanges};
pub use embedder::{EmbeddingOptions, FontEmbedder, FontEncoding};
pub use font_cache::FontCache;
pub use font_descriptor::{FontDescriptor, FontFlags};
pub use font_metrics::{FontMetrics, TextMeasurement};
pub use loader::{FontData, FontFormat, FontLoader};
pub use standard_14::Standard14Font;
pub use ttf_parser::{GlyphMapping, TtfParser};
pub use type0::{create_type0_from_font, needs_type0_font, Type0Font};
pub use type0_parsing::{
detect_cidfont_subtype, detect_type0_font, extract_default_width, extract_descendant_fonts_ref,
extract_font_descriptor_ref, extract_font_file_ref, extract_tounicode_ref, extract_widths_ref,
resolve_type0_hierarchy, CIDFontSubtype, FontFileType, Type0FontInfo, MAX_FONT_STREAM_SIZE,
};
use crate::Result;
#[derive(Debug, Clone)]
pub struct Font {
pub name: String,
pub data: Vec<u8>,
pub format: FontFormat,
pub metrics: FontMetrics,
pub descriptor: FontDescriptor,
pub glyph_mapping: GlyphMapping,
}
impl Font {
pub fn new(name: impl Into<String>) -> Self {
Font {
name: name.into(),
data: Vec::new(),
format: FontFormat::TrueType,
metrics: FontMetrics::default(),
descriptor: FontDescriptor::default(),
glyph_mapping: GlyphMapping::default(),
}
}
pub fn from_file(name: impl Into<String>, path: impl AsRef<std::path::Path>) -> Result<Self> {
let data = std::fs::read(path)?;
Self::from_bytes(name, data)
}
pub fn from_bytes(name: impl Into<String>, data: Vec<u8>) -> Result<Self> {
let name = name.into();
let format = FontFormat::detect(&data)?;
let parser = TtfParser::new(&data)?;
let metrics = parser.extract_metrics()?;
let descriptor = parser.create_descriptor()?;
let glyph_mapping = parser.extract_glyph_mapping()?;
Ok(Font {
name,
data,
format,
metrics,
descriptor,
glyph_mapping,
})
}
pub fn postscript_name(&self) -> &str {
&self.descriptor.font_name
}
pub fn has_glyph(&self, ch: char) -> bool {
self.glyph_mapping.char_to_glyph(ch).is_some()
}
pub fn measure_text(&self, text: &str, font_size: f32) -> TextMeasurement {
self.metrics
.measure_text(text, font_size, &self.glyph_mapping)
}
pub fn line_height(&self, font_size: f32) -> f32 {
self.metrics.line_height(font_size)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_font_format_detection() {
let ttf_data = vec![0x00, 0x01, 0x00, 0x00];
assert!(matches!(
FontFormat::detect(&ttf_data),
Ok(FontFormat::TrueType)
));
let otf_data = vec![0x4F, 0x54, 0x54, 0x4F];
assert!(matches!(
FontFormat::detect(&otf_data),
Ok(FontFormat::OpenType)
));
let invalid_data = vec![0xFF, 0xFF, 0xFF, 0xFF];
assert!(FontFormat::detect(&invalid_data).is_err());
}
#[test]
fn test_font_new() {
let font = Font::new("TestFont");
assert_eq!(font.name, "TestFont");
assert!(font.data.is_empty(), "Data should be empty for new font");
assert!(
matches!(font.format, FontFormat::TrueType),
"Default format should be TrueType"
);
}
#[test]
fn test_font_new_with_string() {
let font = Font::new("Arial".to_string());
assert_eq!(font.name, "Arial");
assert!(font.data.is_empty());
}
#[test]
fn test_font_postscript_name() {
let mut font = Font::new("TestFont");
font.descriptor.font_name = "Helvetica-Bold".to_string();
assert_eq!(font.postscript_name(), "Helvetica-Bold");
}
#[test]
fn test_font_has_glyph_with_empty_mapping() {
let font = Font::new("TestFont");
assert!(!font.has_glyph('A'), "Empty mapping should not have glyph");
assert!(!font.has_glyph('€'), "Empty mapping should not have glyph");
}
#[test]
fn test_font_measure_text_with_defaults() {
let font = Font::new("TestFont");
let measurement = font.measure_text("Hello", 12.0);
assert_eq!(
measurement.width, 36.0,
"5 chars with default 600 units at 12pt should be 36.0"
);
}
#[test]
fn test_font_line_height_with_defaults() {
let font = Font::new("TestFont");
let line_height = font.line_height(12.0);
assert_eq!(
line_height, 14.4,
"Default metrics should produce 14.4 line height at 12pt"
);
}
#[test]
fn test_font_from_file_nonexistent() {
let result = Font::from_file("TestFont", "/nonexistent/path/font.ttf");
assert!(
result.is_err(),
"Loading nonexistent file should return error"
);
}
#[test]
fn test_font_from_bytes_invalid_format() {
let invalid_data = vec![0xFF, 0xFE, 0xFD, 0xFC, 0x00, 0x01, 0x02, 0x03];
let result = Font::from_bytes("InvalidFont", invalid_data);
assert!(
result.is_err(),
"Invalid font data should return error from FontFormat::detect"
);
}
#[test]
fn test_font_from_bytes_too_small() {
let tiny_data = vec![0x00, 0x01];
let result = Font::from_bytes("TinyFont", tiny_data);
assert!(
result.is_err(),
"Too small data should return error during detection"
);
}
#[test]
fn test_font_name_conversion() {
let font1 = Font::new("StrName");
let font2 = Font::new("StringName".to_string());
assert_eq!(font1.name, "StrName");
assert_eq!(font2.name, "StringName");
}
#[test]
fn test_font_fields_are_accessible() {
let mut font = Font::new("TestFont");
font.name = "ModifiedName".to_string();
font.data = vec![1, 2, 3, 4];
font.format = FontFormat::OpenType;
assert_eq!(font.name, "ModifiedName");
assert_eq!(font.data, vec![1, 2, 3, 4]);
assert!(matches!(font.format, FontFormat::OpenType));
}
}