Skip to main content

debug_azul_fonts/
debug_azul_fonts.rs

1//! Debug example for testing System Font resolution.
2//!
3//! This example demonstrates the italic race condition fix:
4//! When querying "System Font" with italic=False, the result should
5//! ALWAYS be a non-italic font, regardless of font discovery order.
6//!
7//! Run with: cargo run --example debug_azul_fonts --features "std,parsing,cache"
8
9use rust_fontconfig::{FcFontCache, FcPattern, FcWeight, PatternMatch};
10
11fn main() {
12    println!("=== System Font Italic Race Condition Test ===\n");
13    
14    // Build the cache (scans all system fonts)
15    println!("Building font cache (scanning system fonts)...");
16    let cache = FcFontCache::build();
17    let all_fonts = cache.list();
18    println!("Cache built: {} patterns\n", all_fonts.len());
19    
20    // FIRST: dump ALL fonts that contain "system" or "sfns" in their name
21    println!("--- ALL fonts containing 'system' or 'sfns' in name/family ---");
22    for (pattern, id) in all_fonts {
23        let name_lower = pattern.name.as_deref().unwrap_or("").to_lowercase();
24        let family_lower = pattern.family.as_deref().unwrap_or("").to_lowercase();
25        if name_lower.contains("system") || name_lower.contains("sfns") 
26           || family_lower.contains("system") || family_lower.contains("sfns") {
27            let style_score = FcFontCache::calculate_style_score(
28                &FcPattern {
29                    weight: FcWeight::Normal,
30                    italic: PatternMatch::False,
31                    oblique: PatternMatch::False,
32                    ..Default::default()
33                },
34                pattern,
35            );
36            println!(
37                "  id={} name={:?} family={:?} italic={:?} bold={:?} weight={:?} stretch={:?} style_score={} subfamily={:?}",
38                id,
39                pattern.name.as_deref().unwrap_or("?"),
40                pattern.family.as_deref().unwrap_or("?"),
41                pattern.italic,
42                pattern.bold,
43                pattern.weight,
44                pattern.stretch,
45                style_score,
46                pattern.metadata.font_subfamily.as_deref().unwrap_or("?"),
47            );
48        }
49    }
50    println!();
51    
52    // Query "System Font" with italic=False (what azul does for button text)
53    let families = vec!["System Font".to_string()];
54    let mut trace = Vec::new();
55    
56    let chain = cache.resolve_font_chain(
57        &families,
58        FcWeight::Normal,
59        PatternMatch::False,  // italic=False
60        PatternMatch::False,  // oblique=False
61        &mut trace,
62    );
63    
64    println!("Font chain for 'System Font' (italic=False, weight=Normal):");
65    for group in &chain.css_fallbacks {
66        println!("  CSS name: '{}'", group.css_name);
67        for (i, font_match) in group.fonts.iter().enumerate() {
68            let meta = cache.get_metadata_by_id(&font_match.id);
69            if let Some(pattern) = meta {
70                let style_score = FcFontCache::calculate_style_score(
71                    &FcPattern {
72                        weight: FcWeight::Normal,
73                        italic: PatternMatch::False,
74                        oblique: PatternMatch::False,
75                        ..Default::default()
76                    },
77                    pattern,
78                );
79                println!(
80                    "    [{}] id={} name={:?} family={:?} italic={:?} bold={:?} weight={:?} style_score={} subfamily={:?}",
81                    i,
82                    font_match.id,
83                    pattern.name.as_deref().unwrap_or("?"),
84                    pattern.family.as_deref().unwrap_or("?"),
85                    pattern.italic,
86                    pattern.bold,
87                    pattern.weight,
88                    style_score,
89                    pattern.metadata.font_subfamily.as_deref().unwrap_or("?"),
90                );
91            } else {
92                println!("    [{}] id={} (no metadata)", i, font_match.id);
93            }
94        }
95    }
96    
97    // Verify: the first matched font should NOT be italic
98    let first_font = chain.css_fallbacks
99        .iter()
100        .flat_map(|g| g.fonts.iter())
101        .next();
102    
103    if let Some(font) = first_font {
104        if let Some(meta) = cache.get_metadata_by_id(&font.id) {
105            if meta.italic == PatternMatch::True {
106                eprintln!("\n❌ FAIL: First matched font is ITALIC!");
107                eprintln!("   Font: {:?} (id={})", meta.name, font.id);
108                eprintln!("   This is the italic race condition bug.");
109                std::process::exit(1);
110            } else {
111                println!("\n✅ PASS: First matched font is NOT italic.");
112                println!("   Font: {:?} (id={})", meta.name, font.id);
113            }
114        }
115    } else {
116        eprintln!("\n⚠ WARNING: No fonts matched 'System Font'");
117    }
118    
119    // Also test with DontCare (original default before the fix)
120    println!("\n--- Comparison: italic=DontCare ---");
121    let mut trace2 = Vec::new();
122    let chain2 = cache.resolve_font_chain(
123        &families,
124        FcWeight::Normal,
125        PatternMatch::DontCare,
126        PatternMatch::DontCare,
127        &mut trace2,
128    );
129    
130    for group in &chain2.css_fallbacks {
131        for (i, font_match) in group.fonts.iter().enumerate() {
132            if let Some(pattern) = cache.get_metadata_by_id(&font_match.id) {
133                println!(
134                    "    [{}] {:?} italic={:?} weight={:?}",
135                    i,
136                    pattern.name.as_deref().unwrap_or("?"),
137                    pattern.italic,
138                    pattern.weight,
139                );
140            }
141        }
142    }
143    
144    // Run the test 10 times to verify determinism
145    println!("\n--- Determinism check (10 runs) ---");
146    let mut all_same = true;
147    let mut first_result = None;
148    
149    for run in 0..10 {
150        // Clear chain cache to force re-resolution
151        let mut trace_n = Vec::new();
152        let chain_n = cache.resolve_font_chain(
153            &families,
154            FcWeight::Normal,
155            PatternMatch::False,
156            PatternMatch::False,
157            &mut trace_n,
158        );
159        
160        let first_id = chain_n.css_fallbacks
161            .iter()
162            .flat_map(|g| g.fonts.iter())
163            .next()
164            .map(|f| f.id);
165        
166        if let Some(id) = first_id {
167            if first_result.is_none() {
168                first_result = Some(id);
169            } else if first_result != Some(id) {
170                println!("    Run {}: id={} — DIFFERENT from first run!", run, id);
171                all_same = false;
172            }
173        }
174    }
175    
176    if all_same {
177        println!("    All 10 runs returned the same first font. ✅");
178    } else {
179        println!("    Results varied across runs! ❌");
180    }
181    
182    println!("\n=== Test complete ===");
183}