character_resolution/
character_resolution.rs

1//! Character resolution example
2//! 
3//! Demonstrates how to resolve individual characters to fonts,
4//! useful for debugging font coverage issues.
5
6use rust_fontconfig::{FcFontCache, FcWeight, PatternMatch};
7
8fn main() {
9    let cache = FcFontCache::build();
10        
11    // Create a font chain with typical web defaults
12    let families = vec![
13        "system-ui".to_string(),
14        "sans-serif".to_string(),
15    ];
16    
17    let mut trace = Vec::new();
18    let chain = cache.resolve_font_chain(
19        &families,
20        FcWeight::Normal,
21        PatternMatch::False,
22        PatternMatch::False,
23        &mut trace,
24    );
25    
26    // Test characters from different Unicode blocks
27    let test_chars = vec![
28        ('A', "Latin Capital Letter A"),
29        ('a', "Latin Small Letter A"),
30        ('0', "Digit Zero"),
31        ('€', "Euro Sign"),
32        ('→', "Rightwards Arrow"),
33        ('中', "CJK Ideograph - China"),
34        ('日', "CJK Ideograph - Sun/Day"),
35        ('あ', "Hiragana Letter A"),
36        ('ア', "Katakana Letter A"),
37        ('한', "Hangul Syllable Han"),
38        ('א', "Hebrew Letter Alef"),
39        ('ا', "Arabic Letter Alef"),
40        ('α', "Greek Small Letter Alpha"),
41        ('Σ', "Greek Capital Letter Sigma"),
42        ('я', "Cyrillic Small Letter Ya"),
43        ('🙂', "Slightly Smiling Face"),
44        ('♠', "Black Spade Suit"),
45        ('∑', "N-ary Summation"),
46        ('∞', "Infinity"),
47        ('℃', "Degree Celsius"),
48    ];
49    
50    println!("Character resolution results:\n");
51    println!("{:<6} {:<30} {:<40}", "Char", "Description", "Font");
52    println!("{}", "-".repeat(80));
53    
54    for (ch, description) in test_chars {
55        let text = ch.to_string();
56        let resolved = chain.resolve_text(&cache, &text);
57        
58        let font_name = resolved.first()
59            .and_then(|(_, info)| info.as_ref())
60            .and_then(|(id, _)| cache.get_metadata_by_id(id))
61            .and_then(|m| m.name.clone().or(m.family.clone()))
62            .unwrap_or_else(|| "⚠ NOT FOUND".to_string());
63        
64        println!("{:<6} {:<30} {}", ch, description, font_name);
65    }
66    
67    // Show how to check if a specific font covers a character
68    println!("\n\nFont Coverage Check\n");
69    
70    let pattern = rust_fontconfig::FcPattern {
71        family: Some("Arial".to_string()),
72        ..Default::default()
73    };
74    
75    if let Some(match_result) = cache.query(&pattern, &mut Vec::new()) {
76        println!("Checking Arial coverage:");
77        
78        // Create a chain just for Arial
79        let arial_chain = cache.resolve_font_chain(
80            &vec!["Arial".to_string()],
81            FcWeight::Normal,
82            PatternMatch::False,
83            PatternMatch::False,
84            &mut Vec::new(),
85        );
86        
87        let check_chars = ['A', '中', '🙂', '→'];
88        for ch in check_chars {
89            let resolved = arial_chain.resolve_text(&cache, &ch.to_string());
90            let found_in_arial = resolved.first()
91                .and_then(|(_, info)| info.as_ref())
92                .map(|(id, _)| id == &match_result.id)
93                .unwrap_or(false);
94            
95            let status = if found_in_arial { "✓" } else { "✗" };
96            println!("  {} '{}' (U+{:04X})", status, ch, ch as u32);
97        }
98    }
99    
100    // Show codepoint ranges supported
101    println!("\n\nUnicode Block Coverage Summary\n");
102    
103    let blocks = [
104        ("Basic Latin", 0x0020..0x007F),
105        ("Latin Extended-A", 0x0100..0x017F),
106        ("Greek", 0x0370..0x03FF),
107        ("Cyrillic", 0x0400..0x04FF),
108        ("Arabic", 0x0600..0x06FF),
109        ("CJK Unified Ideographs", 0x4E00..0x9FFF),
110        ("Hiragana", 0x3040..0x309F),
111        ("Katakana", 0x30A0..0x30FF),
112    ];
113    
114    for (name, range) in blocks {
115        // Sample a few codepoints from each block
116        let sample_points: Vec<char> = range.clone()
117            .step_by(range.len() / 5)
118            .take(5)
119            .filter_map(|cp| char::from_u32(cp))
120            .collect();
121        
122        let sample_text: String = sample_points.iter().collect();
123        let resolved = chain.resolve_text(&cache, &sample_text);
124        
125        let fonts_used: std::collections::HashSet<_> = resolved.iter()
126            .filter_map(|(_, info)| info.as_ref())
127            .map(|(id, _)| id.clone())
128            .collect();
129        
130        let coverage = resolved.iter()
131            .filter(|(_, info)| info.is_some())
132            .count() as f32 / resolved.len() as f32 * 100.0;
133        
134        println!("{:<30} {:>6.1}% coverage ({} fonts)", name, coverage, fonts_used.len());
135    }
136}