pub struct FcFontCache { /* private fields */ }Expand description
Font cache, initialized at startup
Implementations§
Source§impl FcFontCache
impl FcFontCache
Sourcepub fn with_memory_fonts(
&mut self,
fonts: Vec<(FcPattern, FcFont)>,
) -> &mut Self
pub fn with_memory_fonts( &mut self, fonts: Vec<(FcPattern, FcFont)>, ) -> &mut Self
Adds in-memory font files
Sourcepub fn with_memory_font_with_id(
&mut self,
id: FontId,
pattern: FcPattern,
font: FcFont,
) -> &mut Self
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)
Sourcepub fn get_font_by_id<'a>(&'a self, id: &FontId) -> Option<FontSource<'a>>
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?
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
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}Sourcepub fn get_metadata_by_id(&self, id: &FontId) -> Option<&FcPattern>
pub fn get_metadata_by_id(&self, id: &FontId) -> Option<&FcPattern>
Get metadata directly from an ID
Examples found in repository?
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
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}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}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}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}Sourcepub fn get_font_bytes(&self, id: &FontId) -> Option<Vec<u8>>
pub fn get_font_bytes(&self, id: &FontId) -> Option<Vec<u8>>
Get font bytes (either from disk or memory)
Sourcepub fn build() -> Self
pub fn build() -> Self
Builds a new font cache from all fonts discovered on the system
Examples found in repository?
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
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}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}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}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}Sourcepub fn list(&self) -> Vec<(&FcPattern, FontId)>
pub fn list(&self) -> Vec<(&FcPattern, FontId)>
Returns the list of fonts and font patterns
Examples found in repository?
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
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}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}Sourcepub fn query(
&self,
pattern: &FcPattern,
trace: &mut Vec<TraceMsg>,
) -> Option<FontMatch>
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?
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
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}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}Sourcepub fn compute_fallbacks(
&self,
font_id: &FontId,
trace: &mut Vec<TraceMsg>,
) -> Vec<FontMatchNoFallback>
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)
Sourcepub fn get_memory_font(&self, id: &FontId) -> Option<&FcFont>
pub fn get_memory_font(&self, id: &FontId) -> Option<&FcFont>
Get in-memory font data
Sourcepub fn resolve_font_chain(
&self,
font_families: &[String],
weight: FcWeight,
italic: PatternMatch,
oblique: PatternMatch,
trace: &mut Vec<TraceMsg>,
) -> FontFallbackChain
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 weightitalic- Italic style requirementoblique- Oblique style requirementtrace- 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?
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
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}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}Sourcepub fn resolve_font_chain_with_os(
&self,
font_families: &[String],
weight: FcWeight,
italic: PatternMatch,
oblique: PatternMatch,
trace: &mut Vec<TraceMsg>,
os: OperatingSystem,
) -> FontFallbackChain
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
impl Clone for FcFontCache
Source§impl Debug for FcFontCache
impl Debug for FcFontCache
Auto Trait Implementations§
impl !Freeze for FcFontCache
impl RefUnwindSafe for FcFontCache
impl Send for FcFontCache
impl Sync for FcFontCache
impl Unpin for FcFontCache
impl UnwindSafe for FcFontCache
Blanket Implementations§
Source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
Source§impl<T> CloneToUninit for Twhere
T: Clone,
impl<T> CloneToUninit for Twhere
T: Clone,
Source§impl<T> IntoEither for T
impl<T> IntoEither for T
Source§fn into_either(self, into_left: bool) -> Either<Self, Self>
fn into_either(self, into_left: bool) -> Either<Self, Self>
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 moreSource§fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
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