Skip to main content

FcFontCache

Struct FcFontCache 

Source
pub struct FcFontCache { /* private fields */ }
Expand description

Font cache, initialized at startup

Implementations§

Source§

impl FcFontCache

Source

pub fn with_memory_fonts( &mut self, fonts: Vec<(FcPattern, FcFont)>, ) -> &mut Self

Adds in-memory font files

Source

pub fn with_memory_font_with_id( &mut self, id: FontId, pattern: FcPattern, font: FcFont, ) -> &mut Self

Adds a memory font with a specific ID (for testing)

Source

pub fn get_font_by_id<'a>(&'a self, id: &FontId) -> Option<FontSource<'a>>

Get font data for a given font ID

Examples found in repository?
examples/query.rs (line 32)
11fn main() {
12    println!("Building font cache...");
13    let cache = FcFontCache::build();
14    println!("Font cache built with {} fonts\n", cache.list().len());
15
16    // ── Query by family name ──
17    println!("=== Query by Family Name ===");
18    let mut trace = Vec::new();
19    let pattern = FcPattern {
20        family: Some("Arial".to_string()),
21        ..Default::default()
22    };
23
24    if let Some(result) = cache.query(&pattern, &mut trace) {
25        println!("Found Arial:");
26        println!("  Font ID: {:?}", result.id);
27        if let Some(meta) = cache.get_metadata_by_id(&result.id) {
28            println!("  Family: {:?}", meta.family);
29            println!("  Weight: {:?}", meta.weight);
30            println!("  Italic: {:?}", meta.italic);
31        }
32        if let Some(source) = cache.get_font_by_id(&result.id) {
33            match source {
34                rust_fontconfig::FontSource::Disk(path) => println!("  Path: {}", path.path),
35                rust_fontconfig::FontSource::Memory(font) => {
36                    println!("  Memory font: {}", font.id)
37                }
38            }
39        }
40    } else {
41        println!("Arial not found");
42    }
43
44    // ── Query generic family ──
45    println!("\n=== Query Generic 'serif' ===");
46    trace.clear();
47    if let Some(result) = cache.query(
48        &FcPattern {
49            family: Some("serif".to_string()),
50            ..Default::default()
51        },
52        &mut trace,
53    ) {
54        if let Some(meta) = cache.get_metadata_by_id(&result.id) {
55            println!(
56                "Found: {:?}",
57                meta.name.as_ref().or(meta.family.as_ref())
58            );
59        }
60    }
61
62    // ── Query by style (bold + italic) ──
63    println!("\n=== Query Bold Italic ===");
64    trace.clear();
65    if let Some(result) = cache.query(
66        &FcPattern {
67            family: Some("sans-serif".to_string()),
68            weight: FcWeight::Bold,
69            italic: PatternMatch::True,
70            ..Default::default()
71        },
72        &mut trace,
73    ) {
74        if let Some(meta) = cache.get_metadata_by_id(&result.id) {
75            println!(
76                "Found: {:?} weight={:?} italic={:?}",
77                meta.name.as_ref().or(meta.family.as_ref()),
78                meta.weight,
79                meta.italic
80            );
81        }
82    }
83
84    // ── List bold fonts ──
85    println!("\n=== First 5 Bold Fonts ===");
86    for (meta, id) in cache
87        .list()
88        .into_iter()
89        .filter(|(m, _)| matches!(m.weight, FcWeight::Bold | FcWeight::ExtraBold | FcWeight::Black))
90        .take(5)
91    {
92        println!(
93            "  {:?}: {:?}",
94            id,
95            meta.name.as_ref().or(meta.family.as_ref())
96        );
97    }
98
99    // ── Search by name substring ──
100    println!("\n=== Fonts with 'Mono' in name ===");
101    for (meta, _id) in cache.list().into_iter().filter(|(m, _)| {
102        m.name
103            .as_ref()
104            .map(|n| n.contains("Mono"))
105            .unwrap_or(false)
106            || m.family
107                .as_ref()
108                .map(|f| f.contains("Mono"))
109                .unwrap_or(false)
110    }).take(10) {
111        println!(
112            "  {:?}",
113            meta.name.as_ref().or(meta.family.as_ref())
114        );
115    }
116}
More examples
Hide additional examples
examples/integration_api.rs (line 104)
11fn main() {
12    println!("=== Font Integration Pipeline ===\n");
13
14    // ── Step 1: Build font cache ──
15    println!("Step 1: Build font cache");
16    let cache = FcFontCache::build();
17    println!("  {} fonts loaded\n", cache.list().len());
18
19    // ── Step 2: Resolve CSS font-family ──
20    let css_families = vec![
21        "Helvetica".to_string(),
22        "Arial".to_string(),
23        "sans-serif".to_string(),
24    ];
25    println!("Step 2: Resolve font-family: {:?}\n", css_families);
26
27    let chain = cache.resolve_font_chain(
28        &css_families,
29        FcWeight::Normal,
30        PatternMatch::False,
31        PatternMatch::False,
32        &mut Vec::new(),
33    );
34
35    for (i, group) in chain.css_fallbacks.iter().enumerate() {
36        print!("  [{}] '{}': {} fonts", i + 1, group.css_name, group.fonts.len());
37        if let Some(first) = group.fonts.first() {
38            if let Some(meta) = cache.get_metadata_by_id(&first.id) {
39                print!(
40                    " (first: {:?})",
41                    meta.name.as_ref().or(meta.family.as_ref())
42                );
43            }
44        }
45        println!();
46    }
47    println!(
48        "  + {} unicode fallback fonts\n",
49        chain.unicode_fallbacks.len()
50    );
51
52    // ── Step 3: Resolve text to font runs ──
53    let text = "Hello 世界! Привет мир";
54    println!("Step 3: Resolve text: '{}'\n", text);
55
56    let resolved = chain.resolve_text(&cache, text);
57
58    // Group by runs of same font
59    let mut runs: Vec<(String, Option<FontId>)> = Vec::new();
60    let mut current_text = String::new();
61    let mut current_id: Option<FontId> = None;
62
63    for (ch, info) in &resolved {
64        let this_id = info.as_ref().map(|(id, _)| *id);
65        if this_id != current_id {
66            if !current_text.is_empty() {
67                runs.push((current_text.clone(), current_id));
68                current_text.clear();
69            }
70            current_id = this_id;
71        }
72        current_text.push(*ch);
73    }
74    if !current_text.is_empty() {
75        runs.push((current_text, current_id));
76    }
77
78    println!("  Font runs:");
79    for (run_text, font_id) in &runs {
80        let name = font_id
81            .as_ref()
82            .and_then(|id| cache.get_metadata_by_id(id))
83            .and_then(|m| m.name.clone().or(m.family.clone()))
84            .unwrap_or_else(|| "[NO FONT]".into());
85        println!("    '{}' -> {}", run_text, name);
86    }
87
88    // ── Step 4: Load font bytes ──
89    let unique_fonts: std::collections::HashSet<_> =
90        runs.iter().filter_map(|(_, id)| *id).collect();
91
92    println!(
93        "\nStep 4: Load fonts ({} unique needed)\n",
94        unique_fonts.len()
95    );
96    for font_id in &unique_fonts {
97        if let Some(meta) = cache.get_metadata_by_id(font_id) {
98            let name = meta
99                .name
100                .as_ref()
101                .or(meta.family.as_ref())
102                .map(|s| s.as_str())
103                .unwrap_or("?");
104            if let Some(source) = cache.get_font_by_id(font_id) {
105                match source {
106                    rust_fontconfig::FontSource::Disk(path) => {
107                        println!("  {} -> {}", name, path.path);
108                    }
109                    rust_fontconfig::FontSource::Memory(font) => {
110                        println!("  {} -> memory (id: {})", name, font.id);
111                    }
112                }
113            }
114        }
115    }
116
117    println!("\nPipeline summary:");
118    println!("  1. FcFontCache::build()       — once at startup");
119    println!("  2. cache.resolve_font_chain() — per CSS font-family");
120    println!("  3. chain.resolve_text()       — per text run");
121    println!("  4. cache.get_font_by_id()     — load bytes for shaping");
122}
Source

pub fn get_metadata_by_id(&self, id: &FontId) -> Option<&FcPattern>

Get metadata directly from an ID

Examples found in repository?
examples/unicode_aware_fonts.rs (line 62)
53fn print_resolution(cache: &FcFontCache, chain: &FontFallbackChain, text: &str) {
54    let resolved = chain.resolve_text(cache, text);
55
56    let mut current_font: Option<String> = None;
57    let mut segment = String::new();
58
59    for (ch, info) in &resolved {
60        let font_name = info.as_ref().and_then(|(id, _)| {
61            cache
62                .get_metadata_by_id(id)
63                .and_then(|m| m.name.clone().or(m.family.clone()))
64        });
65        if font_name != current_font {
66            if !segment.is_empty() {
67                println!(
68                    "  '{}' -> {}",
69                    segment,
70                    current_font.as_deref().unwrap_or("[NO FONT]")
71                );
72                segment.clear();
73            }
74            current_font = font_name;
75        }
76        segment.push(*ch);
77    }
78    if !segment.is_empty() {
79        println!(
80            "  '{}' -> {}",
81            segment,
82            current_font.as_deref().unwrap_or("[NO FONT]")
83        );
84    }
85}
More examples
Hide additional examples
examples/getfont.rs (line 44)
13fn main() {
14    // Build the cache — scans and parses ALL system fonts
15    let start = Instant::now();
16    let cache = FcFontCache::build();
17    let build_time = start.elapsed();
18
19    println!("Cache built: {} fonts in {:?}\n", cache.list().len(), build_time);
20
21    // Query various fonts to showcase fuzzy matching
22    let queries = [
23        ("Arial", FcWeight::Normal, "Common sans-serif"),
24        ("Helvetica", FcWeight::Bold, "Bold variant"),
25        ("Courier", FcWeight::Normal, "Monospace"),
26        ("Georgia", FcWeight::Normal, "Serif"),
27        ("sans-serif", FcWeight::Normal, "Generic family"),
28    ];
29
30    for (name, weight, desc) in &queries {
31        let t = Instant::now();
32        let result = cache.query(
33            &FcPattern {
34                name: Some(name.to_string()),
35                weight: *weight,
36                ..Default::default()
37            },
38            &mut Vec::new(),
39        );
40
41        match result {
42            Some(fm) => {
43                let found = cache
44                    .get_metadata_by_id(&fm.id)
45                    .and_then(|m| m.name.clone().or(m.family.clone()))
46                    .unwrap_or_else(|| format!("{:?}", fm.id));
47                println!(
48                    "  ✓ '{}' ({}) -> {} [{:?}]",
49                    name, desc, found, t.elapsed()
50                );
51            }
52            None => println!("  ✗ '{}' ({}) -> NOT FOUND [{:?}]", name, desc, t.elapsed()),
53        }
54    }
55}
examples/character_resolution.rs (line 58)
11fn main() {
12    let cache = FcFontCache::build();
13
14    // Create a font chain with typical web defaults
15    let families = vec!["system-ui".to_string(), "sans-serif".to_string()];
16
17    let chain = cache.resolve_font_chain(
18        &families,
19        FcWeight::Normal,
20        PatternMatch::False,
21        PatternMatch::False,
22        &mut Vec::new(),
23    );
24
25    // Test characters from different Unicode blocks
26    let test_chars = [
27        ('A', "Latin Capital Letter A"),
28        ('a', "Latin Small Letter A"),
29        ('0', "Digit Zero"),
30        ('€', "Euro Sign"),
31        ('→', "Rightwards Arrow"),
32        ('中', "CJK Ideograph - China"),
33        ('日', "CJK Ideograph - Sun/Day"),
34        ('あ', "Hiragana Letter A"),
35        ('ア', "Katakana Letter A"),
36        ('한', "Hangul Syllable Han"),
37        ('א', "Hebrew Letter Alef"),
38        ('ا', "Arabic Letter Alef"),
39        ('α', "Greek Small Letter Alpha"),
40        ('я', "Cyrillic Small Letter Ya"),
41        ('🙂', "Slightly Smiling Face"),
42        ('♠', "Black Spade Suit"),
43        ('∑', "N-ary Summation"),
44        ('∞', "Infinity"),
45    ];
46
47    println!("Character resolution results:\n");
48    println!("{:<6} {:<30} {:<40}", "Char", "Description", "Font");
49    println!("{}", "-".repeat(76));
50
51    for (ch, description) in &test_chars {
52        let text = ch.to_string();
53        let resolved = chain.resolve_text(&cache, &text);
54
55        let font_name = resolved
56            .first()
57            .and_then(|(_, info)| info.as_ref())
58            .and_then(|(id, _)| cache.get_metadata_by_id(id))
59            .and_then(|m| m.name.clone().or(m.family.clone()))
60            .unwrap_or_else(|| "⚠ NOT FOUND".to_string());
61
62        println!("{:<6} {:<30} {}", ch, description, font_name);
63    }
64
65    // Check specific font coverage
66    println!("\n\nArial coverage check:");
67    let arial_chain = cache.resolve_font_chain(
68        &["Arial".to_string()],
69        FcWeight::Normal,
70        PatternMatch::False,
71        PatternMatch::False,
72        &mut Vec::new(),
73    );
74
75    let arial_result = cache.query(
76        &rust_fontconfig::FcPattern {
77            family: Some("Arial".to_string()),
78            ..Default::default()
79        },
80        &mut Vec::new(),
81    );
82
83    if let Some(arial_match) = arial_result {
84        for ch in ['A', '中', '🙂', '→'] {
85            let resolved = arial_chain.resolve_text(&cache, &ch.to_string());
86            let in_arial = resolved
87                .first()
88                .and_then(|(_, info)| info.as_ref())
89                .map(|(id, _)| id == &arial_match.id)
90                .unwrap_or(false);
91
92            println!(
93                "  {} '{}' (U+{:04X})",
94                if in_arial { "✓" } else { "✗" },
95                ch,
96                ch as u32
97            );
98        }
99    } else {
100        println!("  Arial not found on this system");
101    }
102}
examples/query.rs (line 27)
11fn main() {
12    println!("Building font cache...");
13    let cache = FcFontCache::build();
14    println!("Font cache built with {} fonts\n", cache.list().len());
15
16    // ── Query by family name ──
17    println!("=== Query by Family Name ===");
18    let mut trace = Vec::new();
19    let pattern = FcPattern {
20        family: Some("Arial".to_string()),
21        ..Default::default()
22    };
23
24    if let Some(result) = cache.query(&pattern, &mut trace) {
25        println!("Found Arial:");
26        println!("  Font ID: {:?}", result.id);
27        if let Some(meta) = cache.get_metadata_by_id(&result.id) {
28            println!("  Family: {:?}", meta.family);
29            println!("  Weight: {:?}", meta.weight);
30            println!("  Italic: {:?}", meta.italic);
31        }
32        if let Some(source) = cache.get_font_by_id(&result.id) {
33            match source {
34                rust_fontconfig::FontSource::Disk(path) => println!("  Path: {}", path.path),
35                rust_fontconfig::FontSource::Memory(font) => {
36                    println!("  Memory font: {}", font.id)
37                }
38            }
39        }
40    } else {
41        println!("Arial not found");
42    }
43
44    // ── Query generic family ──
45    println!("\n=== Query Generic 'serif' ===");
46    trace.clear();
47    if let Some(result) = cache.query(
48        &FcPattern {
49            family: Some("serif".to_string()),
50            ..Default::default()
51        },
52        &mut trace,
53    ) {
54        if let Some(meta) = cache.get_metadata_by_id(&result.id) {
55            println!(
56                "Found: {:?}",
57                meta.name.as_ref().or(meta.family.as_ref())
58            );
59        }
60    }
61
62    // ── Query by style (bold + italic) ──
63    println!("\n=== Query Bold Italic ===");
64    trace.clear();
65    if let Some(result) = cache.query(
66        &FcPattern {
67            family: Some("sans-serif".to_string()),
68            weight: FcWeight::Bold,
69            italic: PatternMatch::True,
70            ..Default::default()
71        },
72        &mut trace,
73    ) {
74        if let Some(meta) = cache.get_metadata_by_id(&result.id) {
75            println!(
76                "Found: {:?} weight={:?} italic={:?}",
77                meta.name.as_ref().or(meta.family.as_ref()),
78                meta.weight,
79                meta.italic
80            );
81        }
82    }
83
84    // ── List bold fonts ──
85    println!("\n=== First 5 Bold Fonts ===");
86    for (meta, id) in cache
87        .list()
88        .into_iter()
89        .filter(|(m, _)| matches!(m.weight, FcWeight::Bold | FcWeight::ExtraBold | FcWeight::Black))
90        .take(5)
91    {
92        println!(
93            "  {:?}: {:?}",
94            id,
95            meta.name.as_ref().or(meta.family.as_ref())
96        );
97    }
98
99    // ── Search by name substring ──
100    println!("\n=== Fonts with 'Mono' in name ===");
101    for (meta, _id) in cache.list().into_iter().filter(|(m, _)| {
102        m.name
103            .as_ref()
104            .map(|n| n.contains("Mono"))
105            .unwrap_or(false)
106            || m.family
107                .as_ref()
108                .map(|f| f.contains("Mono"))
109                .unwrap_or(false)
110    }).take(10) {
111        println!(
112            "  {:?}",
113            meta.name.as_ref().or(meta.family.as_ref())
114        );
115    }
116}
examples/integration_api.rs (line 38)
11fn main() {
12    println!("=== Font Integration Pipeline ===\n");
13
14    // ── Step 1: Build font cache ──
15    println!("Step 1: Build font cache");
16    let cache = FcFontCache::build();
17    println!("  {} fonts loaded\n", cache.list().len());
18
19    // ── Step 2: Resolve CSS font-family ──
20    let css_families = vec![
21        "Helvetica".to_string(),
22        "Arial".to_string(),
23        "sans-serif".to_string(),
24    ];
25    println!("Step 2: Resolve font-family: {:?}\n", css_families);
26
27    let chain = cache.resolve_font_chain(
28        &css_families,
29        FcWeight::Normal,
30        PatternMatch::False,
31        PatternMatch::False,
32        &mut Vec::new(),
33    );
34
35    for (i, group) in chain.css_fallbacks.iter().enumerate() {
36        print!("  [{}] '{}': {} fonts", i + 1, group.css_name, group.fonts.len());
37        if let Some(first) = group.fonts.first() {
38            if let Some(meta) = cache.get_metadata_by_id(&first.id) {
39                print!(
40                    " (first: {:?})",
41                    meta.name.as_ref().or(meta.family.as_ref())
42                );
43            }
44        }
45        println!();
46    }
47    println!(
48        "  + {} unicode fallback fonts\n",
49        chain.unicode_fallbacks.len()
50    );
51
52    // ── Step 3: Resolve text to font runs ──
53    let text = "Hello 世界! Привет мир";
54    println!("Step 3: Resolve text: '{}'\n", text);
55
56    let resolved = chain.resolve_text(&cache, text);
57
58    // Group by runs of same font
59    let mut runs: Vec<(String, Option<FontId>)> = Vec::new();
60    let mut current_text = String::new();
61    let mut current_id: Option<FontId> = None;
62
63    for (ch, info) in &resolved {
64        let this_id = info.as_ref().map(|(id, _)| *id);
65        if this_id != current_id {
66            if !current_text.is_empty() {
67                runs.push((current_text.clone(), current_id));
68                current_text.clear();
69            }
70            current_id = this_id;
71        }
72        current_text.push(*ch);
73    }
74    if !current_text.is_empty() {
75        runs.push((current_text, current_id));
76    }
77
78    println!("  Font runs:");
79    for (run_text, font_id) in &runs {
80        let name = font_id
81            .as_ref()
82            .and_then(|id| cache.get_metadata_by_id(id))
83            .and_then(|m| m.name.clone().or(m.family.clone()))
84            .unwrap_or_else(|| "[NO FONT]".into());
85        println!("    '{}' -> {}", run_text, name);
86    }
87
88    // ── Step 4: Load font bytes ──
89    let unique_fonts: std::collections::HashSet<_> =
90        runs.iter().filter_map(|(_, id)| *id).collect();
91
92    println!(
93        "\nStep 4: Load fonts ({} unique needed)\n",
94        unique_fonts.len()
95    );
96    for font_id in &unique_fonts {
97        if let Some(meta) = cache.get_metadata_by_id(font_id) {
98            let name = meta
99                .name
100                .as_ref()
101                .or(meta.family.as_ref())
102                .map(|s| s.as_str())
103                .unwrap_or("?");
104            if let Some(source) = cache.get_font_by_id(font_id) {
105                match source {
106                    rust_fontconfig::FontSource::Disk(path) => {
107                        println!("  {} -> {}", name, path.path);
108                    }
109                    rust_fontconfig::FontSource::Memory(font) => {
110                        println!("  {} -> memory (id: {})", name, font.id);
111                    }
112                }
113            }
114        }
115    }
116
117    println!("\nPipeline summary:");
118    println!("  1. FcFontCache::build()       — once at startup");
119    println!("  2. cache.resolve_font_chain() — per CSS font-family");
120    println!("  3. chain.resolve_text()       — per text run");
121    println!("  4. cache.get_font_by_id()     — load bytes for shaping");
122}
examples/debug_azul_fonts.rs (line 68)
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}
Source

pub fn get_font_bytes(&self, id: &FontId) -> Option<Vec<u8>>

Get font bytes (either from disk or memory)

Source

pub fn build() -> Self

Builds a new font cache from all fonts discovered on the system

Examples found in repository?
examples/unicode_aware_fonts.rs (line 12)
11fn main() {
12    let cache = FcFontCache::build();
13
14    println!("=== Unicode-Aware Font Selection ===\n");
15
16    // Create a font chain for sans-serif
17    let chain = cache.resolve_font_chain(
18        &["sans-serif".to_string()],
19        FcWeight::Normal,
20        PatternMatch::False,
21        PatternMatch::False,
22        &mut Vec::new(),
23    );
24
25    println!(
26        "Font chain: {} CSS fallbacks, {} unicode fallbacks\n",
27        chain.css_fallbacks.len(),
28        chain.unicode_fallbacks.len()
29    );
30
31    // Resolve different scripts
32    let texts = [
33        ("Latin", "Hello World"),
34        ("CJK", "你好世界"),
35        ("Japanese", "こんにちは世界"),
36        ("Arabic", "مرحبا بالعالم"),
37        ("Cyrillic", "Привет мир"),
38        ("Mixed", "Hello 世界 Привет"),
39    ];
40
41    for (label, text) in &texts {
42        println!("{} text: '{}'", label, text);
43        print_resolution(&cache, &chain, text);
44        println!();
45    }
46
47    println!("Workflow:");
48    println!("  1. resolve_font_chain() — creates fallback chain from CSS font-family");
49    println!("  2. chain.resolve_text()  — maps each character to a font");
50    println!("  3. Use font IDs to load and render glyphs");
51}
More examples
Hide additional examples
examples/getfont.rs (line 16)
13fn main() {
14    // Build the cache — scans and parses ALL system fonts
15    let start = Instant::now();
16    let cache = FcFontCache::build();
17    let build_time = start.elapsed();
18
19    println!("Cache built: {} fonts in {:?}\n", cache.list().len(), build_time);
20
21    // Query various fonts to showcase fuzzy matching
22    let queries = [
23        ("Arial", FcWeight::Normal, "Common sans-serif"),
24        ("Helvetica", FcWeight::Bold, "Bold variant"),
25        ("Courier", FcWeight::Normal, "Monospace"),
26        ("Georgia", FcWeight::Normal, "Serif"),
27        ("sans-serif", FcWeight::Normal, "Generic family"),
28    ];
29
30    for (name, weight, desc) in &queries {
31        let t = Instant::now();
32        let result = cache.query(
33            &FcPattern {
34                name: Some(name.to_string()),
35                weight: *weight,
36                ..Default::default()
37            },
38            &mut Vec::new(),
39        );
40
41        match result {
42            Some(fm) => {
43                let found = cache
44                    .get_metadata_by_id(&fm.id)
45                    .and_then(|m| m.name.clone().or(m.family.clone()))
46                    .unwrap_or_else(|| format!("{:?}", fm.id));
47                println!(
48                    "  ✓ '{}' ({}) -> {} [{:?}]",
49                    name, desc, found, t.elapsed()
50                );
51            }
52            None => println!("  ✗ '{}' ({}) -> NOT FOUND [{:?}]", name, desc, t.elapsed()),
53        }
54    }
55}
examples/character_resolution.rs (line 12)
11fn main() {
12    let cache = FcFontCache::build();
13
14    // Create a font chain with typical web defaults
15    let families = vec!["system-ui".to_string(), "sans-serif".to_string()];
16
17    let chain = cache.resolve_font_chain(
18        &families,
19        FcWeight::Normal,
20        PatternMatch::False,
21        PatternMatch::False,
22        &mut Vec::new(),
23    );
24
25    // Test characters from different Unicode blocks
26    let test_chars = [
27        ('A', "Latin Capital Letter A"),
28        ('a', "Latin Small Letter A"),
29        ('0', "Digit Zero"),
30        ('€', "Euro Sign"),
31        ('→', "Rightwards Arrow"),
32        ('中', "CJK Ideograph - China"),
33        ('日', "CJK Ideograph - Sun/Day"),
34        ('あ', "Hiragana Letter A"),
35        ('ア', "Katakana Letter A"),
36        ('한', "Hangul Syllable Han"),
37        ('א', "Hebrew Letter Alef"),
38        ('ا', "Arabic Letter Alef"),
39        ('α', "Greek Small Letter Alpha"),
40        ('я', "Cyrillic Small Letter Ya"),
41        ('🙂', "Slightly Smiling Face"),
42        ('♠', "Black Spade Suit"),
43        ('∑', "N-ary Summation"),
44        ('∞', "Infinity"),
45    ];
46
47    println!("Character resolution results:\n");
48    println!("{:<6} {:<30} {:<40}", "Char", "Description", "Font");
49    println!("{}", "-".repeat(76));
50
51    for (ch, description) in &test_chars {
52        let text = ch.to_string();
53        let resolved = chain.resolve_text(&cache, &text);
54
55        let font_name = resolved
56            .first()
57            .and_then(|(_, info)| info.as_ref())
58            .and_then(|(id, _)| cache.get_metadata_by_id(id))
59            .and_then(|m| m.name.clone().or(m.family.clone()))
60            .unwrap_or_else(|| "⚠ NOT FOUND".to_string());
61
62        println!("{:<6} {:<30} {}", ch, description, font_name);
63    }
64
65    // Check specific font coverage
66    println!("\n\nArial coverage check:");
67    let arial_chain = cache.resolve_font_chain(
68        &["Arial".to_string()],
69        FcWeight::Normal,
70        PatternMatch::False,
71        PatternMatch::False,
72        &mut Vec::new(),
73    );
74
75    let arial_result = cache.query(
76        &rust_fontconfig::FcPattern {
77            family: Some("Arial".to_string()),
78            ..Default::default()
79        },
80        &mut Vec::new(),
81    );
82
83    if let Some(arial_match) = arial_result {
84        for ch in ['A', '中', '🙂', '→'] {
85            let resolved = arial_chain.resolve_text(&cache, &ch.to_string());
86            let in_arial = resolved
87                .first()
88                .and_then(|(_, info)| info.as_ref())
89                .map(|(id, _)| id == &arial_match.id)
90                .unwrap_or(false);
91
92            println!(
93                "  {} '{}' (U+{:04X})",
94                if in_arial { "✓" } else { "✗" },
95                ch,
96                ch as u32
97            );
98        }
99    } else {
100        println!("  Arial not found on this system");
101    }
102}
examples/query.rs (line 13)
11fn main() {
12    println!("Building font cache...");
13    let cache = FcFontCache::build();
14    println!("Font cache built with {} fonts\n", cache.list().len());
15
16    // ── Query by family name ──
17    println!("=== Query by Family Name ===");
18    let mut trace = Vec::new();
19    let pattern = FcPattern {
20        family: Some("Arial".to_string()),
21        ..Default::default()
22    };
23
24    if let Some(result) = cache.query(&pattern, &mut trace) {
25        println!("Found Arial:");
26        println!("  Font ID: {:?}", result.id);
27        if let Some(meta) = cache.get_metadata_by_id(&result.id) {
28            println!("  Family: {:?}", meta.family);
29            println!("  Weight: {:?}", meta.weight);
30            println!("  Italic: {:?}", meta.italic);
31        }
32        if let Some(source) = cache.get_font_by_id(&result.id) {
33            match source {
34                rust_fontconfig::FontSource::Disk(path) => println!("  Path: {}", path.path),
35                rust_fontconfig::FontSource::Memory(font) => {
36                    println!("  Memory font: {}", font.id)
37                }
38            }
39        }
40    } else {
41        println!("Arial not found");
42    }
43
44    // ── Query generic family ──
45    println!("\n=== Query Generic 'serif' ===");
46    trace.clear();
47    if let Some(result) = cache.query(
48        &FcPattern {
49            family: Some("serif".to_string()),
50            ..Default::default()
51        },
52        &mut trace,
53    ) {
54        if let Some(meta) = cache.get_metadata_by_id(&result.id) {
55            println!(
56                "Found: {:?}",
57                meta.name.as_ref().or(meta.family.as_ref())
58            );
59        }
60    }
61
62    // ── Query by style (bold + italic) ──
63    println!("\n=== Query Bold Italic ===");
64    trace.clear();
65    if let Some(result) = cache.query(
66        &FcPattern {
67            family: Some("sans-serif".to_string()),
68            weight: FcWeight::Bold,
69            italic: PatternMatch::True,
70            ..Default::default()
71        },
72        &mut trace,
73    ) {
74        if let Some(meta) = cache.get_metadata_by_id(&result.id) {
75            println!(
76                "Found: {:?} weight={:?} italic={:?}",
77                meta.name.as_ref().or(meta.family.as_ref()),
78                meta.weight,
79                meta.italic
80            );
81        }
82    }
83
84    // ── List bold fonts ──
85    println!("\n=== First 5 Bold Fonts ===");
86    for (meta, id) in cache
87        .list()
88        .into_iter()
89        .filter(|(m, _)| matches!(m.weight, FcWeight::Bold | FcWeight::ExtraBold | FcWeight::Black))
90        .take(5)
91    {
92        println!(
93            "  {:?}: {:?}",
94            id,
95            meta.name.as_ref().or(meta.family.as_ref())
96        );
97    }
98
99    // ── Search by name substring ──
100    println!("\n=== Fonts with 'Mono' in name ===");
101    for (meta, _id) in cache.list().into_iter().filter(|(m, _)| {
102        m.name
103            .as_ref()
104            .map(|n| n.contains("Mono"))
105            .unwrap_or(false)
106            || m.family
107                .as_ref()
108                .map(|f| f.contains("Mono"))
109                .unwrap_or(false)
110    }).take(10) {
111        println!(
112            "  {:?}",
113            meta.name.as_ref().or(meta.family.as_ref())
114        );
115    }
116}
examples/integration_api.rs (line 16)
11fn main() {
12    println!("=== Font Integration Pipeline ===\n");
13
14    // ── Step 1: Build font cache ──
15    println!("Step 1: Build font cache");
16    let cache = FcFontCache::build();
17    println!("  {} fonts loaded\n", cache.list().len());
18
19    // ── Step 2: Resolve CSS font-family ──
20    let css_families = vec![
21        "Helvetica".to_string(),
22        "Arial".to_string(),
23        "sans-serif".to_string(),
24    ];
25    println!("Step 2: Resolve font-family: {:?}\n", css_families);
26
27    let chain = cache.resolve_font_chain(
28        &css_families,
29        FcWeight::Normal,
30        PatternMatch::False,
31        PatternMatch::False,
32        &mut Vec::new(),
33    );
34
35    for (i, group) in chain.css_fallbacks.iter().enumerate() {
36        print!("  [{}] '{}': {} fonts", i + 1, group.css_name, group.fonts.len());
37        if let Some(first) = group.fonts.first() {
38            if let Some(meta) = cache.get_metadata_by_id(&first.id) {
39                print!(
40                    " (first: {:?})",
41                    meta.name.as_ref().or(meta.family.as_ref())
42                );
43            }
44        }
45        println!();
46    }
47    println!(
48        "  + {} unicode fallback fonts\n",
49        chain.unicode_fallbacks.len()
50    );
51
52    // ── Step 3: Resolve text to font runs ──
53    let text = "Hello 世界! Привет мир";
54    println!("Step 3: Resolve text: '{}'\n", text);
55
56    let resolved = chain.resolve_text(&cache, text);
57
58    // Group by runs of same font
59    let mut runs: Vec<(String, Option<FontId>)> = Vec::new();
60    let mut current_text = String::new();
61    let mut current_id: Option<FontId> = None;
62
63    for (ch, info) in &resolved {
64        let this_id = info.as_ref().map(|(id, _)| *id);
65        if this_id != current_id {
66            if !current_text.is_empty() {
67                runs.push((current_text.clone(), current_id));
68                current_text.clear();
69            }
70            current_id = this_id;
71        }
72        current_text.push(*ch);
73    }
74    if !current_text.is_empty() {
75        runs.push((current_text, current_id));
76    }
77
78    println!("  Font runs:");
79    for (run_text, font_id) in &runs {
80        let name = font_id
81            .as_ref()
82            .and_then(|id| cache.get_metadata_by_id(id))
83            .and_then(|m| m.name.clone().or(m.family.clone()))
84            .unwrap_or_else(|| "[NO FONT]".into());
85        println!("    '{}' -> {}", run_text, name);
86    }
87
88    // ── Step 4: Load font bytes ──
89    let unique_fonts: std::collections::HashSet<_> =
90        runs.iter().filter_map(|(_, id)| *id).collect();
91
92    println!(
93        "\nStep 4: Load fonts ({} unique needed)\n",
94        unique_fonts.len()
95    );
96    for font_id in &unique_fonts {
97        if let Some(meta) = cache.get_metadata_by_id(font_id) {
98            let name = meta
99                .name
100                .as_ref()
101                .or(meta.family.as_ref())
102                .map(|s| s.as_str())
103                .unwrap_or("?");
104            if let Some(source) = cache.get_font_by_id(font_id) {
105                match source {
106                    rust_fontconfig::FontSource::Disk(path) => {
107                        println!("  {} -> {}", name, path.path);
108                    }
109                    rust_fontconfig::FontSource::Memory(font) => {
110                        println!("  {} -> memory (id: {})", name, font.id);
111                    }
112                }
113            }
114        }
115    }
116
117    println!("\nPipeline summary:");
118    println!("  1. FcFontCache::build()       — once at startup");
119    println!("  2. cache.resolve_font_chain() — per CSS font-family");
120    println!("  3. chain.resolve_text()       — per text run");
121    println!("  4. cache.get_font_by_id()     — load bytes for shaping");
122}
examples/debug_azul_fonts.rs (line 16)
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}
Source

pub fn build_with_families(families: &[impl AsRef<str>]) -> Self

Builds a font cache with only specific font families (and their fallbacks).

This is a performance optimization for applications that know ahead of time which fonts they need. Instead of scanning all system fonts (which can be slow on systems with many fonts), only fonts matching the specified families are loaded.

Generic family names like “sans-serif”, “serif”, “monospace” are expanded to OS-specific font names (e.g., “sans-serif” on macOS becomes “Helvetica Neue”, “San Francisco”, etc.).

Note: This will NOT automatically load fallback fonts for scripts not covered by the requested families. If you need Arabic, CJK, or emoji support, either:

  • Add those families explicitly to the filter
  • Use with_memory_fonts() to add bundled fonts
  • Use build() to load all system fonts
§Arguments
  • families - Font family names to load (e.g., [“Arial”, “sans-serif”])
§Example
// Only load Arial and sans-serif fallback fonts
let cache = FcFontCache::build_with_families(&["Arial", "sans-serif"]);
Source

pub fn is_memory_font(&self, id: &FontId) -> bool

Check if a font ID is a memory font (preferred over disk fonts)

Source

pub fn list(&self) -> Vec<(&FcPattern, FontId)>

Returns the list of fonts and font patterns

Examples found in repository?
examples/getfont.rs (line 19)
13fn main() {
14    // Build the cache — scans and parses ALL system fonts
15    let start = Instant::now();
16    let cache = FcFontCache::build();
17    let build_time = start.elapsed();
18
19    println!("Cache built: {} fonts in {:?}\n", cache.list().len(), build_time);
20
21    // Query various fonts to showcase fuzzy matching
22    let queries = [
23        ("Arial", FcWeight::Normal, "Common sans-serif"),
24        ("Helvetica", FcWeight::Bold, "Bold variant"),
25        ("Courier", FcWeight::Normal, "Monospace"),
26        ("Georgia", FcWeight::Normal, "Serif"),
27        ("sans-serif", FcWeight::Normal, "Generic family"),
28    ];
29
30    for (name, weight, desc) in &queries {
31        let t = Instant::now();
32        let result = cache.query(
33            &FcPattern {
34                name: Some(name.to_string()),
35                weight: *weight,
36                ..Default::default()
37            },
38            &mut Vec::new(),
39        );
40
41        match result {
42            Some(fm) => {
43                let found = cache
44                    .get_metadata_by_id(&fm.id)
45                    .and_then(|m| m.name.clone().or(m.family.clone()))
46                    .unwrap_or_else(|| format!("{:?}", fm.id));
47                println!(
48                    "  ✓ '{}' ({}) -> {} [{:?}]",
49                    name, desc, found, t.elapsed()
50                );
51            }
52            None => println!("  ✗ '{}' ({}) -> NOT FOUND [{:?}]", name, desc, t.elapsed()),
53        }
54    }
55}
More examples
Hide additional examples
examples/query.rs (line 14)
11fn main() {
12    println!("Building font cache...");
13    let cache = FcFontCache::build();
14    println!("Font cache built with {} fonts\n", cache.list().len());
15
16    // ── Query by family name ──
17    println!("=== Query by Family Name ===");
18    let mut trace = Vec::new();
19    let pattern = FcPattern {
20        family: Some("Arial".to_string()),
21        ..Default::default()
22    };
23
24    if let Some(result) = cache.query(&pattern, &mut trace) {
25        println!("Found Arial:");
26        println!("  Font ID: {:?}", result.id);
27        if let Some(meta) = cache.get_metadata_by_id(&result.id) {
28            println!("  Family: {:?}", meta.family);
29            println!("  Weight: {:?}", meta.weight);
30            println!("  Italic: {:?}", meta.italic);
31        }
32        if let Some(source) = cache.get_font_by_id(&result.id) {
33            match source {
34                rust_fontconfig::FontSource::Disk(path) => println!("  Path: {}", path.path),
35                rust_fontconfig::FontSource::Memory(font) => {
36                    println!("  Memory font: {}", font.id)
37                }
38            }
39        }
40    } else {
41        println!("Arial not found");
42    }
43
44    // ── Query generic family ──
45    println!("\n=== Query Generic 'serif' ===");
46    trace.clear();
47    if let Some(result) = cache.query(
48        &FcPattern {
49            family: Some("serif".to_string()),
50            ..Default::default()
51        },
52        &mut trace,
53    ) {
54        if let Some(meta) = cache.get_metadata_by_id(&result.id) {
55            println!(
56                "Found: {:?}",
57                meta.name.as_ref().or(meta.family.as_ref())
58            );
59        }
60    }
61
62    // ── Query by style (bold + italic) ──
63    println!("\n=== Query Bold Italic ===");
64    trace.clear();
65    if let Some(result) = cache.query(
66        &FcPattern {
67            family: Some("sans-serif".to_string()),
68            weight: FcWeight::Bold,
69            italic: PatternMatch::True,
70            ..Default::default()
71        },
72        &mut trace,
73    ) {
74        if let Some(meta) = cache.get_metadata_by_id(&result.id) {
75            println!(
76                "Found: {:?} weight={:?} italic={:?}",
77                meta.name.as_ref().or(meta.family.as_ref()),
78                meta.weight,
79                meta.italic
80            );
81        }
82    }
83
84    // ── List bold fonts ──
85    println!("\n=== First 5 Bold Fonts ===");
86    for (meta, id) in cache
87        .list()
88        .into_iter()
89        .filter(|(m, _)| matches!(m.weight, FcWeight::Bold | FcWeight::ExtraBold | FcWeight::Black))
90        .take(5)
91    {
92        println!(
93            "  {:?}: {:?}",
94            id,
95            meta.name.as_ref().or(meta.family.as_ref())
96        );
97    }
98
99    // ── Search by name substring ──
100    println!("\n=== Fonts with 'Mono' in name ===");
101    for (meta, _id) in cache.list().into_iter().filter(|(m, _)| {
102        m.name
103            .as_ref()
104            .map(|n| n.contains("Mono"))
105            .unwrap_or(false)
106            || m.family
107                .as_ref()
108                .map(|f| f.contains("Mono"))
109                .unwrap_or(false)
110    }).take(10) {
111        println!(
112            "  {:?}",
113            meta.name.as_ref().or(meta.family.as_ref())
114        );
115    }
116}
examples/integration_api.rs (line 17)
11fn main() {
12    println!("=== Font Integration Pipeline ===\n");
13
14    // ── Step 1: Build font cache ──
15    println!("Step 1: Build font cache");
16    let cache = FcFontCache::build();
17    println!("  {} fonts loaded\n", cache.list().len());
18
19    // ── Step 2: Resolve CSS font-family ──
20    let css_families = vec![
21        "Helvetica".to_string(),
22        "Arial".to_string(),
23        "sans-serif".to_string(),
24    ];
25    println!("Step 2: Resolve font-family: {:?}\n", css_families);
26
27    let chain = cache.resolve_font_chain(
28        &css_families,
29        FcWeight::Normal,
30        PatternMatch::False,
31        PatternMatch::False,
32        &mut Vec::new(),
33    );
34
35    for (i, group) in chain.css_fallbacks.iter().enumerate() {
36        print!("  [{}] '{}': {} fonts", i + 1, group.css_name, group.fonts.len());
37        if let Some(first) = group.fonts.first() {
38            if let Some(meta) = cache.get_metadata_by_id(&first.id) {
39                print!(
40                    " (first: {:?})",
41                    meta.name.as_ref().or(meta.family.as_ref())
42                );
43            }
44        }
45        println!();
46    }
47    println!(
48        "  + {} unicode fallback fonts\n",
49        chain.unicode_fallbacks.len()
50    );
51
52    // ── Step 3: Resolve text to font runs ──
53    let text = "Hello 世界! Привет мир";
54    println!("Step 3: Resolve text: '{}'\n", text);
55
56    let resolved = chain.resolve_text(&cache, text);
57
58    // Group by runs of same font
59    let mut runs: Vec<(String, Option<FontId>)> = Vec::new();
60    let mut current_text = String::new();
61    let mut current_id: Option<FontId> = None;
62
63    for (ch, info) in &resolved {
64        let this_id = info.as_ref().map(|(id, _)| *id);
65        if this_id != current_id {
66            if !current_text.is_empty() {
67                runs.push((current_text.clone(), current_id));
68                current_text.clear();
69            }
70            current_id = this_id;
71        }
72        current_text.push(*ch);
73    }
74    if !current_text.is_empty() {
75        runs.push((current_text, current_id));
76    }
77
78    println!("  Font runs:");
79    for (run_text, font_id) in &runs {
80        let name = font_id
81            .as_ref()
82            .and_then(|id| cache.get_metadata_by_id(id))
83            .and_then(|m| m.name.clone().or(m.family.clone()))
84            .unwrap_or_else(|| "[NO FONT]".into());
85        println!("    '{}' -> {}", run_text, name);
86    }
87
88    // ── Step 4: Load font bytes ──
89    let unique_fonts: std::collections::HashSet<_> =
90        runs.iter().filter_map(|(_, id)| *id).collect();
91
92    println!(
93        "\nStep 4: Load fonts ({} unique needed)\n",
94        unique_fonts.len()
95    );
96    for font_id in &unique_fonts {
97        if let Some(meta) = cache.get_metadata_by_id(font_id) {
98            let name = meta
99                .name
100                .as_ref()
101                .or(meta.family.as_ref())
102                .map(|s| s.as_str())
103                .unwrap_or("?");
104            if let Some(source) = cache.get_font_by_id(font_id) {
105                match source {
106                    rust_fontconfig::FontSource::Disk(path) => {
107                        println!("  {} -> {}", name, path.path);
108                    }
109                    rust_fontconfig::FontSource::Memory(font) => {
110                        println!("  {} -> memory (id: {})", name, font.id);
111                    }
112                }
113            }
114        }
115    }
116
117    println!("\nPipeline summary:");
118    println!("  1. FcFontCache::build()       — once at startup");
119    println!("  2. cache.resolve_font_chain() — per CSS font-family");
120    println!("  3. chain.resolve_text()       — per text run");
121    println!("  4. cache.get_font_by_id()     — load bytes for shaping");
122}
examples/debug_azul_fonts.rs (line 17)
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}
Source

pub fn is_empty(&self) -> bool

Returns true if the cache contains no font patterns

Source

pub fn len(&self) -> usize

Returns the number of font patterns in the cache

Source

pub fn query( &self, pattern: &FcPattern, trace: &mut Vec<TraceMsg>, ) -> Option<FontMatch>

Queries a font from the in-memory cache, returns the first found font (early return) Memory fonts are always preferred over disk fonts with the same match quality.

Examples found in repository?
examples/getfont.rs (lines 32-39)
13fn main() {
14    // Build the cache — scans and parses ALL system fonts
15    let start = Instant::now();
16    let cache = FcFontCache::build();
17    let build_time = start.elapsed();
18
19    println!("Cache built: {} fonts in {:?}\n", cache.list().len(), build_time);
20
21    // Query various fonts to showcase fuzzy matching
22    let queries = [
23        ("Arial", FcWeight::Normal, "Common sans-serif"),
24        ("Helvetica", FcWeight::Bold, "Bold variant"),
25        ("Courier", FcWeight::Normal, "Monospace"),
26        ("Georgia", FcWeight::Normal, "Serif"),
27        ("sans-serif", FcWeight::Normal, "Generic family"),
28    ];
29
30    for (name, weight, desc) in &queries {
31        let t = Instant::now();
32        let result = cache.query(
33            &FcPattern {
34                name: Some(name.to_string()),
35                weight: *weight,
36                ..Default::default()
37            },
38            &mut Vec::new(),
39        );
40
41        match result {
42            Some(fm) => {
43                let found = cache
44                    .get_metadata_by_id(&fm.id)
45                    .and_then(|m| m.name.clone().or(m.family.clone()))
46                    .unwrap_or_else(|| format!("{:?}", fm.id));
47                println!(
48                    "  ✓ '{}' ({}) -> {} [{:?}]",
49                    name, desc, found, t.elapsed()
50                );
51            }
52            None => println!("  ✗ '{}' ({}) -> NOT FOUND [{:?}]", name, desc, t.elapsed()),
53        }
54    }
55}
More examples
Hide additional examples
examples/character_resolution.rs (lines 75-81)
11fn main() {
12    let cache = FcFontCache::build();
13
14    // Create a font chain with typical web defaults
15    let families = vec!["system-ui".to_string(), "sans-serif".to_string()];
16
17    let chain = cache.resolve_font_chain(
18        &families,
19        FcWeight::Normal,
20        PatternMatch::False,
21        PatternMatch::False,
22        &mut Vec::new(),
23    );
24
25    // Test characters from different Unicode blocks
26    let test_chars = [
27        ('A', "Latin Capital Letter A"),
28        ('a', "Latin Small Letter A"),
29        ('0', "Digit Zero"),
30        ('€', "Euro Sign"),
31        ('→', "Rightwards Arrow"),
32        ('中', "CJK Ideograph - China"),
33        ('日', "CJK Ideograph - Sun/Day"),
34        ('あ', "Hiragana Letter A"),
35        ('ア', "Katakana Letter A"),
36        ('한', "Hangul Syllable Han"),
37        ('א', "Hebrew Letter Alef"),
38        ('ا', "Arabic Letter Alef"),
39        ('α', "Greek Small Letter Alpha"),
40        ('я', "Cyrillic Small Letter Ya"),
41        ('🙂', "Slightly Smiling Face"),
42        ('♠', "Black Spade Suit"),
43        ('∑', "N-ary Summation"),
44        ('∞', "Infinity"),
45    ];
46
47    println!("Character resolution results:\n");
48    println!("{:<6} {:<30} {:<40}", "Char", "Description", "Font");
49    println!("{}", "-".repeat(76));
50
51    for (ch, description) in &test_chars {
52        let text = ch.to_string();
53        let resolved = chain.resolve_text(&cache, &text);
54
55        let font_name = resolved
56            .first()
57            .and_then(|(_, info)| info.as_ref())
58            .and_then(|(id, _)| cache.get_metadata_by_id(id))
59            .and_then(|m| m.name.clone().or(m.family.clone()))
60            .unwrap_or_else(|| "⚠ NOT FOUND".to_string());
61
62        println!("{:<6} {:<30} {}", ch, description, font_name);
63    }
64
65    // Check specific font coverage
66    println!("\n\nArial coverage check:");
67    let arial_chain = cache.resolve_font_chain(
68        &["Arial".to_string()],
69        FcWeight::Normal,
70        PatternMatch::False,
71        PatternMatch::False,
72        &mut Vec::new(),
73    );
74
75    let arial_result = cache.query(
76        &rust_fontconfig::FcPattern {
77            family: Some("Arial".to_string()),
78            ..Default::default()
79        },
80        &mut Vec::new(),
81    );
82
83    if let Some(arial_match) = arial_result {
84        for ch in ['A', '中', '🙂', '→'] {
85            let resolved = arial_chain.resolve_text(&cache, &ch.to_string());
86            let in_arial = resolved
87                .first()
88                .and_then(|(_, info)| info.as_ref())
89                .map(|(id, _)| id == &arial_match.id)
90                .unwrap_or(false);
91
92            println!(
93                "  {} '{}' (U+{:04X})",
94                if in_arial { "✓" } else { "✗" },
95                ch,
96                ch as u32
97            );
98        }
99    } else {
100        println!("  Arial not found on this system");
101    }
102}
examples/query.rs (line 24)
11fn main() {
12    println!("Building font cache...");
13    let cache = FcFontCache::build();
14    println!("Font cache built with {} fonts\n", cache.list().len());
15
16    // ── Query by family name ──
17    println!("=== Query by Family Name ===");
18    let mut trace = Vec::new();
19    let pattern = FcPattern {
20        family: Some("Arial".to_string()),
21        ..Default::default()
22    };
23
24    if let Some(result) = cache.query(&pattern, &mut trace) {
25        println!("Found Arial:");
26        println!("  Font ID: {:?}", result.id);
27        if let Some(meta) = cache.get_metadata_by_id(&result.id) {
28            println!("  Family: {:?}", meta.family);
29            println!("  Weight: {:?}", meta.weight);
30            println!("  Italic: {:?}", meta.italic);
31        }
32        if let Some(source) = cache.get_font_by_id(&result.id) {
33            match source {
34                rust_fontconfig::FontSource::Disk(path) => println!("  Path: {}", path.path),
35                rust_fontconfig::FontSource::Memory(font) => {
36                    println!("  Memory font: {}", font.id)
37                }
38            }
39        }
40    } else {
41        println!("Arial not found");
42    }
43
44    // ── Query generic family ──
45    println!("\n=== Query Generic 'serif' ===");
46    trace.clear();
47    if let Some(result) = cache.query(
48        &FcPattern {
49            family: Some("serif".to_string()),
50            ..Default::default()
51        },
52        &mut trace,
53    ) {
54        if let Some(meta) = cache.get_metadata_by_id(&result.id) {
55            println!(
56                "Found: {:?}",
57                meta.name.as_ref().or(meta.family.as_ref())
58            );
59        }
60    }
61
62    // ── Query by style (bold + italic) ──
63    println!("\n=== Query Bold Italic ===");
64    trace.clear();
65    if let Some(result) = cache.query(
66        &FcPattern {
67            family: Some("sans-serif".to_string()),
68            weight: FcWeight::Bold,
69            italic: PatternMatch::True,
70            ..Default::default()
71        },
72        &mut trace,
73    ) {
74        if let Some(meta) = cache.get_metadata_by_id(&result.id) {
75            println!(
76                "Found: {:?} weight={:?} italic={:?}",
77                meta.name.as_ref().or(meta.family.as_ref()),
78                meta.weight,
79                meta.italic
80            );
81        }
82    }
83
84    // ── List bold fonts ──
85    println!("\n=== First 5 Bold Fonts ===");
86    for (meta, id) in cache
87        .list()
88        .into_iter()
89        .filter(|(m, _)| matches!(m.weight, FcWeight::Bold | FcWeight::ExtraBold | FcWeight::Black))
90        .take(5)
91    {
92        println!(
93            "  {:?}: {:?}",
94            id,
95            meta.name.as_ref().or(meta.family.as_ref())
96        );
97    }
98
99    // ── Search by name substring ──
100    println!("\n=== Fonts with 'Mono' in name ===");
101    for (meta, _id) in cache.list().into_iter().filter(|(m, _)| {
102        m.name
103            .as_ref()
104            .map(|n| n.contains("Mono"))
105            .unwrap_or(false)
106            || m.family
107                .as_ref()
108                .map(|f| f.contains("Mono"))
109                .unwrap_or(false)
110    }).take(10) {
111        println!(
112            "  {:?}",
113            meta.name.as_ref().or(meta.family.as_ref())
114        );
115    }
116}
Source

pub fn compute_fallbacks( &self, font_id: &FontId, trace: &mut Vec<TraceMsg>, ) -> Vec<FontMatchNoFallback>

Compute fallback fonts for a given font This is a lazy operation that can be expensive - only call when actually needed (e.g., for FFI or debugging, not needed for resolve_char)

Source

pub fn get_memory_font(&self, id: &FontId) -> Option<&FcFont>

Get in-memory font data

Source

pub fn query_matches_internal( k: &FcPattern, pattern: &FcPattern, trace: &mut Vec<TraceMsg>, ) -> bool

Check if a pattern matches the query, with detailed tracing

Source

pub fn resolve_font_chain( &self, font_families: &[String], weight: FcWeight, italic: PatternMatch, oblique: PatternMatch, trace: &mut Vec<TraceMsg>, ) -> FontFallbackChain

Resolve a complete font fallback chain for a CSS font-family stack This is the main entry point for font resolution with caching Automatically expands generic CSS families (serif, sans-serif, monospace) to OS-specific fonts

§Arguments
  • font_families - CSS font-family stack (e.g., [“Arial”, “sans-serif”])
  • text - The text to render (used to extract Unicode ranges)
  • weight - Font weight
  • italic - Italic style requirement
  • oblique - Oblique style requirement
  • trace - Debug trace messages
§Returns

A complete font fallback chain with CSS fallbacks and Unicode fallbacks

§Example
let cache = FcFontCache::build();
let families = vec!["Arial".to_string(), "sans-serif".to_string()];
let chain = cache.resolve_font_chain(&families, FcWeight::Normal, 
                                      PatternMatch::DontCare, PatternMatch::DontCare, 
                                      &mut Vec::new());
// On macOS: families expanded to ["Arial", "San Francisco", "Helvetica Neue", "Lucida Grande"]
Examples found in repository?
examples/unicode_aware_fonts.rs (lines 17-23)
11fn main() {
12    let cache = FcFontCache::build();
13
14    println!("=== Unicode-Aware Font Selection ===\n");
15
16    // Create a font chain for sans-serif
17    let chain = cache.resolve_font_chain(
18        &["sans-serif".to_string()],
19        FcWeight::Normal,
20        PatternMatch::False,
21        PatternMatch::False,
22        &mut Vec::new(),
23    );
24
25    println!(
26        "Font chain: {} CSS fallbacks, {} unicode fallbacks\n",
27        chain.css_fallbacks.len(),
28        chain.unicode_fallbacks.len()
29    );
30
31    // Resolve different scripts
32    let texts = [
33        ("Latin", "Hello World"),
34        ("CJK", "你好世界"),
35        ("Japanese", "こんにちは世界"),
36        ("Arabic", "مرحبا بالعالم"),
37        ("Cyrillic", "Привет мир"),
38        ("Mixed", "Hello 世界 Привет"),
39    ];
40
41    for (label, text) in &texts {
42        println!("{} text: '{}'", label, text);
43        print_resolution(&cache, &chain, text);
44        println!();
45    }
46
47    println!("Workflow:");
48    println!("  1. resolve_font_chain() — creates fallback chain from CSS font-family");
49    println!("  2. chain.resolve_text()  — maps each character to a font");
50    println!("  3. Use font IDs to load and render glyphs");
51}
More examples
Hide additional examples
examples/character_resolution.rs (lines 17-23)
11fn main() {
12    let cache = FcFontCache::build();
13
14    // Create a font chain with typical web defaults
15    let families = vec!["system-ui".to_string(), "sans-serif".to_string()];
16
17    let chain = cache.resolve_font_chain(
18        &families,
19        FcWeight::Normal,
20        PatternMatch::False,
21        PatternMatch::False,
22        &mut Vec::new(),
23    );
24
25    // Test characters from different Unicode blocks
26    let test_chars = [
27        ('A', "Latin Capital Letter A"),
28        ('a', "Latin Small Letter A"),
29        ('0', "Digit Zero"),
30        ('€', "Euro Sign"),
31        ('→', "Rightwards Arrow"),
32        ('中', "CJK Ideograph - China"),
33        ('日', "CJK Ideograph - Sun/Day"),
34        ('あ', "Hiragana Letter A"),
35        ('ア', "Katakana Letter A"),
36        ('한', "Hangul Syllable Han"),
37        ('א', "Hebrew Letter Alef"),
38        ('ا', "Arabic Letter Alef"),
39        ('α', "Greek Small Letter Alpha"),
40        ('я', "Cyrillic Small Letter Ya"),
41        ('🙂', "Slightly Smiling Face"),
42        ('♠', "Black Spade Suit"),
43        ('∑', "N-ary Summation"),
44        ('∞', "Infinity"),
45    ];
46
47    println!("Character resolution results:\n");
48    println!("{:<6} {:<30} {:<40}", "Char", "Description", "Font");
49    println!("{}", "-".repeat(76));
50
51    for (ch, description) in &test_chars {
52        let text = ch.to_string();
53        let resolved = chain.resolve_text(&cache, &text);
54
55        let font_name = resolved
56            .first()
57            .and_then(|(_, info)| info.as_ref())
58            .and_then(|(id, _)| cache.get_metadata_by_id(id))
59            .and_then(|m| m.name.clone().or(m.family.clone()))
60            .unwrap_or_else(|| "⚠ NOT FOUND".to_string());
61
62        println!("{:<6} {:<30} {}", ch, description, font_name);
63    }
64
65    // Check specific font coverage
66    println!("\n\nArial coverage check:");
67    let arial_chain = cache.resolve_font_chain(
68        &["Arial".to_string()],
69        FcWeight::Normal,
70        PatternMatch::False,
71        PatternMatch::False,
72        &mut Vec::new(),
73    );
74
75    let arial_result = cache.query(
76        &rust_fontconfig::FcPattern {
77            family: Some("Arial".to_string()),
78            ..Default::default()
79        },
80        &mut Vec::new(),
81    );
82
83    if let Some(arial_match) = arial_result {
84        for ch in ['A', '中', '🙂', '→'] {
85            let resolved = arial_chain.resolve_text(&cache, &ch.to_string());
86            let in_arial = resolved
87                .first()
88                .and_then(|(_, info)| info.as_ref())
89                .map(|(id, _)| id == &arial_match.id)
90                .unwrap_or(false);
91
92            println!(
93                "  {} '{}' (U+{:04X})",
94                if in_arial { "✓" } else { "✗" },
95                ch,
96                ch as u32
97            );
98        }
99    } else {
100        println!("  Arial not found on this system");
101    }
102}
examples/integration_api.rs (lines 27-33)
11fn main() {
12    println!("=== Font Integration Pipeline ===\n");
13
14    // ── Step 1: Build font cache ──
15    println!("Step 1: Build font cache");
16    let cache = FcFontCache::build();
17    println!("  {} fonts loaded\n", cache.list().len());
18
19    // ── Step 2: Resolve CSS font-family ──
20    let css_families = vec![
21        "Helvetica".to_string(),
22        "Arial".to_string(),
23        "sans-serif".to_string(),
24    ];
25    println!("Step 2: Resolve font-family: {:?}\n", css_families);
26
27    let chain = cache.resolve_font_chain(
28        &css_families,
29        FcWeight::Normal,
30        PatternMatch::False,
31        PatternMatch::False,
32        &mut Vec::new(),
33    );
34
35    for (i, group) in chain.css_fallbacks.iter().enumerate() {
36        print!("  [{}] '{}': {} fonts", i + 1, group.css_name, group.fonts.len());
37        if let Some(first) = group.fonts.first() {
38            if let Some(meta) = cache.get_metadata_by_id(&first.id) {
39                print!(
40                    " (first: {:?})",
41                    meta.name.as_ref().or(meta.family.as_ref())
42                );
43            }
44        }
45        println!();
46    }
47    println!(
48        "  + {} unicode fallback fonts\n",
49        chain.unicode_fallbacks.len()
50    );
51
52    // ── Step 3: Resolve text to font runs ──
53    let text = "Hello 世界! Привет мир";
54    println!("Step 3: Resolve text: '{}'\n", text);
55
56    let resolved = chain.resolve_text(&cache, text);
57
58    // Group by runs of same font
59    let mut runs: Vec<(String, Option<FontId>)> = Vec::new();
60    let mut current_text = String::new();
61    let mut current_id: Option<FontId> = None;
62
63    for (ch, info) in &resolved {
64        let this_id = info.as_ref().map(|(id, _)| *id);
65        if this_id != current_id {
66            if !current_text.is_empty() {
67                runs.push((current_text.clone(), current_id));
68                current_text.clear();
69            }
70            current_id = this_id;
71        }
72        current_text.push(*ch);
73    }
74    if !current_text.is_empty() {
75        runs.push((current_text, current_id));
76    }
77
78    println!("  Font runs:");
79    for (run_text, font_id) in &runs {
80        let name = font_id
81            .as_ref()
82            .and_then(|id| cache.get_metadata_by_id(id))
83            .and_then(|m| m.name.clone().or(m.family.clone()))
84            .unwrap_or_else(|| "[NO FONT]".into());
85        println!("    '{}' -> {}", run_text, name);
86    }
87
88    // ── Step 4: Load font bytes ──
89    let unique_fonts: std::collections::HashSet<_> =
90        runs.iter().filter_map(|(_, id)| *id).collect();
91
92    println!(
93        "\nStep 4: Load fonts ({} unique needed)\n",
94        unique_fonts.len()
95    );
96    for font_id in &unique_fonts {
97        if let Some(meta) = cache.get_metadata_by_id(font_id) {
98            let name = meta
99                .name
100                .as_ref()
101                .or(meta.family.as_ref())
102                .map(|s| s.as_str())
103                .unwrap_or("?");
104            if let Some(source) = cache.get_font_by_id(font_id) {
105                match source {
106                    rust_fontconfig::FontSource::Disk(path) => {
107                        println!("  {} -> {}", name, path.path);
108                    }
109                    rust_fontconfig::FontSource::Memory(font) => {
110                        println!("  {} -> memory (id: {})", name, font.id);
111                    }
112                }
113            }
114        }
115    }
116
117    println!("\nPipeline summary:");
118    println!("  1. FcFontCache::build()       — once at startup");
119    println!("  2. cache.resolve_font_chain() — per CSS font-family");
120    println!("  3. chain.resolve_text()       — per text run");
121    println!("  4. cache.get_font_by_id()     — load bytes for shaping");
122}
examples/debug_azul_fonts.rs (lines 56-62)
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}
Source

pub fn resolve_font_chain_with_os( &self, font_families: &[String], weight: FcWeight, italic: PatternMatch, oblique: PatternMatch, trace: &mut Vec<TraceMsg>, os: OperatingSystem, ) -> FontFallbackChain

Resolve font chain with explicit OS specification (useful for testing)

Source

pub fn extract_font_name_tokens(name: &str) -> Vec<String>

Extract tokens from a font name E.g., “NotoSansJP” -> [“Noto”, “Sans”, “JP”] E.g., “Noto Sans CJK JP” -> [“Noto”, “Sans”, “CJK”, “JP”]

Source

pub fn calculate_unicode_coverage(ranges: &[UnicodeRange]) -> u64

Find fallback fonts for a given pattern

Source

pub fn calculate_unicode_compatibility( requested: &[UnicodeRange], available: &[UnicodeRange], ) -> i32

Calculate how well a font’s Unicode ranges cover the requested ranges Returns a compatibility score (higher is better, 0 means no overlap)

Source

pub fn calculate_style_score(original: &FcPattern, candidate: &FcPattern) -> i32

Examples found in repository?
examples/debug_azul_fonts.rs (lines 27-35)
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}

Trait Implementations§

Source§

impl Clone for FcFontCache

Source§

fn clone(&self) -> Self

Returns a duplicate of the value. Read more
1.0.0 · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for FcFontCache

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Default for FcFontCache

Source§

fn default() -> Self

Returns the “default value” for a type. Read more

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where T: Clone,

Source§

unsafe fn clone_to_uninit(&self, dest: *mut u8)

🔬This is a nightly-only experimental API. (clone_to_uninit)
Performs copy-assignment from self to dest. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> IntoEither for T

Source§

fn into_either(self, into_left: bool) -> Either<Self, Self>

Converts self into a Left variant of Either<Self, Self> if into_left is true. Converts self into a Right variant of Either<Self, Self> otherwise. Read more
Source§

fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
where F: FnOnce(&Self) -> bool,

Converts self into a Left variant of Either<Self, Self> if into_left(&self) returns true. Converts self into a Right variant of Either<Self, Self> otherwise. Read more
Source§

impl<T> Pointable for T

Source§

const ALIGN: usize

The alignment of pointer.
Source§

type Init = T

The type for initializers.
Source§

unsafe fn init(init: <T as Pointable>::Init) -> usize

Initializes a with the given initializer. Read more
Source§

unsafe fn deref<'a>(ptr: usize) -> &'a T

Dereferences the given pointer. Read more
Source§

unsafe fn deref_mut<'a>(ptr: usize) -> &'a mut T

Mutably dereferences the given pointer. Read more
Source§

unsafe fn drop(ptr: usize)

Drops the object pointed to by the given pointer. Read more
Source§

impl<T> ToOwned for T
where T: Clone,

Source§

type Owned = T

The resulting type after obtaining ownership.
Source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
Source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.