| Crates.io | fonts |
| lib.rs | fonts |
| version | 0.2.0 |
| created_at | 2025-09-11 19:15:39.646196+00 |
| updated_at | 2025-09-11 19:21:43.054458+00 |
| description | High-performance font parsing and analysis library for Grida Canvas |
| homepage | https://grida.co |
| repository | https://github.com/gridaco/grida |
| max_upload_size | |
| id | 1834410 |
| size | 445,938 |
A high-performance font parsing and selection library for the Grida design tool, providing comprehensive font selection capabilities, italic detection, variable font support, and JSON serialization for WASM communication. This library implements the Blink (Chrome) font selection model for professional-grade font handling.
This crate implements a complete font selection pipeline that follows the Blink (Chrome) font selection model. It provides reliable font selection and italic classification using structured font table data (OS/2 bits, ital axis, and slnt axis). The library is organized around the concept of "Font Selection" with a clean, modular architecture that supports both high-level UI APIs and low-level font parsing.
Key Features:
fvar table parsing with axes and instancesuse fonts::{UIFontParser, UIFontFace};
// Create parser and load font data
let parser = UIFontParser::new();
let font_faces = vec![
UIFontFace {
face_id: "Inter-Regular.ttf".to_string(),
data: std::fs::read("Inter-Regular.ttf")?,
user_font_style_italic: Some(false), // User declares this is not italic
},
UIFontFace {
face_id: "Inter-Italic.ttf".to_string(),
data: std::fs::read("Inter-Italic.ttf")?,
user_font_style_italic: Some(true), // User declares this is italic
},
];
// Analyze font family
let result = parser.analyze_family(Some("Inter".to_string()), font_faces)?;
// Check italic capabilities
println!("Family: {}", result.family_name);
println!("Has italic: {}", result.italic_capability.has_italic);
println!("Strategy: {:?}", result.italic_capability.strategy);
// Find closest italic variants
let italic_matches = parser.get_italics(
Some("Inter".to_string()),
font_faces,
None, // No current style specified
Some(3), // Max 3 results
)?;
if let Some(closest) = italic_matches.first() {
println!("Closest italic: {}", closest.recipe.name);
}
ttf-parserfvar table parsing with axes and instances (Level 1)GSUB tableital Axis: Support for variable fonts with italic axisslnt Axis: Support for slant-based italic in variable fontsslnt axis and italic instancesVec instead of HashMap for consistent JSONThe library is organized into focused modules following the Selection terminology:
parse: Low-level font parsing functionality using ttf-parserselection: Core font selection logic and classificationselection_italic: Italic-specific selection functionality with legacy compatibilityparse_ui: High-level UI-friendly API for font analysisserde: JSON serialization for WASM communication (optional feature)parse_ui) - Recommended for most use casesselection) - For advanced font selection logicparse) - For direct font file analysisselection_italic) - Backward compatibility layeruse fonts::{FontSelectionParser, FaceRecord, FontStyle};
// Create font selection parser
let parser = FontSelectionParser::new();
// Create face records (typically from font files)
let face_records = vec![
FaceRecord {
face_id: "Inter-Regular.ttf".to_string(),
ps_name: "Inter-Regular".to_string(),
family_name: "Inter".to_string(),
subfamily_name: "Regular".to_string(),
is_variable: false,
axes: std::collections::HashMap::new(),
os2_italic_bit: false,
weight_class: 400,
width_class: 5,
user_font_style_italic: None,
// ... other fields
},
];
// Build capability map for font selection
let capability_map = parser.build_capability_map(face_records);
// Select a font face based on style requirements
let selection = parser.select_face(&capability_map, 400, 5, FontStyle::Italic);
match selection {
FontSelection::Selected { face_id, vf_recipe, .. } => {
println!("Selected face: {}", face_id);
if let Some(recipe) = vf_recipe {
println!("Variable font recipe: {:?}", recipe.axis_values);
}
}
FontSelection::Unavailable => {
println!("No suitable face found");
}
}
use fonts::UIFontParser;
// Create UI parser
let parser = UIFontParser::new();
// Load font family data with user-specified IDs
let font_faces = vec![
UIFontFace {
face_id: "Inter-Regular.ttf".to_string(),
data: std::fs::read("Inter-Regular.ttf")?,
user_font_style_italic: Some(false), // User declares this is not italic
},
UIFontFace {
face_id: "Inter-Italic.ttf".to_string(),
data: std::fs::read("Inter-Italic.ttf")?,
user_font_style_italic: Some(true), // User declares this is italic
},
];
// Analyze entire family
let result = parser.analyze_family(Some("Inter".to_string()), font_faces)?;
// Display family information
println!("Family: {}", result.family_name);
println!("Italic available: {}", result.italic_capability.has_italic);
println!("Strategy: {:?}", result.italic_capability.strategy);
// Show available styles
for recipe in &result.italic_capability.recipes {
println!("Style: {} - {}", recipe.name, recipe.description);
if let Some(vf_recipe) = &recipe.vf_recipe {
println!(" VF Recipe: {:?}", vf_recipe.axis_values);
}
}
// Show variable font axes
if let Some(vf_info) = &result.variable_font_info {
for axis in &vf_info.axes {
println!("Axis: {} ({}): {} to {}", axis.tag, axis.name, axis.min, axis.max);
}
}
use fonts::{Parser, FontSelectionParser};
// Parse individual font files
let font_data = std::fs::read("font.ttf")?;
let parser = Parser::new(&font_data)?;
let face_record = parser.extract_face_record("font-face-id".to_string(), None)?;
// Manual font selection
let selection_parser = FontSelectionParser::new();
let classification = selection_parser.classify_face(face_record);
// Build capability map
let faces = vec![face_record1, face_record2, face_record3];
let capability_map = selection_parser.build_capability_map(faces);
serde feature)use fonts::serde::*;
// Enable serde feature: cargo build --features serde
// Convert analysis results to JSON
let response = FontAnalysisResponse {
classifications: vec![FaceClassificationJson::from(classification)],
capability_map: ItalicCapabilityMapJson::from(capability_map),
fvar_data: None,
stat_data: None,
metadata: AnalysisMetadata {
face_count: 1,
has_variable_fonts: false,
timestamp: "2024-01-15T10:30:00Z".to_string(),
engine_version: "0.1.0".to_string(),
},
};
// Serialize to JSON
let json_string = serde_json::to_string(&response)?;
Add to your Cargo.toml:
[dependencies]
grida-canvas-fonts = "0.1.0"
# Optional: Enable JSON serialization for WASM
[dependencies.grida-canvas-fonts]
version = "0.1.0"
features = ["serde"]
UIFontParserMain high-level parser for UI consumption.
pub struct UIFontParser {
// Main analysis method
pub fn analyze_family(
&self,
family_name: Option<String>,
font_faces: Vec<UIFontFace>,
) -> Result<UIFontFamilyResult, String>,
// Style matching methods
pub fn get_italics(
&self,
family_name: Option<String>,
font_faces: Vec<UIFontFace>,
current_style: Option<CurrentTextStyle>,
max_results: Option<usize>,
) -> Result<Vec<ItalicMatch>, String>
pub fn get_romans(
&self,
family_name: Option<String>,
font_faces: Vec<UIFontFace>,
current_style: Option<CurrentTextStyle>,
max_results: Option<usize>,
) -> Result<Vec<ItalicMatch>, String>
pub fn get_faces(
&self,
face_type: FaceType,
family_name: Option<String>,
font_faces: Vec<UIFontFace>,
current_style: Option<CurrentTextStyle>,
max_results: Option<usize>,
) -> Result<Vec<ItalicMatch>, String>
}
UIFontFaceRepresents a font face with user-specified ID, data, and optional style declaration.
pub struct UIFontFace {
pub face_id: String, // User-specified identifier (e.g., filename, URL, index)
pub data: Vec<u8>, // Raw font data
pub user_font_style_italic: Option<bool>, // User-declared italic style (highest priority when set)
}
CurrentTextStyleRepresents the current text style being used, with only relevant properties for matching.
pub struct CurrentTextStyle {
pub weight: Option<u16>, // Font weight (100-900)
pub width: Option<u16>, // Font width/stretch (50-200)
pub slant: Option<f32>, // Font slant angle (-90 to 90 degrees)
pub custom_axes: HashMap<String, f32>, // Additional custom axis values
}
Smart Auto-Resolution: The parser intelligently handles duplicated values between explicit properties and custom axes:
weight property takes priority over custom_axes["wght"] if both are setwidth property takes priority over custom_axes["wdth"] if both are setslant property takes priority over custom_axes["slnt"] if both are setThis allows flexible input while ensuring consistent, predictable behavior across different font types.
FaceTypeSpecifies the type of font faces to retrieve using the unified get_faces() interface.
pub enum FaceType {
Italic, // Retrieve italic (slanted) font variants
Roman, // Retrieve roman (upright) font variants
}
ItalicMatchRepresents a matching italic variant with distance and axis differences.
pub struct ItalicMatch {
pub recipe: UIFontItalicRecipe, // The matched italic recipe
pub distance: f32, // Distance score (lower is closer match)
pub axis_diffs: Option<Vec<AxisDiff>>, // Axis differences for variable fonts
}
AxisDiffRepresents a difference in a variable font axis.
pub struct AxisDiff {
pub tag: String, // Axis tag (e.g., "wght", "wdth", "slnt")
pub spec: f32, // Target/specified value
pub diff: f32, // Difference (spec - current)
}
UIFontFamilyResultComplete family-level analysis result.
pub struct UIFontFamilyResult {
pub family_name: String, // Family name
pub italic_capability: UIFontItalicCapability, // Italic capabilities
pub variable_font_info: Option<UIFontVariableInfo>, // Variable font data
pub face_info: Vec<UIFontFaceInfo>, // Face-level information
}
UIFontItalicCapabilityItalic capability analysis for UI consumption.
pub struct UIFontItalicCapability {
pub has_italic: bool, // Whether family has italic variants
pub has_upright: bool, // Whether family has upright variants
pub strategy: UIFontItalicStrategy, // Primary italic strategy
pub recipes: Vec<UIFontItalicRecipe>, // Available italic recipes
pub scenario: FamilyScenario, // Family scenario type
}
UIFontItalicRecipeItalic recipe for UI consumption.
pub struct UIFontItalicRecipe {
pub name: String, // User-friendly name (e.g., "Bold Italic")
pub description: String, // User-friendly description
pub is_italic: bool, // Whether this recipe produces italic text
pub face_id: String, // Face ID to use for this recipe
pub vf_recipe: Option<VfRecipe>, // Variable font recipe (if applicable)
pub weight_range: (u16, u16), // Weight range (min, max)
pub stretch_range: (u16, u16), // Stretch range (min, max)
}
ParserLow-level font parser backed by ttf-parser.
pub struct Parser<'a> {
// Font parsing methods
pub fn new(data: &'a [u8]) -> Result<Self, ttf_parser::FaceParsingError>
pub fn extract_face_record(&self, face_id: String, user_font_style_italic: Option<bool>) -> Result<FaceRecord, String>
pub fn fvar(&self) -> FvarData
pub fn stat(&self) -> StatData
pub fn ffeatures(&self) -> Vec<FontFeature>
}
FontSelectionParserCore font selection pipeline.
pub struct FontSelectionParser {
pub config: ParserConfig,
}
impl FontSelectionParser {
pub fn new() -> Self
pub fn classify_face(&self, face_record: FaceRecord) -> ClassifiedFace
pub fn build_capability_map(&self, faces: Vec<FaceRecord>) -> FontSelectionCapabilityMap
pub fn select_face(&self, capability_map: &FontSelectionCapabilityMap, weight: u16, stretch: u16, style: FontStyle) -> FontSelection
}
FaceClassificationResult of font classification for a single face.
pub struct FaceClassification {
pub font_style: FontStyle, // Normal or Italic
pub vf_recipe: Option<VfRecipe>, // Variable font recipe
pub weight_key: u16, // Weight for family aggregation
pub stretch_key: u16, // Stretch for family aggregation
pub is_variable: bool, // Whether this is a variable font
pub instance_info: Option<InstanceInfo>, // Instance information (Scenario 3-1)
// Legacy compatibility
pub fn italic_kind(&self) -> FontStyle // Backward compatibility getter
}
FontSelectionCapabilityMapFamily-level font selection capability mapping.
pub struct FontSelectionCapabilityMap {
pub upright_slots: HashMap<(u16, u16), FaceOrVfWithRecipe>, // (weight, stretch) -> face
pub italic_slots: HashMap<(u16, u16), FaceOrVfWithRecipe>, // (weight, stretch) -> face
pub scenario: FamilyScenario, // Family scenario type
}
serde feature)FontAnalysisResponseComplete font analysis results for WASM consumption.
pub struct FontAnalysisResponse {
pub classifications: Vec<FaceClassificationJson>,
pub capability_map: ItalicCapabilityMapJson,
pub fvar_data: Option<FvarDataJson>,
pub stat_data: Option<StatDataJson>,
pub metadata: AnalysisMetadata,
}
pub mod utils {
pub fn success_response<T: serde::Serialize>(data: T) -> SuccessResponse<T>
pub fn error_response(code: &str, message: &str) -> ErrorResponseWrapper
pub fn error_response_with_details(code: &str, message: &str, details: &str) -> ErrorResponseWrapper
}
The library handles various font family configurations:
Allerta-Regular.ttf, Molle-Italic.ttfPTSerif-Regular.ttf, PTSerif-Italic.ttf, etc.)ital Axisital axis (0 = normal, 1 = italic)slnt Axis & Italic Instancesslnt axis and explicit italic instancesital Axis - Support for variable fonts with italic axisslnt Axis - Support for slant-based italic in variable fontsslnt axis and italic instancesfvar.instances Analysis - Complex edge cases and malformed fontsuse fonts::{UIFontParser, UIFontFace};
let parser = UIFontParser::new();
let font_faces = vec![UIFontFace {
face_id: "Inter-VariableFont.ttf".to_string(),
data: std::fs::read("Inter-VariableFont.ttf")?,
user_font_style_italic: None, // Let the parser analyze the font metadata
}];
let result = parser.analyze_family(None, font_faces)?;
println!("Family: {}", result.family_name);
println!("Italic available: {}", result.italic_capability.has_italic);
println!("Strategy: {:?}", result.italic_capability.strategy);
let font_faces = vec![
UIFontFace {
face_id: "Inter-Regular.ttf".to_string(),
data: std::fs::read("Inter-Regular.ttf")?,
user_font_style_italic: Some(false), // User declares this is not italic
},
UIFontFace {
face_id: "Inter-Italic.ttf".to_string(),
data: std::fs::read("Inter-Italic.ttf")?,
user_font_style_italic: Some(true), // User declares this is italic
},
];
let result = parser.analyze_family(Some("Inter".to_string()), font_faces)?;
println!("Family scenario: {:?}", result.italic_capability.scenario);
println!("Upright available: {}", result.italic_capability.has_upright);
println!("Italic available: {}", result.italic_capability.has_italic);
Find the closest italic or roman variants to your current text style:
use fonts::{UIFontParser, UIFontFace, CurrentTextStyle};
use std::collections::HashMap;
let parser = UIFontParser::new();
let font_faces = vec![
UIFontFace {
face_id: "Inter-Regular.ttf".to_string(),
data: std::fs::read("Inter-Regular.ttf")?,
user_font_style_italic: Some(false),
},
UIFontFace {
face_id: "Inter-Italic.ttf".to_string(),
data: std::fs::read("Inter-Italic.ttf")?,
user_font_style_italic: Some(true),
},
];
// Define current text style with smart auto-resolution
let mut custom_axes = HashMap::new();
custom_axes.insert("wght".to_string(), 500.0); // Will be overridden by weight: 400
custom_axes.insert("wdth".to_string(), 120.0); // Will be overridden by width: 100
custom_axes.insert("slnt".to_string(), 5.0); // Will be used since slant is None
let current_style = CurrentTextStyle {
weight: Some(400), // Takes priority over custom_axes["wght"]
width: Some(100), // Takes priority over custom_axes["wdth"]
slant: None, // Falls back to custom_axes["slnt"] if font has slnt axis
custom_axes,
};
// Find closest italic variants
let italic_matches = parser.get_italics(
Some("Inter".to_string()),
font_faces.clone(),
Some(current_style.clone()),
Some(3), // Max 3 results
)?;
// Find closest roman (non-italic) variants
let roman_matches = parser.get_romans(
Some("Inter".to_string()),
font_faces,
Some(current_style),
Some(3), // Max 3 results
)?;
if let Some(closest_italic) = italic_matches.first() {
println!("Closest italic: {}", closest_italic.recipe.name);
println!("Distance: {}", closest_italic.distance);
// Check axis differences for variable fonts
if let Some(axis_diffs) = &closest_italic.axis_diffs {
for diff in axis_diffs {
println!("Axis {}: spec={} (diff: {})",
diff.tag, diff.spec, diff.diff);
}
}
}
if let Some(closest_roman) = roman_matches.first() {
println!("Closest roman: {}", closest_roman.recipe.name);
println!("Distance: {}", closest_roman.distance);
}
For easier toggling between italic and roman variants, use the unified get_faces() interface:
use fonts::{UIFontParser, UIFontFace, CurrentTextStyle, FaceType};
use std::collections::HashMap;
let parser = UIFontParser::new();
let font_faces = vec![
UIFontFace {
face_id: "Inter-Regular.ttf".to_string(),
data: std::fs::read("Inter-Regular.ttf")?,
user_font_style_italic: Some(false),
},
UIFontFace {
face_id: "Inter-Italic.ttf".to_string(),
data: std::fs::read("Inter-Italic.ttf")?,
user_font_style_italic: Some(true),
},
];
let current_style = CurrentTextStyle {
weight: Some(400),
width: Some(100),
slant: None,
custom_axes: HashMap::new(),
};
// Toggle to italic
let italic_matches = parser.get_faces(
FaceType::Italic,
Some("Inter".to_string()),
font_faces.clone(),
Some(current_style.clone()),
Some(1),
)?;
// Toggle to roman
let roman_matches = parser.get_faces(
FaceType::Roman,
Some("Inter".to_string()),
font_faces,
Some(current_style),
Some(1),
)?;
// Easy toggling in UI code
let toggle_to_italic = parser.get_faces(
FaceType::Italic,
Some("Inter".to_string()),
font_faces,
Some(current_style),
Some(1),
)?;
// Enable serde feature
use fonts::serde::*;
// Create analysis response
let response = FontAnalysisResponse {
classifications: classifications.into_iter().map(|c| c.into()).collect(),
capability_map: capability_map.into(),
fvar_data: fvar_data.map(|f| f.into()),
stat_data: stat_data.map(|s| s.into()),
metadata: AnalysisMetadata {
face_count: faces.len(),
has_variable_fonts: faces.iter().any(|f| f.is_variable),
timestamp: chrono::Utc::now().to_rfc3339(),
engine_version: env!("CARGO_PKG_VERSION").to_string(),
},
};
// Serialize for WASM
let json = serde_json::to_string(&response)?;
ttf-parser for efficient font data accesscargo test --all-targets --all-featuresThis project is part of the Grida design tool ecosystem. See the main project license for details.
For technical details, testing information, dependencies, and changelog, see AGENTS.md.