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 30)
7fn main() {
8    // Initialize font cache - scans system fonts
9    println!("Building font cache...");
10    let cache = FcFontCache::build();
11    println!("Font cache built with {} fonts\n", cache.list().len());
12    
13    // Example 1: Query by family name
14    println!("=== Query by Family Name ===");
15    let mut trace = Vec::new();
16    let pattern = FcPattern {
17        family: Some("Arial".to_string()),
18        ..Default::default()
19    };
20    
21    if let Some(match_result) = cache.query(&pattern, &mut trace) {
22        println!("Found Arial:");
23        println!("  Font ID: {:?}", match_result.id);
24        if let Some(meta) = cache.get_metadata_by_id(&match_result.id) {
25            println!("  Family: {:?}", meta.family);
26            println!("  Weight: {:?}", meta.weight);
27            println!("  Italic: {:?}", meta.italic);
28        }
29        // To get the path, use get_font_by_id
30        if let Some(source) = cache.get_font_by_id(&match_result.id) {
31            match source {
32                rust_fontconfig::FontSource::Disk(path) => {
33                    println!("  Path: {}", path.path);
34                }
35                rust_fontconfig::FontSource::Memory(font) => {
36                    println!("  Memory font: {}", font.id);
37                }
38            }
39        }
40    } else {
41        println!("Arial not found, trace:");
42        for t in &trace {
43            println!("  {:?}", t);
44        }
45    }
46    
47    // Example 2: Query by generic family
48    println!("\n=== Query Generic 'serif' ===");
49    trace.clear();
50    let pattern = FcPattern {
51        family: Some("serif".to_string()),
52        ..Default::default()
53    };
54    
55    if let Some(match_result) = cache.query(&pattern, &mut trace) {
56        println!("Found serif font:");
57        if let Some(meta) = cache.get_metadata_by_id(&match_result.id) {
58            println!("  Name: {:?}", meta.name.as_ref().or(meta.family.as_ref()));
59        }
60    }
61    
62    // Example 3: Query by style (bold + italic)
63    println!("\n=== Query Bold Italic ===");
64    trace.clear();
65    let pattern = FcPattern {
66        family: Some("sans-serif".to_string()),
67        weight: FcWeight::Bold,
68        italic: PatternMatch::True,
69        ..Default::default()
70    };
71    
72    if let Some(match_result) = cache.query(&pattern, &mut trace) {
73        println!("Found bold italic sans-serif:");
74        if let Some(meta) = cache.get_metadata_by_id(&match_result.id) {
75            println!("  Name: {:?}", meta.name);
76            println!("  Family: {:?}", meta.family);
77            println!("  Weight: {:?}", meta.weight);
78            println!("  Italic: {:?}", meta.italic);
79        }
80    }
81    
82    // Example 4: List all fonts with a specific weight
83    println!("\n=== Listing Bold Fonts ===");
84    let bold_fonts: Vec<_> = cache.list().into_iter()
85        .filter(|(meta, _id)| {
86            matches!(meta.weight, FcWeight::Bold | FcWeight::ExtraBold | FcWeight::Black)
87        })
88        .take(5)
89        .collect();
90    
91    println!("First 5 bold fonts:");
92    for (meta, id) in bold_fonts {
93        println!("  {:?}: {:?}", id, meta.name.as_ref().or(meta.family.as_ref()));
94    }
95    
96    // Example 5: Search by name pattern
97    println!("\n=== Fonts with 'Mono' in name ===");
98    let mono_fonts: Vec<_> = cache.list().into_iter()
99        .filter(|(meta, _id)| {
100            meta.name.as_ref().map(|n| n.contains("Mono")).unwrap_or(false) ||
101            meta.family.as_ref().map(|f| f.contains("Mono")).unwrap_or(false)
102        })
103        .take(5)
104        .collect();
105    
106    println!("First 5 monospace fonts:");
107    for (meta, id) in mono_fonts {
108        println!("  {:?}: {:?}", id, meta.name.as_ref().or(meta.family.as_ref()));
109    }
110}
More examples
Hide additional examples
examples/integration_api.rs (line 114)
7fn main() {
8    println!("Font Integration API Example\n");
9    
10    // Step 1: Build the font cache once at application startup
11    println!("Step 1: Building font cache...");
12    let cache = FcFontCache::build();
13    println!("  Loaded {} fonts\n", cache.list().len());
14    
15    // Step 2: Simulate CSS font-family resolution
16    // This is what a browser/text renderer would do
17    println!("Step 2: Resolving CSS font-family: 'Helvetica, Arial, sans-serif'\n");
18    
19    let css_families = vec![
20        "Helvetica".to_string(),
21        "Arial".to_string(),
22        "sans-serif".to_string(),
23    ];
24    
25    let mut trace = Vec::new();
26    let chain = cache.resolve_font_chain(
27        &css_families,
28        FcWeight::Normal,
29        PatternMatch::False,  // italic
30        PatternMatch::False,  // oblique
31        &mut trace,
32    );
33    
34    println!("  CSS fallback groups: {}", chain.css_fallbacks.len());
35    for (i, group) in chain.css_fallbacks.iter().enumerate() {
36        println!("    {}: CSS '{}' -> {} fonts", i + 1, group.css_name, group.fonts.len());
37        for font in group.fonts.iter().take(2) {
38            if let Some(meta) = cache.get_metadata_by_id(&font.id) {
39                let name = meta.name.as_ref().or(meta.family.as_ref());
40                println!("       - {:?}", name);
41            }
42        }
43        if group.fonts.len() > 2 {
44            println!("       ... and {} more", group.fonts.len() - 2);
45        }
46    }
47    
48    println!("\n  Unicode fallback fonts: {}", chain.unicode_fallbacks.len());
49    for (i, font) in chain.unicode_fallbacks.iter().take(3).enumerate() {
50        if let Some(meta) = cache.get_metadata_by_id(&font.id) {
51            println!("    {}: {:?}", 
52                     i + 1, 
53                     meta.name.as_ref().or(meta.family.as_ref()));
54        }
55    }
56    if chain.unicode_fallbacks.len() > 3 {
57        println!("    ... and {} more", chain.unicode_fallbacks.len() - 3);
58    }
59    
60    // Step 3: Resolve text to fonts
61    // This maps each character to a specific font
62    println!("\n\nStep 3: Resolve text to fonts");
63    
64    let text = "Hello 世界! Привет мир";
65    println!("  Input text: '{}'\n", text);
66    
67    let resolved = chain.resolve_text(&cache, text);
68    
69    // Group by runs of same font
70    let mut runs = Vec::new();
71    let mut current_run_text = String::new();
72    let mut current_font_id: Option<FontId> = None;
73    
74    for (ch, font_info) in &resolved {
75        let this_font_id = font_info.as_ref().map(|(id, _)| *id);
76        
77        if this_font_id != current_font_id {
78            if !current_run_text.is_empty() {
79                runs.push((current_run_text.clone(), current_font_id));
80                current_run_text.clear();
81            }
82            current_font_id = this_font_id;
83        }
84        current_run_text.push(*ch);
85    }
86    if !current_run_text.is_empty() {
87        runs.push((current_run_text, current_font_id));
88    }
89    
90    println!("  Font runs:");
91    for (run_text, font_id) in &runs {
92        let font_name = font_id.as_ref()
93            .and_then(|id| cache.get_metadata_by_id(id))
94            .and_then(|m| m.name.clone().or(m.family.clone()))
95            .unwrap_or_else(|| "[NO FONT]".to_string());
96        println!("    '{}' -> {}", run_text, font_name);
97    }
98    
99    // Step 4: Load font data for shaping
100    // In a real application, you'd load font bytes here
101    println!("\n\nStep 4: Loading fonts for shaping");
102    
103    // Collect unique font IDs needed for this text
104    let unique_fonts: std::collections::HashSet<_> = runs.iter()
105        .filter_map(|(_, id)| *id)
106        .collect();
107    
108    println!("  Unique fonts needed: {}", unique_fonts.len());
109    
110    for font_id in &unique_fonts {
111        if let Some(meta) = cache.get_metadata_by_id(font_id) {
112            println!("    - {:?}", meta.name.as_ref().or(meta.family.as_ref()));
113            // Get font path via get_font_by_id
114            if let Some(source) = cache.get_font_by_id(font_id) {
115                match source {
116                    rust_fontconfig::FontSource::Disk(path) => {
117                        // In real code, you'd load the font file here:
118                        // let bytes = std::fs::read(&path.path)?;
119                        // let parsed = ttf_parser::Face::parse(&bytes, path.font_index as u32)?;
120                        println!("      Path: {}", path.path);
121                    }
122                    rust_fontconfig::FontSource::Memory(font) => {
123                        println!("      Memory font (id: {})", font.id);
124                    }
125                }
126            }
127        }
128    }
129    
130    println!("\nWorkflow summary:");
131    println!("");
132    println!("1. FcFontCache::build() - once at startup");
133    println!("2. cache.resolve_font_chain() - per CSS font-family declaration");
134    println!("3. chain.resolve_text(\"abc\") -> [Run {{ font, glyphs: \"abc\" }}] - per text string to shape");
135    println!("4. Load font bytes and shape each run with its font");
136}
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 81)
68fn print_text_resolution(
69    cache: &FcFontCache,
70    chain: &rust_fontconfig::FontFallbackChain,
71    text: &str,
72) {
73    let resolved = chain.resolve_text(cache, text);
74    
75    // Group consecutive characters by font
76    let mut current_font: Option<String> = None;
77    let mut current_segment = String::new();
78    
79    for (ch, font_info) in resolved {
80        let font_name = font_info.map(|(id, _)| {
81            cache.get_metadata_by_id(&id)
82                .and_then(|p| p.name.clone().or(p.family.clone()))
83                .unwrap_or_else(|| format!("{:?}", id))
84        });
85        
86        if font_name != current_font {
87            if !current_segment.is_empty() {
88                println!("  '{}' -> {}", 
89                         current_segment, 
90                         current_font.as_deref().unwrap_or("[NO FONT]"));
91                current_segment.clear();
92            }
93            current_font = font_name;
94        }
95        current_segment.push(ch);
96    }
97    
98    if !current_segment.is_empty() {
99        println!("  '{}' -> {}", 
100                 current_segment, 
101                 current_font.as_deref().unwrap_or("[NO FONT]"));
102    }
103}
More examples
Hide additional examples
examples/getfont.rs (line 37)
4fn main() {
5    let start = Instant::now();
6    let cache = FcFontCache::build();
7    let build_time = start.elapsed();
8
9    println!("✓ Cache built with {} fonts in {:?}", cache.list().len(), build_time);
10    println!();
11
12    // Test various font queries to showcase fuzzy matching
13    let test_queries = vec![
14        ("Arial", FcWeight::Normal, "Common sans-serif font"),
15        ("NotoSansJP", FcWeight::Normal, "Japanese font (fuzzy match)"),
16        ("Helvetica", FcWeight::Bold, "Bold variant"),
17        ("DejaVu Sans", FcWeight::Normal, "Font with spaces"),
18        ("Courier", FcWeight::Normal, "Monospace font"),
19    ];
20
21    for (font_name, weight, description) in test_queries {
22        println!("Searching for: '{}' ({})", font_name, description);
23        
24        let query_start = Instant::now();
25        let result = cache.query(
26            &FcPattern {
27                name: Some(font_name.to_string()),
28                weight,
29                ..Default::default()
30            },
31            &mut Vec::new(),
32        );
33        let query_time = query_start.elapsed();
34
35        match result {
36            Some(font_match) => {
37                if let Some(pattern) = cache.get_metadata_by_id(&font_match.id) {
38                    let name = pattern.name.as_ref().or(pattern.family.as_ref())
39                        .map(|s| s.as_str()).unwrap_or("<unknown>");
40                    println!("  ✓ Found: {} (query time: {:?})", name, query_time);
41                    println!("    - Weight: {:?}", pattern.weight);
42                    println!("    - Unicode ranges: {}", font_match.unicode_ranges.len());
43                    println!("    - Fallbacks: {}", font_match.fallbacks.len());
44                } else {
45                    println!("  ✓ Found font ID: {} (query time: {:?})", font_match.id, query_time);
46                }
47            }
48            None => {
49                println!("  ✗ Not found (query time: {:?})", query_time);
50            }
51        }
52        println!();
53    }
54}
examples/query.rs (line 24)
7fn main() {
8    // Initialize font cache - scans system fonts
9    println!("Building font cache...");
10    let cache = FcFontCache::build();
11    println!("Font cache built with {} fonts\n", cache.list().len());
12    
13    // Example 1: Query by family name
14    println!("=== Query by Family Name ===");
15    let mut trace = Vec::new();
16    let pattern = FcPattern {
17        family: Some("Arial".to_string()),
18        ..Default::default()
19    };
20    
21    if let Some(match_result) = cache.query(&pattern, &mut trace) {
22        println!("Found Arial:");
23        println!("  Font ID: {:?}", match_result.id);
24        if let Some(meta) = cache.get_metadata_by_id(&match_result.id) {
25            println!("  Family: {:?}", meta.family);
26            println!("  Weight: {:?}", meta.weight);
27            println!("  Italic: {:?}", meta.italic);
28        }
29        // To get the path, use get_font_by_id
30        if let Some(source) = cache.get_font_by_id(&match_result.id) {
31            match source {
32                rust_fontconfig::FontSource::Disk(path) => {
33                    println!("  Path: {}", path.path);
34                }
35                rust_fontconfig::FontSource::Memory(font) => {
36                    println!("  Memory font: {}", font.id);
37                }
38            }
39        }
40    } else {
41        println!("Arial not found, trace:");
42        for t in &trace {
43            println!("  {:?}", t);
44        }
45    }
46    
47    // Example 2: Query by generic family
48    println!("\n=== Query Generic 'serif' ===");
49    trace.clear();
50    let pattern = FcPattern {
51        family: Some("serif".to_string()),
52        ..Default::default()
53    };
54    
55    if let Some(match_result) = cache.query(&pattern, &mut trace) {
56        println!("Found serif font:");
57        if let Some(meta) = cache.get_metadata_by_id(&match_result.id) {
58            println!("  Name: {:?}", meta.name.as_ref().or(meta.family.as_ref()));
59        }
60    }
61    
62    // Example 3: Query by style (bold + italic)
63    println!("\n=== Query Bold Italic ===");
64    trace.clear();
65    let pattern = FcPattern {
66        family: Some("sans-serif".to_string()),
67        weight: FcWeight::Bold,
68        italic: PatternMatch::True,
69        ..Default::default()
70    };
71    
72    if let Some(match_result) = cache.query(&pattern, &mut trace) {
73        println!("Found bold italic sans-serif:");
74        if let Some(meta) = cache.get_metadata_by_id(&match_result.id) {
75            println!("  Name: {:?}", meta.name);
76            println!("  Family: {:?}", meta.family);
77            println!("  Weight: {:?}", meta.weight);
78            println!("  Italic: {:?}", meta.italic);
79        }
80    }
81    
82    // Example 4: List all fonts with a specific weight
83    println!("\n=== Listing Bold Fonts ===");
84    let bold_fonts: Vec<_> = cache.list().into_iter()
85        .filter(|(meta, _id)| {
86            matches!(meta.weight, FcWeight::Bold | FcWeight::ExtraBold | FcWeight::Black)
87        })
88        .take(5)
89        .collect();
90    
91    println!("First 5 bold fonts:");
92    for (meta, id) in bold_fonts {
93        println!("  {:?}: {:?}", id, meta.name.as_ref().or(meta.family.as_ref()));
94    }
95    
96    // Example 5: Search by name pattern
97    println!("\n=== Fonts with 'Mono' in name ===");
98    let mono_fonts: Vec<_> = cache.list().into_iter()
99        .filter(|(meta, _id)| {
100            meta.name.as_ref().map(|n| n.contains("Mono")).unwrap_or(false) ||
101            meta.family.as_ref().map(|f| f.contains("Mono")).unwrap_or(false)
102        })
103        .take(5)
104        .collect();
105    
106    println!("First 5 monospace fonts:");
107    for (meta, id) in mono_fonts {
108        println!("  {:?}: {:?}", id, meta.name.as_ref().or(meta.family.as_ref()));
109    }
110}
examples/character_resolution.rs (line 60)
8fn main() {
9    let cache = FcFontCache::build();
10        
11    // Create a font chain with typical web defaults
12    let families = vec![
13        "system-ui".to_string(),
14        "sans-serif".to_string(),
15    ];
16    
17    let mut trace = Vec::new();
18    let chain = cache.resolve_font_chain(
19        &families,
20        FcWeight::Normal,
21        PatternMatch::False,
22        PatternMatch::False,
23        &mut trace,
24    );
25    
26    // Test characters from different Unicode blocks
27    let test_chars = vec![
28        ('A', "Latin Capital Letter A"),
29        ('a', "Latin Small Letter A"),
30        ('0', "Digit Zero"),
31        ('€', "Euro Sign"),
32        ('→', "Rightwards Arrow"),
33        ('中', "CJK Ideograph - China"),
34        ('日', "CJK Ideograph - Sun/Day"),
35        ('あ', "Hiragana Letter A"),
36        ('ア', "Katakana Letter A"),
37        ('한', "Hangul Syllable Han"),
38        ('א', "Hebrew Letter Alef"),
39        ('ا', "Arabic Letter Alef"),
40        ('α', "Greek Small Letter Alpha"),
41        ('Σ', "Greek Capital Letter Sigma"),
42        ('я', "Cyrillic Small Letter Ya"),
43        ('🙂', "Slightly Smiling Face"),
44        ('♠', "Black Spade Suit"),
45        ('∑', "N-ary Summation"),
46        ('∞', "Infinity"),
47        ('℃', "Degree Celsius"),
48    ];
49    
50    println!("Character resolution results:\n");
51    println!("{:<6} {:<30} {:<40}", "Char", "Description", "Font");
52    println!("{}", "-".repeat(80));
53    
54    for (ch, description) in test_chars {
55        let text = ch.to_string();
56        let resolved = chain.resolve_text(&cache, &text);
57        
58        let font_name = resolved.first()
59            .and_then(|(_, info)| info.as_ref())
60            .and_then(|(id, _)| cache.get_metadata_by_id(id))
61            .and_then(|m| m.name.clone().or(m.family.clone()))
62            .unwrap_or_else(|| "⚠ NOT FOUND".to_string());
63        
64        println!("{:<6} {:<30} {}", ch, description, font_name);
65    }
66    
67    // Show how to check if a specific font covers a character
68    println!("\n\nFont Coverage Check\n");
69    
70    let pattern = rust_fontconfig::FcPattern {
71        family: Some("Arial".to_string()),
72        ..Default::default()
73    };
74    
75    if let Some(match_result) = cache.query(&pattern, &mut Vec::new()) {
76        println!("Checking Arial coverage:");
77        
78        // Create a chain just for Arial
79        let arial_chain = cache.resolve_font_chain(
80            &vec!["Arial".to_string()],
81            FcWeight::Normal,
82            PatternMatch::False,
83            PatternMatch::False,
84            &mut Vec::new(),
85        );
86        
87        let check_chars = ['A', '中', '🙂', '→'];
88        for ch in check_chars {
89            let resolved = arial_chain.resolve_text(&cache, &ch.to_string());
90            let found_in_arial = resolved.first()
91                .and_then(|(_, info)| info.as_ref())
92                .map(|(id, _)| id == &match_result.id)
93                .unwrap_or(false);
94            
95            let status = if found_in_arial { "✓" } else { "✗" };
96            println!("  {} '{}' (U+{:04X})", status, ch, ch as u32);
97        }
98    }
99    
100    // Show codepoint ranges supported
101    println!("\n\nUnicode Block Coverage Summary\n");
102    
103    let blocks = [
104        ("Basic Latin", 0x0020..0x007F),
105        ("Latin Extended-A", 0x0100..0x017F),
106        ("Greek", 0x0370..0x03FF),
107        ("Cyrillic", 0x0400..0x04FF),
108        ("Arabic", 0x0600..0x06FF),
109        ("CJK Unified Ideographs", 0x4E00..0x9FFF),
110        ("Hiragana", 0x3040..0x309F),
111        ("Katakana", 0x30A0..0x30FF),
112    ];
113    
114    for (name, range) in blocks {
115        // Sample a few codepoints from each block
116        let sample_points: Vec<char> = range.clone()
117            .step_by(range.len() / 5)
118            .take(5)
119            .filter_map(|cp| char::from_u32(cp))
120            .collect();
121        
122        let sample_text: String = sample_points.iter().collect();
123        let resolved = chain.resolve_text(&cache, &sample_text);
124        
125        let fonts_used: std::collections::HashSet<_> = resolved.iter()
126            .filter_map(|(_, info)| info.as_ref())
127            .map(|(id, _)| id.clone())
128            .collect();
129        
130        let coverage = resolved.iter()
131            .filter(|(_, info)| info.is_some())
132            .count() as f32 / resolved.len() as f32 * 100.0;
133        
134        println!("{:<30} {:>6.1}% coverage ({} fonts)", name, coverage, fonts_used.len());
135    }
136}
examples/integration_api.rs (line 38)
7fn main() {
8    println!("Font Integration API Example\n");
9    
10    // Step 1: Build the font cache once at application startup
11    println!("Step 1: Building font cache...");
12    let cache = FcFontCache::build();
13    println!("  Loaded {} fonts\n", cache.list().len());
14    
15    // Step 2: Simulate CSS font-family resolution
16    // This is what a browser/text renderer would do
17    println!("Step 2: Resolving CSS font-family: 'Helvetica, Arial, sans-serif'\n");
18    
19    let css_families = vec![
20        "Helvetica".to_string(),
21        "Arial".to_string(),
22        "sans-serif".to_string(),
23    ];
24    
25    let mut trace = Vec::new();
26    let chain = cache.resolve_font_chain(
27        &css_families,
28        FcWeight::Normal,
29        PatternMatch::False,  // italic
30        PatternMatch::False,  // oblique
31        &mut trace,
32    );
33    
34    println!("  CSS fallback groups: {}", chain.css_fallbacks.len());
35    for (i, group) in chain.css_fallbacks.iter().enumerate() {
36        println!("    {}: CSS '{}' -> {} fonts", i + 1, group.css_name, group.fonts.len());
37        for font in group.fonts.iter().take(2) {
38            if let Some(meta) = cache.get_metadata_by_id(&font.id) {
39                let name = meta.name.as_ref().or(meta.family.as_ref());
40                println!("       - {:?}", name);
41            }
42        }
43        if group.fonts.len() > 2 {
44            println!("       ... and {} more", group.fonts.len() - 2);
45        }
46    }
47    
48    println!("\n  Unicode fallback fonts: {}", chain.unicode_fallbacks.len());
49    for (i, font) in chain.unicode_fallbacks.iter().take(3).enumerate() {
50        if let Some(meta) = cache.get_metadata_by_id(&font.id) {
51            println!("    {}: {:?}", 
52                     i + 1, 
53                     meta.name.as_ref().or(meta.family.as_ref()));
54        }
55    }
56    if chain.unicode_fallbacks.len() > 3 {
57        println!("    ... and {} more", chain.unicode_fallbacks.len() - 3);
58    }
59    
60    // Step 3: Resolve text to fonts
61    // This maps each character to a specific font
62    println!("\n\nStep 3: Resolve text to fonts");
63    
64    let text = "Hello 世界! Привет мир";
65    println!("  Input text: '{}'\n", text);
66    
67    let resolved = chain.resolve_text(&cache, text);
68    
69    // Group by runs of same font
70    let mut runs = Vec::new();
71    let mut current_run_text = String::new();
72    let mut current_font_id: Option<FontId> = None;
73    
74    for (ch, font_info) in &resolved {
75        let this_font_id = font_info.as_ref().map(|(id, _)| *id);
76        
77        if this_font_id != current_font_id {
78            if !current_run_text.is_empty() {
79                runs.push((current_run_text.clone(), current_font_id));
80                current_run_text.clear();
81            }
82            current_font_id = this_font_id;
83        }
84        current_run_text.push(*ch);
85    }
86    if !current_run_text.is_empty() {
87        runs.push((current_run_text, current_font_id));
88    }
89    
90    println!("  Font runs:");
91    for (run_text, font_id) in &runs {
92        let font_name = font_id.as_ref()
93            .and_then(|id| cache.get_metadata_by_id(id))
94            .and_then(|m| m.name.clone().or(m.family.clone()))
95            .unwrap_or_else(|| "[NO FONT]".to_string());
96        println!("    '{}' -> {}", run_text, font_name);
97    }
98    
99    // Step 4: Load font data for shaping
100    // In a real application, you'd load font bytes here
101    println!("\n\nStep 4: Loading fonts for shaping");
102    
103    // Collect unique font IDs needed for this text
104    let unique_fonts: std::collections::HashSet<_> = runs.iter()
105        .filter_map(|(_, id)| *id)
106        .collect();
107    
108    println!("  Unique fonts needed: {}", unique_fonts.len());
109    
110    for font_id in &unique_fonts {
111        if let Some(meta) = cache.get_metadata_by_id(font_id) {
112            println!("    - {:?}", meta.name.as_ref().or(meta.family.as_ref()));
113            // Get font path via get_font_by_id
114            if let Some(source) = cache.get_font_by_id(font_id) {
115                match source {
116                    rust_fontconfig::FontSource::Disk(path) => {
117                        // In real code, you'd load the font file here:
118                        // let bytes = std::fs::read(&path.path)?;
119                        // let parsed = ttf_parser::Face::parse(&bytes, path.font_index as u32)?;
120                        println!("      Path: {}", path.path);
121                    }
122                    rust_fontconfig::FontSource::Memory(font) => {
123                        println!("      Memory font (id: {})", font.id);
124                    }
125                }
126            }
127        }
128    }
129    
130    println!("\nWorkflow summary:");
131    println!("");
132    println!("1. FcFontCache::build() - once at startup");
133    println!("2. cache.resolve_font_chain() - per CSS font-family declaration");
134    println!("3. chain.resolve_text(\"abc\") -> [Run {{ font, glyphs: \"abc\" }}] - per text string to shape");
135    println!("4. Load font bytes and shape each run with its font");
136}
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/getfont.rs (line 6)
4fn main() {
5    let start = Instant::now();
6    let cache = FcFontCache::build();
7    let build_time = start.elapsed();
8
9    println!("✓ Cache built with {} fonts in {:?}", cache.list().len(), build_time);
10    println!();
11
12    // Test various font queries to showcase fuzzy matching
13    let test_queries = vec![
14        ("Arial", FcWeight::Normal, "Common sans-serif font"),
15        ("NotoSansJP", FcWeight::Normal, "Japanese font (fuzzy match)"),
16        ("Helvetica", FcWeight::Bold, "Bold variant"),
17        ("DejaVu Sans", FcWeight::Normal, "Font with spaces"),
18        ("Courier", FcWeight::Normal, "Monospace font"),
19    ];
20
21    for (font_name, weight, description) in test_queries {
22        println!("Searching for: '{}' ({})", font_name, description);
23        
24        let query_start = Instant::now();
25        let result = cache.query(
26            &FcPattern {
27                name: Some(font_name.to_string()),
28                weight,
29                ..Default::default()
30            },
31            &mut Vec::new(),
32        );
33        let query_time = query_start.elapsed();
34
35        match result {
36            Some(font_match) => {
37                if let Some(pattern) = cache.get_metadata_by_id(&font_match.id) {
38                    let name = pattern.name.as_ref().or(pattern.family.as_ref())
39                        .map(|s| s.as_str()).unwrap_or("<unknown>");
40                    println!("  ✓ Found: {} (query time: {:?})", name, query_time);
41                    println!("    - Weight: {:?}", pattern.weight);
42                    println!("    - Unicode ranges: {}", font_match.unicode_ranges.len());
43                    println!("    - Fallbacks: {}", font_match.fallbacks.len());
44                } else {
45                    println!("  ✓ Found font ID: {} (query time: {:?})", font_match.id, query_time);
46                }
47            }
48            None => {
49                println!("  ✗ Not found (query time: {:?})", query_time);
50            }
51        }
52        println!();
53    }
54}
More examples
Hide additional examples
examples/unicode_aware_fonts.rs (line 9)
7fn main() {
8    // Initialize font cache
9    let cache = FcFontCache::build();
10    
11    println!("=== Unicode-Aware Font Selection ===\n");
12    
13    // Step 1: Create a font chain for sans-serif fonts
14    println!("Step 1: Resolve font chain for 'sans-serif'");
15    let mut trace = Vec::new();
16    let chain = cache.resolve_font_chain(
17        &vec!["sans-serif".to_string()],
18        FcWeight::Normal,
19        PatternMatch::False,  // italic
20        PatternMatch::False,  // oblique
21        &mut trace,
22    );
23    
24    println!("  Font chain has {} CSS fallbacks and {} unicode fallbacks\n", 
25             chain.css_fallbacks.len(),
26             chain.unicode_fallbacks.len());
27    
28    // Step 2: Resolve different texts against the chain
29    println!("Step 2: Resolve various texts against the font chain\n");
30    
31    // Latin text
32    let latin_text = "Hello World";
33    println!("Latin text: '{}'", latin_text);
34    print_text_resolution(&cache, &chain, latin_text);
35    
36    // CJK (Chinese) text
37    let cjk_text = "你好世界";
38    println!("\nCJK text: '{}'", cjk_text);
39    print_text_resolution(&cache, &chain, cjk_text);
40    
41    // Japanese text
42    let japanese_text = "こんにちは世界";
43    println!("\nJapanese text: '{}'", japanese_text);
44    print_text_resolution(&cache, &chain, japanese_text);
45    
46    // Arabic text
47    let arabic_text = "مرحبا بالعالم";
48    println!("\nArabic text: '{}'", arabic_text);
49    print_text_resolution(&cache, &chain, arabic_text);
50    
51    // Cyrillic text
52    let cyrillic_text = "Привет мир";
53    println!("\nCyrillic text: '{}'", cyrillic_text);
54    print_text_resolution(&cache, &chain, cyrillic_text);
55    
56    // Mixed text
57    let mixed_text = "Hello 世界 Привет";
58    println!("\nMixed text: '{}'", mixed_text);
59    print_text_resolution(&cache, &chain, mixed_text);
60    
61    println!("\n=== Summary ===");
62    println!("The workflow is:");
63    println!("1. resolve_font_chain() - creates a fallback chain from CSS font-family");
64    println!("2. chain.resolve_text() - maps each character to a font in the chain");
65    println!("3. Use the font IDs to load and render glyphs");
66}
examples/query.rs (line 10)
7fn main() {
8    // Initialize font cache - scans system fonts
9    println!("Building font cache...");
10    let cache = FcFontCache::build();
11    println!("Font cache built with {} fonts\n", cache.list().len());
12    
13    // Example 1: Query by family name
14    println!("=== Query by Family Name ===");
15    let mut trace = Vec::new();
16    let pattern = FcPattern {
17        family: Some("Arial".to_string()),
18        ..Default::default()
19    };
20    
21    if let Some(match_result) = cache.query(&pattern, &mut trace) {
22        println!("Found Arial:");
23        println!("  Font ID: {:?}", match_result.id);
24        if let Some(meta) = cache.get_metadata_by_id(&match_result.id) {
25            println!("  Family: {:?}", meta.family);
26            println!("  Weight: {:?}", meta.weight);
27            println!("  Italic: {:?}", meta.italic);
28        }
29        // To get the path, use get_font_by_id
30        if let Some(source) = cache.get_font_by_id(&match_result.id) {
31            match source {
32                rust_fontconfig::FontSource::Disk(path) => {
33                    println!("  Path: {}", path.path);
34                }
35                rust_fontconfig::FontSource::Memory(font) => {
36                    println!("  Memory font: {}", font.id);
37                }
38            }
39        }
40    } else {
41        println!("Arial not found, trace:");
42        for t in &trace {
43            println!("  {:?}", t);
44        }
45    }
46    
47    // Example 2: Query by generic family
48    println!("\n=== Query Generic 'serif' ===");
49    trace.clear();
50    let pattern = FcPattern {
51        family: Some("serif".to_string()),
52        ..Default::default()
53    };
54    
55    if let Some(match_result) = cache.query(&pattern, &mut trace) {
56        println!("Found serif font:");
57        if let Some(meta) = cache.get_metadata_by_id(&match_result.id) {
58            println!("  Name: {:?}", meta.name.as_ref().or(meta.family.as_ref()));
59        }
60    }
61    
62    // Example 3: Query by style (bold + italic)
63    println!("\n=== Query Bold Italic ===");
64    trace.clear();
65    let pattern = FcPattern {
66        family: Some("sans-serif".to_string()),
67        weight: FcWeight::Bold,
68        italic: PatternMatch::True,
69        ..Default::default()
70    };
71    
72    if let Some(match_result) = cache.query(&pattern, &mut trace) {
73        println!("Found bold italic sans-serif:");
74        if let Some(meta) = cache.get_metadata_by_id(&match_result.id) {
75            println!("  Name: {:?}", meta.name);
76            println!("  Family: {:?}", meta.family);
77            println!("  Weight: {:?}", meta.weight);
78            println!("  Italic: {:?}", meta.italic);
79        }
80    }
81    
82    // Example 4: List all fonts with a specific weight
83    println!("\n=== Listing Bold Fonts ===");
84    let bold_fonts: Vec<_> = cache.list().into_iter()
85        .filter(|(meta, _id)| {
86            matches!(meta.weight, FcWeight::Bold | FcWeight::ExtraBold | FcWeight::Black)
87        })
88        .take(5)
89        .collect();
90    
91    println!("First 5 bold fonts:");
92    for (meta, id) in bold_fonts {
93        println!("  {:?}: {:?}", id, meta.name.as_ref().or(meta.family.as_ref()));
94    }
95    
96    // Example 5: Search by name pattern
97    println!("\n=== Fonts with 'Mono' in name ===");
98    let mono_fonts: Vec<_> = cache.list().into_iter()
99        .filter(|(meta, _id)| {
100            meta.name.as_ref().map(|n| n.contains("Mono")).unwrap_or(false) ||
101            meta.family.as_ref().map(|f| f.contains("Mono")).unwrap_or(false)
102        })
103        .take(5)
104        .collect();
105    
106    println!("First 5 monospace fonts:");
107    for (meta, id) in mono_fonts {
108        println!("  {:?}: {:?}", id, meta.name.as_ref().or(meta.family.as_ref()));
109    }
110}
examples/character_resolution.rs (line 9)
8fn main() {
9    let cache = FcFontCache::build();
10        
11    // Create a font chain with typical web defaults
12    let families = vec![
13        "system-ui".to_string(),
14        "sans-serif".to_string(),
15    ];
16    
17    let mut trace = Vec::new();
18    let chain = cache.resolve_font_chain(
19        &families,
20        FcWeight::Normal,
21        PatternMatch::False,
22        PatternMatch::False,
23        &mut trace,
24    );
25    
26    // Test characters from different Unicode blocks
27    let test_chars = vec![
28        ('A', "Latin Capital Letter A"),
29        ('a', "Latin Small Letter A"),
30        ('0', "Digit Zero"),
31        ('€', "Euro Sign"),
32        ('→', "Rightwards Arrow"),
33        ('中', "CJK Ideograph - China"),
34        ('日', "CJK Ideograph - Sun/Day"),
35        ('あ', "Hiragana Letter A"),
36        ('ア', "Katakana Letter A"),
37        ('한', "Hangul Syllable Han"),
38        ('א', "Hebrew Letter Alef"),
39        ('ا', "Arabic Letter Alef"),
40        ('α', "Greek Small Letter Alpha"),
41        ('Σ', "Greek Capital Letter Sigma"),
42        ('я', "Cyrillic Small Letter Ya"),
43        ('🙂', "Slightly Smiling Face"),
44        ('♠', "Black Spade Suit"),
45        ('∑', "N-ary Summation"),
46        ('∞', "Infinity"),
47        ('℃', "Degree Celsius"),
48    ];
49    
50    println!("Character resolution results:\n");
51    println!("{:<6} {:<30} {:<40}", "Char", "Description", "Font");
52    println!("{}", "-".repeat(80));
53    
54    for (ch, description) in test_chars {
55        let text = ch.to_string();
56        let resolved = chain.resolve_text(&cache, &text);
57        
58        let font_name = resolved.first()
59            .and_then(|(_, info)| info.as_ref())
60            .and_then(|(id, _)| cache.get_metadata_by_id(id))
61            .and_then(|m| m.name.clone().or(m.family.clone()))
62            .unwrap_or_else(|| "⚠ NOT FOUND".to_string());
63        
64        println!("{:<6} {:<30} {}", ch, description, font_name);
65    }
66    
67    // Show how to check if a specific font covers a character
68    println!("\n\nFont Coverage Check\n");
69    
70    let pattern = rust_fontconfig::FcPattern {
71        family: Some("Arial".to_string()),
72        ..Default::default()
73    };
74    
75    if let Some(match_result) = cache.query(&pattern, &mut Vec::new()) {
76        println!("Checking Arial coverage:");
77        
78        // Create a chain just for Arial
79        let arial_chain = cache.resolve_font_chain(
80            &vec!["Arial".to_string()],
81            FcWeight::Normal,
82            PatternMatch::False,
83            PatternMatch::False,
84            &mut Vec::new(),
85        );
86        
87        let check_chars = ['A', '中', '🙂', '→'];
88        for ch in check_chars {
89            let resolved = arial_chain.resolve_text(&cache, &ch.to_string());
90            let found_in_arial = resolved.first()
91                .and_then(|(_, info)| info.as_ref())
92                .map(|(id, _)| id == &match_result.id)
93                .unwrap_or(false);
94            
95            let status = if found_in_arial { "✓" } else { "✗" };
96            println!("  {} '{}' (U+{:04X})", status, ch, ch as u32);
97        }
98    }
99    
100    // Show codepoint ranges supported
101    println!("\n\nUnicode Block Coverage Summary\n");
102    
103    let blocks = [
104        ("Basic Latin", 0x0020..0x007F),
105        ("Latin Extended-A", 0x0100..0x017F),
106        ("Greek", 0x0370..0x03FF),
107        ("Cyrillic", 0x0400..0x04FF),
108        ("Arabic", 0x0600..0x06FF),
109        ("CJK Unified Ideographs", 0x4E00..0x9FFF),
110        ("Hiragana", 0x3040..0x309F),
111        ("Katakana", 0x30A0..0x30FF),
112    ];
113    
114    for (name, range) in blocks {
115        // Sample a few codepoints from each block
116        let sample_points: Vec<char> = range.clone()
117            .step_by(range.len() / 5)
118            .take(5)
119            .filter_map(|cp| char::from_u32(cp))
120            .collect();
121        
122        let sample_text: String = sample_points.iter().collect();
123        let resolved = chain.resolve_text(&cache, &sample_text);
124        
125        let fonts_used: std::collections::HashSet<_> = resolved.iter()
126            .filter_map(|(_, info)| info.as_ref())
127            .map(|(id, _)| id.clone())
128            .collect();
129        
130        let coverage = resolved.iter()
131            .filter(|(_, info)| info.is_some())
132            .count() as f32 / resolved.len() as f32 * 100.0;
133        
134        println!("{:<30} {:>6.1}% coverage ({} fonts)", name, coverage, fonts_used.len());
135    }
136}
examples/integration_api.rs (line 12)
7fn main() {
8    println!("Font Integration API Example\n");
9    
10    // Step 1: Build the font cache once at application startup
11    println!("Step 1: Building font cache...");
12    let cache = FcFontCache::build();
13    println!("  Loaded {} fonts\n", cache.list().len());
14    
15    // Step 2: Simulate CSS font-family resolution
16    // This is what a browser/text renderer would do
17    println!("Step 2: Resolving CSS font-family: 'Helvetica, Arial, sans-serif'\n");
18    
19    let css_families = vec![
20        "Helvetica".to_string(),
21        "Arial".to_string(),
22        "sans-serif".to_string(),
23    ];
24    
25    let mut trace = Vec::new();
26    let chain = cache.resolve_font_chain(
27        &css_families,
28        FcWeight::Normal,
29        PatternMatch::False,  // italic
30        PatternMatch::False,  // oblique
31        &mut trace,
32    );
33    
34    println!("  CSS fallback groups: {}", chain.css_fallbacks.len());
35    for (i, group) in chain.css_fallbacks.iter().enumerate() {
36        println!("    {}: CSS '{}' -> {} fonts", i + 1, group.css_name, group.fonts.len());
37        for font in group.fonts.iter().take(2) {
38            if let Some(meta) = cache.get_metadata_by_id(&font.id) {
39                let name = meta.name.as_ref().or(meta.family.as_ref());
40                println!("       - {:?}", name);
41            }
42        }
43        if group.fonts.len() > 2 {
44            println!("       ... and {} more", group.fonts.len() - 2);
45        }
46    }
47    
48    println!("\n  Unicode fallback fonts: {}", chain.unicode_fallbacks.len());
49    for (i, font) in chain.unicode_fallbacks.iter().take(3).enumerate() {
50        if let Some(meta) = cache.get_metadata_by_id(&font.id) {
51            println!("    {}: {:?}", 
52                     i + 1, 
53                     meta.name.as_ref().or(meta.family.as_ref()));
54        }
55    }
56    if chain.unicode_fallbacks.len() > 3 {
57        println!("    ... and {} more", chain.unicode_fallbacks.len() - 3);
58    }
59    
60    // Step 3: Resolve text to fonts
61    // This maps each character to a specific font
62    println!("\n\nStep 3: Resolve text to fonts");
63    
64    let text = "Hello 世界! Привет мир";
65    println!("  Input text: '{}'\n", text);
66    
67    let resolved = chain.resolve_text(&cache, text);
68    
69    // Group by runs of same font
70    let mut runs = Vec::new();
71    let mut current_run_text = String::new();
72    let mut current_font_id: Option<FontId> = None;
73    
74    for (ch, font_info) in &resolved {
75        let this_font_id = font_info.as_ref().map(|(id, _)| *id);
76        
77        if this_font_id != current_font_id {
78            if !current_run_text.is_empty() {
79                runs.push((current_run_text.clone(), current_font_id));
80                current_run_text.clear();
81            }
82            current_font_id = this_font_id;
83        }
84        current_run_text.push(*ch);
85    }
86    if !current_run_text.is_empty() {
87        runs.push((current_run_text, current_font_id));
88    }
89    
90    println!("  Font runs:");
91    for (run_text, font_id) in &runs {
92        let font_name = font_id.as_ref()
93            .and_then(|id| cache.get_metadata_by_id(id))
94            .and_then(|m| m.name.clone().or(m.family.clone()))
95            .unwrap_or_else(|| "[NO FONT]".to_string());
96        println!("    '{}' -> {}", run_text, font_name);
97    }
98    
99    // Step 4: Load font data for shaping
100    // In a real application, you'd load font bytes here
101    println!("\n\nStep 4: Loading fonts for shaping");
102    
103    // Collect unique font IDs needed for this text
104    let unique_fonts: std::collections::HashSet<_> = runs.iter()
105        .filter_map(|(_, id)| *id)
106        .collect();
107    
108    println!("  Unique fonts needed: {}", unique_fonts.len());
109    
110    for font_id in &unique_fonts {
111        if let Some(meta) = cache.get_metadata_by_id(font_id) {
112            println!("    - {:?}", meta.name.as_ref().or(meta.family.as_ref()));
113            // Get font path via get_font_by_id
114            if let Some(source) = cache.get_font_by_id(font_id) {
115                match source {
116                    rust_fontconfig::FontSource::Disk(path) => {
117                        // In real code, you'd load the font file here:
118                        // let bytes = std::fs::read(&path.path)?;
119                        // let parsed = ttf_parser::Face::parse(&bytes, path.font_index as u32)?;
120                        println!("      Path: {}", path.path);
121                    }
122                    rust_fontconfig::FontSource::Memory(font) => {
123                        println!("      Memory font (id: {})", font.id);
124                    }
125                }
126            }
127        }
128    }
129    
130    println!("\nWorkflow summary:");
131    println!("");
132    println!("1. FcFontCache::build() - once at startup");
133    println!("2. cache.resolve_font_chain() - per CSS font-family declaration");
134    println!("3. chain.resolve_text(\"abc\") -> [Run {{ font, glyphs: \"abc\" }}] - per text string to shape");
135    println!("4. Load font bytes and shape each run with its font");
136}
Source

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

Returns the list of fonts and font patterns

Examples found in repository?
examples/getfont.rs (line 9)
4fn main() {
5    let start = Instant::now();
6    let cache = FcFontCache::build();
7    let build_time = start.elapsed();
8
9    println!("✓ Cache built with {} fonts in {:?}", cache.list().len(), build_time);
10    println!();
11
12    // Test various font queries to showcase fuzzy matching
13    let test_queries = vec![
14        ("Arial", FcWeight::Normal, "Common sans-serif font"),
15        ("NotoSansJP", FcWeight::Normal, "Japanese font (fuzzy match)"),
16        ("Helvetica", FcWeight::Bold, "Bold variant"),
17        ("DejaVu Sans", FcWeight::Normal, "Font with spaces"),
18        ("Courier", FcWeight::Normal, "Monospace font"),
19    ];
20
21    for (font_name, weight, description) in test_queries {
22        println!("Searching for: '{}' ({})", font_name, description);
23        
24        let query_start = Instant::now();
25        let result = cache.query(
26            &FcPattern {
27                name: Some(font_name.to_string()),
28                weight,
29                ..Default::default()
30            },
31            &mut Vec::new(),
32        );
33        let query_time = query_start.elapsed();
34
35        match result {
36            Some(font_match) => {
37                if let Some(pattern) = cache.get_metadata_by_id(&font_match.id) {
38                    let name = pattern.name.as_ref().or(pattern.family.as_ref())
39                        .map(|s| s.as_str()).unwrap_or("<unknown>");
40                    println!("  ✓ Found: {} (query time: {:?})", name, query_time);
41                    println!("    - Weight: {:?}", pattern.weight);
42                    println!("    - Unicode ranges: {}", font_match.unicode_ranges.len());
43                    println!("    - Fallbacks: {}", font_match.fallbacks.len());
44                } else {
45                    println!("  ✓ Found font ID: {} (query time: {:?})", font_match.id, query_time);
46                }
47            }
48            None => {
49                println!("  ✗ Not found (query time: {:?})", query_time);
50            }
51        }
52        println!();
53    }
54}
More examples
Hide additional examples
examples/query.rs (line 11)
7fn main() {
8    // Initialize font cache - scans system fonts
9    println!("Building font cache...");
10    let cache = FcFontCache::build();
11    println!("Font cache built with {} fonts\n", cache.list().len());
12    
13    // Example 1: Query by family name
14    println!("=== Query by Family Name ===");
15    let mut trace = Vec::new();
16    let pattern = FcPattern {
17        family: Some("Arial".to_string()),
18        ..Default::default()
19    };
20    
21    if let Some(match_result) = cache.query(&pattern, &mut trace) {
22        println!("Found Arial:");
23        println!("  Font ID: {:?}", match_result.id);
24        if let Some(meta) = cache.get_metadata_by_id(&match_result.id) {
25            println!("  Family: {:?}", meta.family);
26            println!("  Weight: {:?}", meta.weight);
27            println!("  Italic: {:?}", meta.italic);
28        }
29        // To get the path, use get_font_by_id
30        if let Some(source) = cache.get_font_by_id(&match_result.id) {
31            match source {
32                rust_fontconfig::FontSource::Disk(path) => {
33                    println!("  Path: {}", path.path);
34                }
35                rust_fontconfig::FontSource::Memory(font) => {
36                    println!("  Memory font: {}", font.id);
37                }
38            }
39        }
40    } else {
41        println!("Arial not found, trace:");
42        for t in &trace {
43            println!("  {:?}", t);
44        }
45    }
46    
47    // Example 2: Query by generic family
48    println!("\n=== Query Generic 'serif' ===");
49    trace.clear();
50    let pattern = FcPattern {
51        family: Some("serif".to_string()),
52        ..Default::default()
53    };
54    
55    if let Some(match_result) = cache.query(&pattern, &mut trace) {
56        println!("Found serif font:");
57        if let Some(meta) = cache.get_metadata_by_id(&match_result.id) {
58            println!("  Name: {:?}", meta.name.as_ref().or(meta.family.as_ref()));
59        }
60    }
61    
62    // Example 3: Query by style (bold + italic)
63    println!("\n=== Query Bold Italic ===");
64    trace.clear();
65    let pattern = FcPattern {
66        family: Some("sans-serif".to_string()),
67        weight: FcWeight::Bold,
68        italic: PatternMatch::True,
69        ..Default::default()
70    };
71    
72    if let Some(match_result) = cache.query(&pattern, &mut trace) {
73        println!("Found bold italic sans-serif:");
74        if let Some(meta) = cache.get_metadata_by_id(&match_result.id) {
75            println!("  Name: {:?}", meta.name);
76            println!("  Family: {:?}", meta.family);
77            println!("  Weight: {:?}", meta.weight);
78            println!("  Italic: {:?}", meta.italic);
79        }
80    }
81    
82    // Example 4: List all fonts with a specific weight
83    println!("\n=== Listing Bold Fonts ===");
84    let bold_fonts: Vec<_> = cache.list().into_iter()
85        .filter(|(meta, _id)| {
86            matches!(meta.weight, FcWeight::Bold | FcWeight::ExtraBold | FcWeight::Black)
87        })
88        .take(5)
89        .collect();
90    
91    println!("First 5 bold fonts:");
92    for (meta, id) in bold_fonts {
93        println!("  {:?}: {:?}", id, meta.name.as_ref().or(meta.family.as_ref()));
94    }
95    
96    // Example 5: Search by name pattern
97    println!("\n=== Fonts with 'Mono' in name ===");
98    let mono_fonts: Vec<_> = cache.list().into_iter()
99        .filter(|(meta, _id)| {
100            meta.name.as_ref().map(|n| n.contains("Mono")).unwrap_or(false) ||
101            meta.family.as_ref().map(|f| f.contains("Mono")).unwrap_or(false)
102        })
103        .take(5)
104        .collect();
105    
106    println!("First 5 monospace fonts:");
107    for (meta, id) in mono_fonts {
108        println!("  {:?}: {:?}", id, meta.name.as_ref().or(meta.family.as_ref()));
109    }
110}
examples/integration_api.rs (line 13)
7fn main() {
8    println!("Font Integration API Example\n");
9    
10    // Step 1: Build the font cache once at application startup
11    println!("Step 1: Building font cache...");
12    let cache = FcFontCache::build();
13    println!("  Loaded {} fonts\n", cache.list().len());
14    
15    // Step 2: Simulate CSS font-family resolution
16    // This is what a browser/text renderer would do
17    println!("Step 2: Resolving CSS font-family: 'Helvetica, Arial, sans-serif'\n");
18    
19    let css_families = vec![
20        "Helvetica".to_string(),
21        "Arial".to_string(),
22        "sans-serif".to_string(),
23    ];
24    
25    let mut trace = Vec::new();
26    let chain = cache.resolve_font_chain(
27        &css_families,
28        FcWeight::Normal,
29        PatternMatch::False,  // italic
30        PatternMatch::False,  // oblique
31        &mut trace,
32    );
33    
34    println!("  CSS fallback groups: {}", chain.css_fallbacks.len());
35    for (i, group) in chain.css_fallbacks.iter().enumerate() {
36        println!("    {}: CSS '{}' -> {} fonts", i + 1, group.css_name, group.fonts.len());
37        for font in group.fonts.iter().take(2) {
38            if let Some(meta) = cache.get_metadata_by_id(&font.id) {
39                let name = meta.name.as_ref().or(meta.family.as_ref());
40                println!("       - {:?}", name);
41            }
42        }
43        if group.fonts.len() > 2 {
44            println!("       ... and {} more", group.fonts.len() - 2);
45        }
46    }
47    
48    println!("\n  Unicode fallback fonts: {}", chain.unicode_fallbacks.len());
49    for (i, font) in chain.unicode_fallbacks.iter().take(3).enumerate() {
50        if let Some(meta) = cache.get_metadata_by_id(&font.id) {
51            println!("    {}: {:?}", 
52                     i + 1, 
53                     meta.name.as_ref().or(meta.family.as_ref()));
54        }
55    }
56    if chain.unicode_fallbacks.len() > 3 {
57        println!("    ... and {} more", chain.unicode_fallbacks.len() - 3);
58    }
59    
60    // Step 3: Resolve text to fonts
61    // This maps each character to a specific font
62    println!("\n\nStep 3: Resolve text to fonts");
63    
64    let text = "Hello 世界! Привет мир";
65    println!("  Input text: '{}'\n", text);
66    
67    let resolved = chain.resolve_text(&cache, text);
68    
69    // Group by runs of same font
70    let mut runs = Vec::new();
71    let mut current_run_text = String::new();
72    let mut current_font_id: Option<FontId> = None;
73    
74    for (ch, font_info) in &resolved {
75        let this_font_id = font_info.as_ref().map(|(id, _)| *id);
76        
77        if this_font_id != current_font_id {
78            if !current_run_text.is_empty() {
79                runs.push((current_run_text.clone(), current_font_id));
80                current_run_text.clear();
81            }
82            current_font_id = this_font_id;
83        }
84        current_run_text.push(*ch);
85    }
86    if !current_run_text.is_empty() {
87        runs.push((current_run_text, current_font_id));
88    }
89    
90    println!("  Font runs:");
91    for (run_text, font_id) in &runs {
92        let font_name = font_id.as_ref()
93            .and_then(|id| cache.get_metadata_by_id(id))
94            .and_then(|m| m.name.clone().or(m.family.clone()))
95            .unwrap_or_else(|| "[NO FONT]".to_string());
96        println!("    '{}' -> {}", run_text, font_name);
97    }
98    
99    // Step 4: Load font data for shaping
100    // In a real application, you'd load font bytes here
101    println!("\n\nStep 4: Loading fonts for shaping");
102    
103    // Collect unique font IDs needed for this text
104    let unique_fonts: std::collections::HashSet<_> = runs.iter()
105        .filter_map(|(_, id)| *id)
106        .collect();
107    
108    println!("  Unique fonts needed: {}", unique_fonts.len());
109    
110    for font_id in &unique_fonts {
111        if let Some(meta) = cache.get_metadata_by_id(font_id) {
112            println!("    - {:?}", meta.name.as_ref().or(meta.family.as_ref()));
113            // Get font path via get_font_by_id
114            if let Some(source) = cache.get_font_by_id(font_id) {
115                match source {
116                    rust_fontconfig::FontSource::Disk(path) => {
117                        // In real code, you'd load the font file here:
118                        // let bytes = std::fs::read(&path.path)?;
119                        // let parsed = ttf_parser::Face::parse(&bytes, path.font_index as u32)?;
120                        println!("      Path: {}", path.path);
121                    }
122                    rust_fontconfig::FontSource::Memory(font) => {
123                        println!("      Memory font (id: {})", font.id);
124                    }
125                }
126            }
127        }
128    }
129    
130    println!("\nWorkflow summary:");
131    println!("");
132    println!("1. FcFontCache::build() - once at startup");
133    println!("2. cache.resolve_font_chain() - per CSS font-family declaration");
134    println!("3. chain.resolve_text(\"abc\") -> [Run {{ font, glyphs: \"abc\" }}] - per text string to shape");
135    println!("4. Load font bytes and shape each run with its font");
136}
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)

Examples found in repository?
examples/getfont.rs (lines 25-32)
4fn main() {
5    let start = Instant::now();
6    let cache = FcFontCache::build();
7    let build_time = start.elapsed();
8
9    println!("✓ Cache built with {} fonts in {:?}", cache.list().len(), build_time);
10    println!();
11
12    // Test various font queries to showcase fuzzy matching
13    let test_queries = vec![
14        ("Arial", FcWeight::Normal, "Common sans-serif font"),
15        ("NotoSansJP", FcWeight::Normal, "Japanese font (fuzzy match)"),
16        ("Helvetica", FcWeight::Bold, "Bold variant"),
17        ("DejaVu Sans", FcWeight::Normal, "Font with spaces"),
18        ("Courier", FcWeight::Normal, "Monospace font"),
19    ];
20
21    for (font_name, weight, description) in test_queries {
22        println!("Searching for: '{}' ({})", font_name, description);
23        
24        let query_start = Instant::now();
25        let result = cache.query(
26            &FcPattern {
27                name: Some(font_name.to_string()),
28                weight,
29                ..Default::default()
30            },
31            &mut Vec::new(),
32        );
33        let query_time = query_start.elapsed();
34
35        match result {
36            Some(font_match) => {
37                if let Some(pattern) = cache.get_metadata_by_id(&font_match.id) {
38                    let name = pattern.name.as_ref().or(pattern.family.as_ref())
39                        .map(|s| s.as_str()).unwrap_or("<unknown>");
40                    println!("  ✓ Found: {} (query time: {:?})", name, query_time);
41                    println!("    - Weight: {:?}", pattern.weight);
42                    println!("    - Unicode ranges: {}", font_match.unicode_ranges.len());
43                    println!("    - Fallbacks: {}", font_match.fallbacks.len());
44                } else {
45                    println!("  ✓ Found font ID: {} (query time: {:?})", font_match.id, query_time);
46                }
47            }
48            None => {
49                println!("  ✗ Not found (query time: {:?})", query_time);
50            }
51        }
52        println!();
53    }
54}
More examples
Hide additional examples
examples/query.rs (line 21)
7fn main() {
8    // Initialize font cache - scans system fonts
9    println!("Building font cache...");
10    let cache = FcFontCache::build();
11    println!("Font cache built with {} fonts\n", cache.list().len());
12    
13    // Example 1: Query by family name
14    println!("=== Query by Family Name ===");
15    let mut trace = Vec::new();
16    let pattern = FcPattern {
17        family: Some("Arial".to_string()),
18        ..Default::default()
19    };
20    
21    if let Some(match_result) = cache.query(&pattern, &mut trace) {
22        println!("Found Arial:");
23        println!("  Font ID: {:?}", match_result.id);
24        if let Some(meta) = cache.get_metadata_by_id(&match_result.id) {
25            println!("  Family: {:?}", meta.family);
26            println!("  Weight: {:?}", meta.weight);
27            println!("  Italic: {:?}", meta.italic);
28        }
29        // To get the path, use get_font_by_id
30        if let Some(source) = cache.get_font_by_id(&match_result.id) {
31            match source {
32                rust_fontconfig::FontSource::Disk(path) => {
33                    println!("  Path: {}", path.path);
34                }
35                rust_fontconfig::FontSource::Memory(font) => {
36                    println!("  Memory font: {}", font.id);
37                }
38            }
39        }
40    } else {
41        println!("Arial not found, trace:");
42        for t in &trace {
43            println!("  {:?}", t);
44        }
45    }
46    
47    // Example 2: Query by generic family
48    println!("\n=== Query Generic 'serif' ===");
49    trace.clear();
50    let pattern = FcPattern {
51        family: Some("serif".to_string()),
52        ..Default::default()
53    };
54    
55    if let Some(match_result) = cache.query(&pattern, &mut trace) {
56        println!("Found serif font:");
57        if let Some(meta) = cache.get_metadata_by_id(&match_result.id) {
58            println!("  Name: {:?}", meta.name.as_ref().or(meta.family.as_ref()));
59        }
60    }
61    
62    // Example 3: Query by style (bold + italic)
63    println!("\n=== Query Bold Italic ===");
64    trace.clear();
65    let pattern = FcPattern {
66        family: Some("sans-serif".to_string()),
67        weight: FcWeight::Bold,
68        italic: PatternMatch::True,
69        ..Default::default()
70    };
71    
72    if let Some(match_result) = cache.query(&pattern, &mut trace) {
73        println!("Found bold italic sans-serif:");
74        if let Some(meta) = cache.get_metadata_by_id(&match_result.id) {
75            println!("  Name: {:?}", meta.name);
76            println!("  Family: {:?}", meta.family);
77            println!("  Weight: {:?}", meta.weight);
78            println!("  Italic: {:?}", meta.italic);
79        }
80    }
81    
82    // Example 4: List all fonts with a specific weight
83    println!("\n=== Listing Bold Fonts ===");
84    let bold_fonts: Vec<_> = cache.list().into_iter()
85        .filter(|(meta, _id)| {
86            matches!(meta.weight, FcWeight::Bold | FcWeight::ExtraBold | FcWeight::Black)
87        })
88        .take(5)
89        .collect();
90    
91    println!("First 5 bold fonts:");
92    for (meta, id) in bold_fonts {
93        println!("  {:?}: {:?}", id, meta.name.as_ref().or(meta.family.as_ref()));
94    }
95    
96    // Example 5: Search by name pattern
97    println!("\n=== Fonts with 'Mono' in name ===");
98    let mono_fonts: Vec<_> = cache.list().into_iter()
99        .filter(|(meta, _id)| {
100            meta.name.as_ref().map(|n| n.contains("Mono")).unwrap_or(false) ||
101            meta.family.as_ref().map(|f| f.contains("Mono")).unwrap_or(false)
102        })
103        .take(5)
104        .collect();
105    
106    println!("First 5 monospace fonts:");
107    for (meta, id) in mono_fonts {
108        println!("  {:?}: {:?}", id, meta.name.as_ref().or(meta.family.as_ref()));
109    }
110}
examples/character_resolution.rs (line 75)
8fn main() {
9    let cache = FcFontCache::build();
10        
11    // Create a font chain with typical web defaults
12    let families = vec![
13        "system-ui".to_string(),
14        "sans-serif".to_string(),
15    ];
16    
17    let mut trace = Vec::new();
18    let chain = cache.resolve_font_chain(
19        &families,
20        FcWeight::Normal,
21        PatternMatch::False,
22        PatternMatch::False,
23        &mut trace,
24    );
25    
26    // Test characters from different Unicode blocks
27    let test_chars = vec![
28        ('A', "Latin Capital Letter A"),
29        ('a', "Latin Small Letter A"),
30        ('0', "Digit Zero"),
31        ('€', "Euro Sign"),
32        ('→', "Rightwards Arrow"),
33        ('中', "CJK Ideograph - China"),
34        ('日', "CJK Ideograph - Sun/Day"),
35        ('あ', "Hiragana Letter A"),
36        ('ア', "Katakana Letter A"),
37        ('한', "Hangul Syllable Han"),
38        ('א', "Hebrew Letter Alef"),
39        ('ا', "Arabic Letter Alef"),
40        ('α', "Greek Small Letter Alpha"),
41        ('Σ', "Greek Capital Letter Sigma"),
42        ('я', "Cyrillic Small Letter Ya"),
43        ('🙂', "Slightly Smiling Face"),
44        ('♠', "Black Spade Suit"),
45        ('∑', "N-ary Summation"),
46        ('∞', "Infinity"),
47        ('℃', "Degree Celsius"),
48    ];
49    
50    println!("Character resolution results:\n");
51    println!("{:<6} {:<30} {:<40}", "Char", "Description", "Font");
52    println!("{}", "-".repeat(80));
53    
54    for (ch, description) in test_chars {
55        let text = ch.to_string();
56        let resolved = chain.resolve_text(&cache, &text);
57        
58        let font_name = resolved.first()
59            .and_then(|(_, info)| info.as_ref())
60            .and_then(|(id, _)| cache.get_metadata_by_id(id))
61            .and_then(|m| m.name.clone().or(m.family.clone()))
62            .unwrap_or_else(|| "⚠ NOT FOUND".to_string());
63        
64        println!("{:<6} {:<30} {}", ch, description, font_name);
65    }
66    
67    // Show how to check if a specific font covers a character
68    println!("\n\nFont Coverage Check\n");
69    
70    let pattern = rust_fontconfig::FcPattern {
71        family: Some("Arial".to_string()),
72        ..Default::default()
73    };
74    
75    if let Some(match_result) = cache.query(&pattern, &mut Vec::new()) {
76        println!("Checking Arial coverage:");
77        
78        // Create a chain just for Arial
79        let arial_chain = cache.resolve_font_chain(
80            &vec!["Arial".to_string()],
81            FcWeight::Normal,
82            PatternMatch::False,
83            PatternMatch::False,
84            &mut Vec::new(),
85        );
86        
87        let check_chars = ['A', '中', '🙂', '→'];
88        for ch in check_chars {
89            let resolved = arial_chain.resolve_text(&cache, &ch.to_string());
90            let found_in_arial = resolved.first()
91                .and_then(|(_, info)| info.as_ref())
92                .map(|(id, _)| id == &match_result.id)
93                .unwrap_or(false);
94            
95            let status = if found_in_arial { "✓" } else { "✗" };
96            println!("  {} '{}' (U+{:04X})", status, ch, ch as u32);
97        }
98    }
99    
100    // Show codepoint ranges supported
101    println!("\n\nUnicode Block Coverage Summary\n");
102    
103    let blocks = [
104        ("Basic Latin", 0x0020..0x007F),
105        ("Latin Extended-A", 0x0100..0x017F),
106        ("Greek", 0x0370..0x03FF),
107        ("Cyrillic", 0x0400..0x04FF),
108        ("Arabic", 0x0600..0x06FF),
109        ("CJK Unified Ideographs", 0x4E00..0x9FFF),
110        ("Hiragana", 0x3040..0x309F),
111        ("Katakana", 0x30A0..0x30FF),
112    ];
113    
114    for (name, range) in blocks {
115        // Sample a few codepoints from each block
116        let sample_points: Vec<char> = range.clone()
117            .step_by(range.len() / 5)
118            .take(5)
119            .filter_map(|cp| char::from_u32(cp))
120            .collect();
121        
122        let sample_text: String = sample_points.iter().collect();
123        let resolved = chain.resolve_text(&cache, &sample_text);
124        
125        let fonts_used: std::collections::HashSet<_> = resolved.iter()
126            .filter_map(|(_, info)| info.as_ref())
127            .map(|(id, _)| id.clone())
128            .collect();
129        
130        let coverage = resolved.iter()
131            .filter(|(_, info)| info.is_some())
132            .count() as f32 / resolved.len() as f32 * 100.0;
133        
134        println!("{:<30} {:>6.1}% coverage ({} fonts)", name, coverage, fonts_used.len());
135    }
136}
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 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 16-22)
7fn main() {
8    // Initialize font cache
9    let cache = FcFontCache::build();
10    
11    println!("=== Unicode-Aware Font Selection ===\n");
12    
13    // Step 1: Create a font chain for sans-serif fonts
14    println!("Step 1: Resolve font chain for 'sans-serif'");
15    let mut trace = Vec::new();
16    let chain = cache.resolve_font_chain(
17        &vec!["sans-serif".to_string()],
18        FcWeight::Normal,
19        PatternMatch::False,  // italic
20        PatternMatch::False,  // oblique
21        &mut trace,
22    );
23    
24    println!("  Font chain has {} CSS fallbacks and {} unicode fallbacks\n", 
25             chain.css_fallbacks.len(),
26             chain.unicode_fallbacks.len());
27    
28    // Step 2: Resolve different texts against the chain
29    println!("Step 2: Resolve various texts against the font chain\n");
30    
31    // Latin text
32    let latin_text = "Hello World";
33    println!("Latin text: '{}'", latin_text);
34    print_text_resolution(&cache, &chain, latin_text);
35    
36    // CJK (Chinese) text
37    let cjk_text = "你好世界";
38    println!("\nCJK text: '{}'", cjk_text);
39    print_text_resolution(&cache, &chain, cjk_text);
40    
41    // Japanese text
42    let japanese_text = "こんにちは世界";
43    println!("\nJapanese text: '{}'", japanese_text);
44    print_text_resolution(&cache, &chain, japanese_text);
45    
46    // Arabic text
47    let arabic_text = "مرحبا بالعالم";
48    println!("\nArabic text: '{}'", arabic_text);
49    print_text_resolution(&cache, &chain, arabic_text);
50    
51    // Cyrillic text
52    let cyrillic_text = "Привет мир";
53    println!("\nCyrillic text: '{}'", cyrillic_text);
54    print_text_resolution(&cache, &chain, cyrillic_text);
55    
56    // Mixed text
57    let mixed_text = "Hello 世界 Привет";
58    println!("\nMixed text: '{}'", mixed_text);
59    print_text_resolution(&cache, &chain, mixed_text);
60    
61    println!("\n=== Summary ===");
62    println!("The workflow is:");
63    println!("1. resolve_font_chain() - creates a fallback chain from CSS font-family");
64    println!("2. chain.resolve_text() - maps each character to a font in the chain");
65    println!("3. Use the font IDs to load and render glyphs");
66}
More examples
Hide additional examples
examples/character_resolution.rs (lines 18-24)
8fn main() {
9    let cache = FcFontCache::build();
10        
11    // Create a font chain with typical web defaults
12    let families = vec![
13        "system-ui".to_string(),
14        "sans-serif".to_string(),
15    ];
16    
17    let mut trace = Vec::new();
18    let chain = cache.resolve_font_chain(
19        &families,
20        FcWeight::Normal,
21        PatternMatch::False,
22        PatternMatch::False,
23        &mut trace,
24    );
25    
26    // Test characters from different Unicode blocks
27    let test_chars = vec![
28        ('A', "Latin Capital Letter A"),
29        ('a', "Latin Small Letter A"),
30        ('0', "Digit Zero"),
31        ('€', "Euro Sign"),
32        ('→', "Rightwards Arrow"),
33        ('中', "CJK Ideograph - China"),
34        ('日', "CJK Ideograph - Sun/Day"),
35        ('あ', "Hiragana Letter A"),
36        ('ア', "Katakana Letter A"),
37        ('한', "Hangul Syllable Han"),
38        ('א', "Hebrew Letter Alef"),
39        ('ا', "Arabic Letter Alef"),
40        ('α', "Greek Small Letter Alpha"),
41        ('Σ', "Greek Capital Letter Sigma"),
42        ('я', "Cyrillic Small Letter Ya"),
43        ('🙂', "Slightly Smiling Face"),
44        ('♠', "Black Spade Suit"),
45        ('∑', "N-ary Summation"),
46        ('∞', "Infinity"),
47        ('℃', "Degree Celsius"),
48    ];
49    
50    println!("Character resolution results:\n");
51    println!("{:<6} {:<30} {:<40}", "Char", "Description", "Font");
52    println!("{}", "-".repeat(80));
53    
54    for (ch, description) in test_chars {
55        let text = ch.to_string();
56        let resolved = chain.resolve_text(&cache, &text);
57        
58        let font_name = resolved.first()
59            .and_then(|(_, info)| info.as_ref())
60            .and_then(|(id, _)| cache.get_metadata_by_id(id))
61            .and_then(|m| m.name.clone().or(m.family.clone()))
62            .unwrap_or_else(|| "⚠ NOT FOUND".to_string());
63        
64        println!("{:<6} {:<30} {}", ch, description, font_name);
65    }
66    
67    // Show how to check if a specific font covers a character
68    println!("\n\nFont Coverage Check\n");
69    
70    let pattern = rust_fontconfig::FcPattern {
71        family: Some("Arial".to_string()),
72        ..Default::default()
73    };
74    
75    if let Some(match_result) = cache.query(&pattern, &mut Vec::new()) {
76        println!("Checking Arial coverage:");
77        
78        // Create a chain just for Arial
79        let arial_chain = cache.resolve_font_chain(
80            &vec!["Arial".to_string()],
81            FcWeight::Normal,
82            PatternMatch::False,
83            PatternMatch::False,
84            &mut Vec::new(),
85        );
86        
87        let check_chars = ['A', '中', '🙂', '→'];
88        for ch in check_chars {
89            let resolved = arial_chain.resolve_text(&cache, &ch.to_string());
90            let found_in_arial = resolved.first()
91                .and_then(|(_, info)| info.as_ref())
92                .map(|(id, _)| id == &match_result.id)
93                .unwrap_or(false);
94            
95            let status = if found_in_arial { "✓" } else { "✗" };
96            println!("  {} '{}' (U+{:04X})", status, ch, ch as u32);
97        }
98    }
99    
100    // Show codepoint ranges supported
101    println!("\n\nUnicode Block Coverage Summary\n");
102    
103    let blocks = [
104        ("Basic Latin", 0x0020..0x007F),
105        ("Latin Extended-A", 0x0100..0x017F),
106        ("Greek", 0x0370..0x03FF),
107        ("Cyrillic", 0x0400..0x04FF),
108        ("Arabic", 0x0600..0x06FF),
109        ("CJK Unified Ideographs", 0x4E00..0x9FFF),
110        ("Hiragana", 0x3040..0x309F),
111        ("Katakana", 0x30A0..0x30FF),
112    ];
113    
114    for (name, range) in blocks {
115        // Sample a few codepoints from each block
116        let sample_points: Vec<char> = range.clone()
117            .step_by(range.len() / 5)
118            .take(5)
119            .filter_map(|cp| char::from_u32(cp))
120            .collect();
121        
122        let sample_text: String = sample_points.iter().collect();
123        let resolved = chain.resolve_text(&cache, &sample_text);
124        
125        let fonts_used: std::collections::HashSet<_> = resolved.iter()
126            .filter_map(|(_, info)| info.as_ref())
127            .map(|(id, _)| id.clone())
128            .collect();
129        
130        let coverage = resolved.iter()
131            .filter(|(_, info)| info.is_some())
132            .count() as f32 / resolved.len() as f32 * 100.0;
133        
134        println!("{:<30} {:>6.1}% coverage ({} fonts)", name, coverage, fonts_used.len());
135    }
136}
examples/integration_api.rs (lines 26-32)
7fn main() {
8    println!("Font Integration API Example\n");
9    
10    // Step 1: Build the font cache once at application startup
11    println!("Step 1: Building font cache...");
12    let cache = FcFontCache::build();
13    println!("  Loaded {} fonts\n", cache.list().len());
14    
15    // Step 2: Simulate CSS font-family resolution
16    // This is what a browser/text renderer would do
17    println!("Step 2: Resolving CSS font-family: 'Helvetica, Arial, sans-serif'\n");
18    
19    let css_families = vec![
20        "Helvetica".to_string(),
21        "Arial".to_string(),
22        "sans-serif".to_string(),
23    ];
24    
25    let mut trace = Vec::new();
26    let chain = cache.resolve_font_chain(
27        &css_families,
28        FcWeight::Normal,
29        PatternMatch::False,  // italic
30        PatternMatch::False,  // oblique
31        &mut trace,
32    );
33    
34    println!("  CSS fallback groups: {}", chain.css_fallbacks.len());
35    for (i, group) in chain.css_fallbacks.iter().enumerate() {
36        println!("    {}: CSS '{}' -> {} fonts", i + 1, group.css_name, group.fonts.len());
37        for font in group.fonts.iter().take(2) {
38            if let Some(meta) = cache.get_metadata_by_id(&font.id) {
39                let name = meta.name.as_ref().or(meta.family.as_ref());
40                println!("       - {:?}", name);
41            }
42        }
43        if group.fonts.len() > 2 {
44            println!("       ... and {} more", group.fonts.len() - 2);
45        }
46    }
47    
48    println!("\n  Unicode fallback fonts: {}", chain.unicode_fallbacks.len());
49    for (i, font) in chain.unicode_fallbacks.iter().take(3).enumerate() {
50        if let Some(meta) = cache.get_metadata_by_id(&font.id) {
51            println!("    {}: {:?}", 
52                     i + 1, 
53                     meta.name.as_ref().or(meta.family.as_ref()));
54        }
55    }
56    if chain.unicode_fallbacks.len() > 3 {
57        println!("    ... and {} more", chain.unicode_fallbacks.len() - 3);
58    }
59    
60    // Step 3: Resolve text to fonts
61    // This maps each character to a specific font
62    println!("\n\nStep 3: Resolve text to fonts");
63    
64    let text = "Hello 世界! Привет мир";
65    println!("  Input text: '{}'\n", text);
66    
67    let resolved = chain.resolve_text(&cache, text);
68    
69    // Group by runs of same font
70    let mut runs = Vec::new();
71    let mut current_run_text = String::new();
72    let mut current_font_id: Option<FontId> = None;
73    
74    for (ch, font_info) in &resolved {
75        let this_font_id = font_info.as_ref().map(|(id, _)| *id);
76        
77        if this_font_id != current_font_id {
78            if !current_run_text.is_empty() {
79                runs.push((current_run_text.clone(), current_font_id));
80                current_run_text.clear();
81            }
82            current_font_id = this_font_id;
83        }
84        current_run_text.push(*ch);
85    }
86    if !current_run_text.is_empty() {
87        runs.push((current_run_text, current_font_id));
88    }
89    
90    println!("  Font runs:");
91    for (run_text, font_id) in &runs {
92        let font_name = font_id.as_ref()
93            .and_then(|id| cache.get_metadata_by_id(id))
94            .and_then(|m| m.name.clone().or(m.family.clone()))
95            .unwrap_or_else(|| "[NO FONT]".to_string());
96        println!("    '{}' -> {}", run_text, font_name);
97    }
98    
99    // Step 4: Load font data for shaping
100    // In a real application, you'd load font bytes here
101    println!("\n\nStep 4: Loading fonts for shaping");
102    
103    // Collect unique font IDs needed for this text
104    let unique_fonts: std::collections::HashSet<_> = runs.iter()
105        .filter_map(|(_, id)| *id)
106        .collect();
107    
108    println!("  Unique fonts needed: {}", unique_fonts.len());
109    
110    for font_id in &unique_fonts {
111        if let Some(meta) = cache.get_metadata_by_id(font_id) {
112            println!("    - {:?}", meta.name.as_ref().or(meta.family.as_ref()));
113            // Get font path via get_font_by_id
114            if let Some(source) = cache.get_font_by_id(font_id) {
115                match source {
116                    rust_fontconfig::FontSource::Disk(path) => {
117                        // In real code, you'd load the font file here:
118                        // let bytes = std::fs::read(&path.path)?;
119                        // let parsed = ttf_parser::Face::parse(&bytes, path.font_index as u32)?;
120                        println!("      Path: {}", path.path);
121                    }
122                    rust_fontconfig::FontSource::Memory(font) => {
123                        println!("      Memory font (id: {})", font.id);
124                    }
125                }
126            }
127        }
128    }
129    
130    println!("\nWorkflow summary:");
131    println!("");
132    println!("1. FcFontCache::build() - once at startup");
133    println!("2. cache.resolve_font_chain() - per CSS font-family declaration");
134    println!("3. chain.resolve_text(\"abc\") -> [Run {{ font, glyphs: \"abc\" }}] - per text string to shape");
135    println!("4. Load font bytes and shape each run with its font");
136}
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)

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.