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?
11fn main() {
12 println!("Building font cache...");
13 let cache = FcFontCache::build();
14 println!("Font cache built with {} fonts\n", cache.list().len());
15
16 // ── Query by family name ──
17 println!("=== Query by Family Name ===");
18 let mut trace = Vec::new();
19 let pattern = FcPattern {
20 family: Some("Arial".to_string()),
21 ..Default::default()
22 };
23
24 if let Some(result) = cache.query(&pattern, &mut trace) {
25 println!("Found Arial:");
26 println!(" Font ID: {:?}", result.id);
27 if let Some(meta) = cache.get_metadata_by_id(&result.id) {
28 println!(" Family: {:?}", meta.family);
29 println!(" Weight: {:?}", meta.weight);
30 println!(" Italic: {:?}", meta.italic);
31 }
32 if let Some(source) = cache.get_font_by_id(&result.id) {
33 match source {
34 rust_fontconfig::FontSource::Disk(path) => println!(" Path: {}", path.path),
35 rust_fontconfig::FontSource::Memory(font) => {
36 println!(" Memory font: {}", font.id)
37 }
38 }
39 }
40 } else {
41 println!("Arial not found");
42 }
43
44 // ── Query generic family ──
45 println!("\n=== Query Generic 'serif' ===");
46 trace.clear();
47 if let Some(result) = cache.query(
48 &FcPattern {
49 family: Some("serif".to_string()),
50 ..Default::default()
51 },
52 &mut trace,
53 ) {
54 if let Some(meta) = cache.get_metadata_by_id(&result.id) {
55 println!(
56 "Found: {:?}",
57 meta.name.as_ref().or(meta.family.as_ref())
58 );
59 }
60 }
61
62 // ── Query by style (bold + italic) ──
63 println!("\n=== Query Bold Italic ===");
64 trace.clear();
65 if let Some(result) = cache.query(
66 &FcPattern {
67 family: Some("sans-serif".to_string()),
68 weight: FcWeight::Bold,
69 italic: PatternMatch::True,
70 ..Default::default()
71 },
72 &mut trace,
73 ) {
74 if let Some(meta) = cache.get_metadata_by_id(&result.id) {
75 println!(
76 "Found: {:?} weight={:?} italic={:?}",
77 meta.name.as_ref().or(meta.family.as_ref()),
78 meta.weight,
79 meta.italic
80 );
81 }
82 }
83
84 // ── List bold fonts ──
85 println!("\n=== First 5 Bold Fonts ===");
86 for (meta, id) in cache
87 .list()
88 .into_iter()
89 .filter(|(m, _)| matches!(m.weight, FcWeight::Bold | FcWeight::ExtraBold | FcWeight::Black))
90 .take(5)
91 {
92 println!(
93 " {:?}: {:?}",
94 id,
95 meta.name.as_ref().or(meta.family.as_ref())
96 );
97 }
98
99 // ── Search by name substring ──
100 println!("\n=== Fonts with 'Mono' in name ===");
101 for (meta, _id) in cache.list().into_iter().filter(|(m, _)| {
102 m.name
103 .as_ref()
104 .map(|n| n.contains("Mono"))
105 .unwrap_or(false)
106 || m.family
107 .as_ref()
108 .map(|f| f.contains("Mono"))
109 .unwrap_or(false)
110 }).take(10) {
111 println!(
112 " {:?}",
113 meta.name.as_ref().or(meta.family.as_ref())
114 );
115 }
116}More examples
11fn main() {
12 println!("=== Font Integration Pipeline ===\n");
13
14 // ── Step 1: Build font cache ──
15 println!("Step 1: Build font cache");
16 let cache = FcFontCache::build();
17 println!(" {} fonts loaded\n", cache.list().len());
18
19 // ── Step 2: Resolve CSS font-family ──
20 let css_families = vec![
21 "Helvetica".to_string(),
22 "Arial".to_string(),
23 "sans-serif".to_string(),
24 ];
25 println!("Step 2: Resolve font-family: {:?}\n", css_families);
26
27 let chain = cache.resolve_font_chain(
28 &css_families,
29 FcWeight::Normal,
30 PatternMatch::False,
31 PatternMatch::False,
32 &mut Vec::new(),
33 );
34
35 for (i, group) in chain.css_fallbacks.iter().enumerate() {
36 print!(" [{}] '{}': {} fonts", i + 1, group.css_name, group.fonts.len());
37 if let Some(first) = group.fonts.first() {
38 if let Some(meta) = cache.get_metadata_by_id(&first.id) {
39 print!(
40 " (first: {:?})",
41 meta.name.as_ref().or(meta.family.as_ref())
42 );
43 }
44 }
45 println!();
46 }
47 println!(
48 " + {} unicode fallback fonts\n",
49 chain.unicode_fallbacks.len()
50 );
51
52 // ── Step 3: Resolve text to font runs ──
53 let text = "Hello 世界! Привет мир";
54 println!("Step 3: Resolve text: '{}'\n", text);
55
56 let resolved = chain.resolve_text(&cache, text);
57
58 // Group by runs of same font
59 let mut runs: Vec<(String, Option<FontId>)> = Vec::new();
60 let mut current_text = String::new();
61 let mut current_id: Option<FontId> = None;
62
63 for (ch, info) in &resolved {
64 let this_id = info.as_ref().map(|(id, _)| *id);
65 if this_id != current_id {
66 if !current_text.is_empty() {
67 runs.push((current_text.clone(), current_id));
68 current_text.clear();
69 }
70 current_id = this_id;
71 }
72 current_text.push(*ch);
73 }
74 if !current_text.is_empty() {
75 runs.push((current_text, current_id));
76 }
77
78 println!(" Font runs:");
79 for (run_text, font_id) in &runs {
80 let name = font_id
81 .as_ref()
82 .and_then(|id| cache.get_metadata_by_id(id))
83 .and_then(|m| m.name.clone().or(m.family.clone()))
84 .unwrap_or_else(|| "[NO FONT]".into());
85 println!(" '{}' -> {}", run_text, name);
86 }
87
88 // ── Step 4: Load font bytes ──
89 let unique_fonts: std::collections::HashSet<_> =
90 runs.iter().filter_map(|(_, id)| *id).collect();
91
92 println!(
93 "\nStep 4: Load fonts ({} unique needed)\n",
94 unique_fonts.len()
95 );
96 for font_id in &unique_fonts {
97 if let Some(meta) = cache.get_metadata_by_id(font_id) {
98 let name = meta
99 .name
100 .as_ref()
101 .or(meta.family.as_ref())
102 .map(|s| s.as_str())
103 .unwrap_or("?");
104 if let Some(source) = cache.get_font_by_id(font_id) {
105 match source {
106 rust_fontconfig::FontSource::Disk(path) => {
107 println!(" {} -> {}", name, path.path);
108 }
109 rust_fontconfig::FontSource::Memory(font) => {
110 println!(" {} -> memory (id: {})", name, font.id);
111 }
112 }
113 }
114 }
115 }
116
117 println!("\nPipeline summary:");
118 println!(" 1. FcFontCache::build() — once at startup");
119 println!(" 2. cache.resolve_font_chain() — per CSS font-family");
120 println!(" 3. chain.resolve_text() — per text run");
121 println!(" 4. cache.get_font_by_id() — load bytes for shaping");
122}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?
53fn print_resolution(cache: &FcFontCache, chain: &FontFallbackChain, text: &str) {
54 let resolved = chain.resolve_text(cache, text);
55
56 let mut current_font: Option<String> = None;
57 let mut segment = String::new();
58
59 for (ch, info) in &resolved {
60 let font_name = info.as_ref().and_then(|(id, _)| {
61 cache
62 .get_metadata_by_id(id)
63 .and_then(|m| m.name.clone().or(m.family.clone()))
64 });
65 if font_name != current_font {
66 if !segment.is_empty() {
67 println!(
68 " '{}' -> {}",
69 segment,
70 current_font.as_deref().unwrap_or("[NO FONT]")
71 );
72 segment.clear();
73 }
74 current_font = font_name;
75 }
76 segment.push(*ch);
77 }
78 if !segment.is_empty() {
79 println!(
80 " '{}' -> {}",
81 segment,
82 current_font.as_deref().unwrap_or("[NO FONT]")
83 );
84 }
85}More examples
13fn main() {
14 // Build the cache — scans and parses ALL system fonts
15 let start = Instant::now();
16 let cache = FcFontCache::build();
17 let build_time = start.elapsed();
18
19 println!("Cache built: {} fonts in {:?}\n", cache.list().len(), build_time);
20
21 // Query various fonts to showcase fuzzy matching
22 let queries = [
23 ("Arial", FcWeight::Normal, "Common sans-serif"),
24 ("Helvetica", FcWeight::Bold, "Bold variant"),
25 ("Courier", FcWeight::Normal, "Monospace"),
26 ("Georgia", FcWeight::Normal, "Serif"),
27 ("sans-serif", FcWeight::Normal, "Generic family"),
28 ];
29
30 for (name, weight, desc) in &queries {
31 let t = Instant::now();
32 let result = cache.query(
33 &FcPattern {
34 name: Some(name.to_string()),
35 weight: *weight,
36 ..Default::default()
37 },
38 &mut Vec::new(),
39 );
40
41 match result {
42 Some(fm) => {
43 let found = cache
44 .get_metadata_by_id(&fm.id)
45 .and_then(|m| m.name.clone().or(m.family.clone()))
46 .unwrap_or_else(|| format!("{:?}", fm.id));
47 println!(
48 " ✓ '{}' ({}) -> {} [{:?}]",
49 name, desc, found, t.elapsed()
50 );
51 }
52 None => println!(" ✗ '{}' ({}) -> NOT FOUND [{:?}]", name, desc, t.elapsed()),
53 }
54 }
55}11fn main() {
12 let cache = FcFontCache::build();
13
14 // Create a font chain with typical web defaults
15 let families = vec!["system-ui".to_string(), "sans-serif".to_string()];
16
17 let chain = cache.resolve_font_chain(
18 &families,
19 FcWeight::Normal,
20 PatternMatch::False,
21 PatternMatch::False,
22 &mut Vec::new(),
23 );
24
25 // Test characters from different Unicode blocks
26 let test_chars = [
27 ('A', "Latin Capital Letter A"),
28 ('a', "Latin Small Letter A"),
29 ('0', "Digit Zero"),
30 ('€', "Euro Sign"),
31 ('→', "Rightwards Arrow"),
32 ('中', "CJK Ideograph - China"),
33 ('日', "CJK Ideograph - Sun/Day"),
34 ('あ', "Hiragana Letter A"),
35 ('ア', "Katakana Letter A"),
36 ('한', "Hangul Syllable Han"),
37 ('א', "Hebrew Letter Alef"),
38 ('ا', "Arabic Letter Alef"),
39 ('α', "Greek Small Letter Alpha"),
40 ('я', "Cyrillic Small Letter Ya"),
41 ('🙂', "Slightly Smiling Face"),
42 ('♠', "Black Spade Suit"),
43 ('∑', "N-ary Summation"),
44 ('∞', "Infinity"),
45 ];
46
47 println!("Character resolution results:\n");
48 println!("{:<6} {:<30} {:<40}", "Char", "Description", "Font");
49 println!("{}", "-".repeat(76));
50
51 for (ch, description) in &test_chars {
52 let text = ch.to_string();
53 let resolved = chain.resolve_text(&cache, &text);
54
55 let font_name = resolved
56 .first()
57 .and_then(|(_, info)| info.as_ref())
58 .and_then(|(id, _)| cache.get_metadata_by_id(id))
59 .and_then(|m| m.name.clone().or(m.family.clone()))
60 .unwrap_or_else(|| "⚠ NOT FOUND".to_string());
61
62 println!("{:<6} {:<30} {}", ch, description, font_name);
63 }
64
65 // Check specific font coverage
66 println!("\n\nArial coverage check:");
67 let arial_chain = cache.resolve_font_chain(
68 &["Arial".to_string()],
69 FcWeight::Normal,
70 PatternMatch::False,
71 PatternMatch::False,
72 &mut Vec::new(),
73 );
74
75 let arial_result = cache.query(
76 &rust_fontconfig::FcPattern {
77 family: Some("Arial".to_string()),
78 ..Default::default()
79 },
80 &mut Vec::new(),
81 );
82
83 if let Some(arial_match) = arial_result {
84 for ch in ['A', '中', '🙂', '→'] {
85 let resolved = arial_chain.resolve_text(&cache, &ch.to_string());
86 let in_arial = resolved
87 .first()
88 .and_then(|(_, info)| info.as_ref())
89 .map(|(id, _)| id == &arial_match.id)
90 .unwrap_or(false);
91
92 println!(
93 " {} '{}' (U+{:04X})",
94 if in_arial { "✓" } else { "✗" },
95 ch,
96 ch as u32
97 );
98 }
99 } else {
100 println!(" Arial not found on this system");
101 }
102}11fn main() {
12 println!("Building font cache...");
13 let cache = FcFontCache::build();
14 println!("Font cache built with {} fonts\n", cache.list().len());
15
16 // ── Query by family name ──
17 println!("=== Query by Family Name ===");
18 let mut trace = Vec::new();
19 let pattern = FcPattern {
20 family: Some("Arial".to_string()),
21 ..Default::default()
22 };
23
24 if let Some(result) = cache.query(&pattern, &mut trace) {
25 println!("Found Arial:");
26 println!(" Font ID: {:?}", result.id);
27 if let Some(meta) = cache.get_metadata_by_id(&result.id) {
28 println!(" Family: {:?}", meta.family);
29 println!(" Weight: {:?}", meta.weight);
30 println!(" Italic: {:?}", meta.italic);
31 }
32 if let Some(source) = cache.get_font_by_id(&result.id) {
33 match source {
34 rust_fontconfig::FontSource::Disk(path) => println!(" Path: {}", path.path),
35 rust_fontconfig::FontSource::Memory(font) => {
36 println!(" Memory font: {}", font.id)
37 }
38 }
39 }
40 } else {
41 println!("Arial not found");
42 }
43
44 // ── Query generic family ──
45 println!("\n=== Query Generic 'serif' ===");
46 trace.clear();
47 if let Some(result) = cache.query(
48 &FcPattern {
49 family: Some("serif".to_string()),
50 ..Default::default()
51 },
52 &mut trace,
53 ) {
54 if let Some(meta) = cache.get_metadata_by_id(&result.id) {
55 println!(
56 "Found: {:?}",
57 meta.name.as_ref().or(meta.family.as_ref())
58 );
59 }
60 }
61
62 // ── Query by style (bold + italic) ──
63 println!("\n=== Query Bold Italic ===");
64 trace.clear();
65 if let Some(result) = cache.query(
66 &FcPattern {
67 family: Some("sans-serif".to_string()),
68 weight: FcWeight::Bold,
69 italic: PatternMatch::True,
70 ..Default::default()
71 },
72 &mut trace,
73 ) {
74 if let Some(meta) = cache.get_metadata_by_id(&result.id) {
75 println!(
76 "Found: {:?} weight={:?} italic={:?}",
77 meta.name.as_ref().or(meta.family.as_ref()),
78 meta.weight,
79 meta.italic
80 );
81 }
82 }
83
84 // ── List bold fonts ──
85 println!("\n=== First 5 Bold Fonts ===");
86 for (meta, id) in cache
87 .list()
88 .into_iter()
89 .filter(|(m, _)| matches!(m.weight, FcWeight::Bold | FcWeight::ExtraBold | FcWeight::Black))
90 .take(5)
91 {
92 println!(
93 " {:?}: {:?}",
94 id,
95 meta.name.as_ref().or(meta.family.as_ref())
96 );
97 }
98
99 // ── Search by name substring ──
100 println!("\n=== Fonts with 'Mono' in name ===");
101 for (meta, _id) in cache.list().into_iter().filter(|(m, _)| {
102 m.name
103 .as_ref()
104 .map(|n| n.contains("Mono"))
105 .unwrap_or(false)
106 || m.family
107 .as_ref()
108 .map(|f| f.contains("Mono"))
109 .unwrap_or(false)
110 }).take(10) {
111 println!(
112 " {:?}",
113 meta.name.as_ref().or(meta.family.as_ref())
114 );
115 }
116}11fn main() {
12 println!("=== Font Integration Pipeline ===\n");
13
14 // ── Step 1: Build font cache ──
15 println!("Step 1: Build font cache");
16 let cache = FcFontCache::build();
17 println!(" {} fonts loaded\n", cache.list().len());
18
19 // ── Step 2: Resolve CSS font-family ──
20 let css_families = vec![
21 "Helvetica".to_string(),
22 "Arial".to_string(),
23 "sans-serif".to_string(),
24 ];
25 println!("Step 2: Resolve font-family: {:?}\n", css_families);
26
27 let chain = cache.resolve_font_chain(
28 &css_families,
29 FcWeight::Normal,
30 PatternMatch::False,
31 PatternMatch::False,
32 &mut Vec::new(),
33 );
34
35 for (i, group) in chain.css_fallbacks.iter().enumerate() {
36 print!(" [{}] '{}': {} fonts", i + 1, group.css_name, group.fonts.len());
37 if let Some(first) = group.fonts.first() {
38 if let Some(meta) = cache.get_metadata_by_id(&first.id) {
39 print!(
40 " (first: {:?})",
41 meta.name.as_ref().or(meta.family.as_ref())
42 );
43 }
44 }
45 println!();
46 }
47 println!(
48 " + {} unicode fallback fonts\n",
49 chain.unicode_fallbacks.len()
50 );
51
52 // ── Step 3: Resolve text to font runs ──
53 let text = "Hello 世界! Привет мир";
54 println!("Step 3: Resolve text: '{}'\n", text);
55
56 let resolved = chain.resolve_text(&cache, text);
57
58 // Group by runs of same font
59 let mut runs: Vec<(String, Option<FontId>)> = Vec::new();
60 let mut current_text = String::new();
61 let mut current_id: Option<FontId> = None;
62
63 for (ch, info) in &resolved {
64 let this_id = info.as_ref().map(|(id, _)| *id);
65 if this_id != current_id {
66 if !current_text.is_empty() {
67 runs.push((current_text.clone(), current_id));
68 current_text.clear();
69 }
70 current_id = this_id;
71 }
72 current_text.push(*ch);
73 }
74 if !current_text.is_empty() {
75 runs.push((current_text, current_id));
76 }
77
78 println!(" Font runs:");
79 for (run_text, font_id) in &runs {
80 let name = font_id
81 .as_ref()
82 .and_then(|id| cache.get_metadata_by_id(id))
83 .and_then(|m| m.name.clone().or(m.family.clone()))
84 .unwrap_or_else(|| "[NO FONT]".into());
85 println!(" '{}' -> {}", run_text, name);
86 }
87
88 // ── Step 4: Load font bytes ──
89 let unique_fonts: std::collections::HashSet<_> =
90 runs.iter().filter_map(|(_, id)| *id).collect();
91
92 println!(
93 "\nStep 4: Load fonts ({} unique needed)\n",
94 unique_fonts.len()
95 );
96 for font_id in &unique_fonts {
97 if let Some(meta) = cache.get_metadata_by_id(font_id) {
98 let name = meta
99 .name
100 .as_ref()
101 .or(meta.family.as_ref())
102 .map(|s| s.as_str())
103 .unwrap_or("?");
104 if let Some(source) = cache.get_font_by_id(font_id) {
105 match source {
106 rust_fontconfig::FontSource::Disk(path) => {
107 println!(" {} -> {}", name, path.path);
108 }
109 rust_fontconfig::FontSource::Memory(font) => {
110 println!(" {} -> memory (id: {})", name, font.id);
111 }
112 }
113 }
114 }
115 }
116
117 println!("\nPipeline summary:");
118 println!(" 1. FcFontCache::build() — once at startup");
119 println!(" 2. cache.resolve_font_chain() — per CSS font-family");
120 println!(" 3. chain.resolve_text() — per text run");
121 println!(" 4. cache.get_font_by_id() — load bytes for shaping");
122}11fn main() {
12 println!("=== System Font Italic Race Condition Test ===\n");
13
14 // Build the cache (scans all system fonts)
15 println!("Building font cache (scanning system fonts)...");
16 let cache = FcFontCache::build();
17 let all_fonts = cache.list();
18 println!("Cache built: {} patterns\n", all_fonts.len());
19
20 // FIRST: dump ALL fonts that contain "system" or "sfns" in their name
21 println!("--- ALL fonts containing 'system' or 'sfns' in name/family ---");
22 for (pattern, id) in all_fonts {
23 let name_lower = pattern.name.as_deref().unwrap_or("").to_lowercase();
24 let family_lower = pattern.family.as_deref().unwrap_or("").to_lowercase();
25 if name_lower.contains("system") || name_lower.contains("sfns")
26 || family_lower.contains("system") || family_lower.contains("sfns") {
27 let style_score = FcFontCache::calculate_style_score(
28 &FcPattern {
29 weight: FcWeight::Normal,
30 italic: PatternMatch::False,
31 oblique: PatternMatch::False,
32 ..Default::default()
33 },
34 pattern,
35 );
36 println!(
37 " id={} name={:?} family={:?} italic={:?} bold={:?} weight={:?} stretch={:?} style_score={} subfamily={:?}",
38 id,
39 pattern.name.as_deref().unwrap_or("?"),
40 pattern.family.as_deref().unwrap_or("?"),
41 pattern.italic,
42 pattern.bold,
43 pattern.weight,
44 pattern.stretch,
45 style_score,
46 pattern.metadata.font_subfamily.as_deref().unwrap_or("?"),
47 );
48 }
49 }
50 println!();
51
52 // Query "System Font" with italic=False (what azul does for button text)
53 let families = vec!["System Font".to_string()];
54 let mut trace = Vec::new();
55
56 let chain = cache.resolve_font_chain(
57 &families,
58 FcWeight::Normal,
59 PatternMatch::False, // italic=False
60 PatternMatch::False, // oblique=False
61 &mut trace,
62 );
63
64 println!("Font chain for 'System Font' (italic=False, weight=Normal):");
65 for group in &chain.css_fallbacks {
66 println!(" CSS name: '{}'", group.css_name);
67 for (i, font_match) in group.fonts.iter().enumerate() {
68 let meta = cache.get_metadata_by_id(&font_match.id);
69 if let Some(pattern) = meta {
70 let style_score = FcFontCache::calculate_style_score(
71 &FcPattern {
72 weight: FcWeight::Normal,
73 italic: PatternMatch::False,
74 oblique: PatternMatch::False,
75 ..Default::default()
76 },
77 pattern,
78 );
79 println!(
80 " [{}] id={} name={:?} family={:?} italic={:?} bold={:?} weight={:?} style_score={} subfamily={:?}",
81 i,
82 font_match.id,
83 pattern.name.as_deref().unwrap_or("?"),
84 pattern.family.as_deref().unwrap_or("?"),
85 pattern.italic,
86 pattern.bold,
87 pattern.weight,
88 style_score,
89 pattern.metadata.font_subfamily.as_deref().unwrap_or("?"),
90 );
91 } else {
92 println!(" [{}] id={} (no metadata)", i, font_match.id);
93 }
94 }
95 }
96
97 // Verify: the first matched font should NOT be italic
98 let first_font = chain.css_fallbacks
99 .iter()
100 .flat_map(|g| g.fonts.iter())
101 .next();
102
103 if let Some(font) = first_font {
104 if let Some(meta) = cache.get_metadata_by_id(&font.id) {
105 if meta.italic == PatternMatch::True {
106 eprintln!("\n❌ FAIL: First matched font is ITALIC!");
107 eprintln!(" Font: {:?} (id={})", meta.name, font.id);
108 eprintln!(" This is the italic race condition bug.");
109 std::process::exit(1);
110 } else {
111 println!("\n✅ PASS: First matched font is NOT italic.");
112 println!(" Font: {:?} (id={})", meta.name, font.id);
113 }
114 }
115 } else {
116 eprintln!("\n⚠ WARNING: No fonts matched 'System Font'");
117 }
118
119 // Also test with DontCare (original default before the fix)
120 println!("\n--- Comparison: italic=DontCare ---");
121 let mut trace2 = Vec::new();
122 let chain2 = cache.resolve_font_chain(
123 &families,
124 FcWeight::Normal,
125 PatternMatch::DontCare,
126 PatternMatch::DontCare,
127 &mut trace2,
128 );
129
130 for group in &chain2.css_fallbacks {
131 for (i, font_match) in group.fonts.iter().enumerate() {
132 if let Some(pattern) = cache.get_metadata_by_id(&font_match.id) {
133 println!(
134 " [{}] {:?} italic={:?} weight={:?}",
135 i,
136 pattern.name.as_deref().unwrap_or("?"),
137 pattern.italic,
138 pattern.weight,
139 );
140 }
141 }
142 }
143
144 // Run the test 10 times to verify determinism
145 println!("\n--- Determinism check (10 runs) ---");
146 let mut all_same = true;
147 let mut first_result = None;
148
149 for run in 0..10 {
150 // Clear chain cache to force re-resolution
151 let mut trace_n = Vec::new();
152 let chain_n = cache.resolve_font_chain(
153 &families,
154 FcWeight::Normal,
155 PatternMatch::False,
156 PatternMatch::False,
157 &mut trace_n,
158 );
159
160 let first_id = chain_n.css_fallbacks
161 .iter()
162 .flat_map(|g| g.fonts.iter())
163 .next()
164 .map(|f| f.id);
165
166 if let Some(id) = first_id {
167 if first_result.is_none() {
168 first_result = Some(id);
169 } else if first_result != Some(id) {
170 println!(" Run {}: id={} — DIFFERENT from first run!", run, id);
171 all_same = false;
172 }
173 }
174 }
175
176 if all_same {
177 println!(" All 10 runs returned the same first font. ✅");
178 } else {
179 println!(" Results varied across runs! ❌");
180 }
181
182 println!("\n=== Test complete ===");
183}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?
11fn main() {
12 let cache = FcFontCache::build();
13
14 println!("=== Unicode-Aware Font Selection ===\n");
15
16 // Create a font chain for sans-serif
17 let chain = cache.resolve_font_chain(
18 &["sans-serif".to_string()],
19 FcWeight::Normal,
20 PatternMatch::False,
21 PatternMatch::False,
22 &mut Vec::new(),
23 );
24
25 println!(
26 "Font chain: {} CSS fallbacks, {} unicode fallbacks\n",
27 chain.css_fallbacks.len(),
28 chain.unicode_fallbacks.len()
29 );
30
31 // Resolve different scripts
32 let texts = [
33 ("Latin", "Hello World"),
34 ("CJK", "你好世界"),
35 ("Japanese", "こんにちは世界"),
36 ("Arabic", "مرحبا بالعالم"),
37 ("Cyrillic", "Привет мир"),
38 ("Mixed", "Hello 世界 Привет"),
39 ];
40
41 for (label, text) in &texts {
42 println!("{} text: '{}'", label, text);
43 print_resolution(&cache, &chain, text);
44 println!();
45 }
46
47 println!("Workflow:");
48 println!(" 1. resolve_font_chain() — creates fallback chain from CSS font-family");
49 println!(" 2. chain.resolve_text() — maps each character to a font");
50 println!(" 3. Use font IDs to load and render glyphs");
51}More examples
13fn main() {
14 // Build the cache — scans and parses ALL system fonts
15 let start = Instant::now();
16 let cache = FcFontCache::build();
17 let build_time = start.elapsed();
18
19 println!("Cache built: {} fonts in {:?}\n", cache.list().len(), build_time);
20
21 // Query various fonts to showcase fuzzy matching
22 let queries = [
23 ("Arial", FcWeight::Normal, "Common sans-serif"),
24 ("Helvetica", FcWeight::Bold, "Bold variant"),
25 ("Courier", FcWeight::Normal, "Monospace"),
26 ("Georgia", FcWeight::Normal, "Serif"),
27 ("sans-serif", FcWeight::Normal, "Generic family"),
28 ];
29
30 for (name, weight, desc) in &queries {
31 let t = Instant::now();
32 let result = cache.query(
33 &FcPattern {
34 name: Some(name.to_string()),
35 weight: *weight,
36 ..Default::default()
37 },
38 &mut Vec::new(),
39 );
40
41 match result {
42 Some(fm) => {
43 let found = cache
44 .get_metadata_by_id(&fm.id)
45 .and_then(|m| m.name.clone().or(m.family.clone()))
46 .unwrap_or_else(|| format!("{:?}", fm.id));
47 println!(
48 " ✓ '{}' ({}) -> {} [{:?}]",
49 name, desc, found, t.elapsed()
50 );
51 }
52 None => println!(" ✗ '{}' ({}) -> NOT FOUND [{:?}]", name, desc, t.elapsed()),
53 }
54 }
55}11fn main() {
12 let cache = FcFontCache::build();
13
14 // Create a font chain with typical web defaults
15 let families = vec!["system-ui".to_string(), "sans-serif".to_string()];
16
17 let chain = cache.resolve_font_chain(
18 &families,
19 FcWeight::Normal,
20 PatternMatch::False,
21 PatternMatch::False,
22 &mut Vec::new(),
23 );
24
25 // Test characters from different Unicode blocks
26 let test_chars = [
27 ('A', "Latin Capital Letter A"),
28 ('a', "Latin Small Letter A"),
29 ('0', "Digit Zero"),
30 ('€', "Euro Sign"),
31 ('→', "Rightwards Arrow"),
32 ('中', "CJK Ideograph - China"),
33 ('日', "CJK Ideograph - Sun/Day"),
34 ('あ', "Hiragana Letter A"),
35 ('ア', "Katakana Letter A"),
36 ('한', "Hangul Syllable Han"),
37 ('א', "Hebrew Letter Alef"),
38 ('ا', "Arabic Letter Alef"),
39 ('α', "Greek Small Letter Alpha"),
40 ('я', "Cyrillic Small Letter Ya"),
41 ('🙂', "Slightly Smiling Face"),
42 ('♠', "Black Spade Suit"),
43 ('∑', "N-ary Summation"),
44 ('∞', "Infinity"),
45 ];
46
47 println!("Character resolution results:\n");
48 println!("{:<6} {:<30} {:<40}", "Char", "Description", "Font");
49 println!("{}", "-".repeat(76));
50
51 for (ch, description) in &test_chars {
52 let text = ch.to_string();
53 let resolved = chain.resolve_text(&cache, &text);
54
55 let font_name = resolved
56 .first()
57 .and_then(|(_, info)| info.as_ref())
58 .and_then(|(id, _)| cache.get_metadata_by_id(id))
59 .and_then(|m| m.name.clone().or(m.family.clone()))
60 .unwrap_or_else(|| "⚠ NOT FOUND".to_string());
61
62 println!("{:<6} {:<30} {}", ch, description, font_name);
63 }
64
65 // Check specific font coverage
66 println!("\n\nArial coverage check:");
67 let arial_chain = cache.resolve_font_chain(
68 &["Arial".to_string()],
69 FcWeight::Normal,
70 PatternMatch::False,
71 PatternMatch::False,
72 &mut Vec::new(),
73 );
74
75 let arial_result = cache.query(
76 &rust_fontconfig::FcPattern {
77 family: Some("Arial".to_string()),
78 ..Default::default()
79 },
80 &mut Vec::new(),
81 );
82
83 if let Some(arial_match) = arial_result {
84 for ch in ['A', '中', '🙂', '→'] {
85 let resolved = arial_chain.resolve_text(&cache, &ch.to_string());
86 let in_arial = resolved
87 .first()
88 .and_then(|(_, info)| info.as_ref())
89 .map(|(id, _)| id == &arial_match.id)
90 .unwrap_or(false);
91
92 println!(
93 " {} '{}' (U+{:04X})",
94 if in_arial { "✓" } else { "✗" },
95 ch,
96 ch as u32
97 );
98 }
99 } else {
100 println!(" Arial not found on this system");
101 }
102}11fn main() {
12 println!("Building font cache...");
13 let cache = FcFontCache::build();
14 println!("Font cache built with {} fonts\n", cache.list().len());
15
16 // ── Query by family name ──
17 println!("=== Query by Family Name ===");
18 let mut trace = Vec::new();
19 let pattern = FcPattern {
20 family: Some("Arial".to_string()),
21 ..Default::default()
22 };
23
24 if let Some(result) = cache.query(&pattern, &mut trace) {
25 println!("Found Arial:");
26 println!(" Font ID: {:?}", result.id);
27 if let Some(meta) = cache.get_metadata_by_id(&result.id) {
28 println!(" Family: {:?}", meta.family);
29 println!(" Weight: {:?}", meta.weight);
30 println!(" Italic: {:?}", meta.italic);
31 }
32 if let Some(source) = cache.get_font_by_id(&result.id) {
33 match source {
34 rust_fontconfig::FontSource::Disk(path) => println!(" Path: {}", path.path),
35 rust_fontconfig::FontSource::Memory(font) => {
36 println!(" Memory font: {}", font.id)
37 }
38 }
39 }
40 } else {
41 println!("Arial not found");
42 }
43
44 // ── Query generic family ──
45 println!("\n=== Query Generic 'serif' ===");
46 trace.clear();
47 if let Some(result) = cache.query(
48 &FcPattern {
49 family: Some("serif".to_string()),
50 ..Default::default()
51 },
52 &mut trace,
53 ) {
54 if let Some(meta) = cache.get_metadata_by_id(&result.id) {
55 println!(
56 "Found: {:?}",
57 meta.name.as_ref().or(meta.family.as_ref())
58 );
59 }
60 }
61
62 // ── Query by style (bold + italic) ──
63 println!("\n=== Query Bold Italic ===");
64 trace.clear();
65 if let Some(result) = cache.query(
66 &FcPattern {
67 family: Some("sans-serif".to_string()),
68 weight: FcWeight::Bold,
69 italic: PatternMatch::True,
70 ..Default::default()
71 },
72 &mut trace,
73 ) {
74 if let Some(meta) = cache.get_metadata_by_id(&result.id) {
75 println!(
76 "Found: {:?} weight={:?} italic={:?}",
77 meta.name.as_ref().or(meta.family.as_ref()),
78 meta.weight,
79 meta.italic
80 );
81 }
82 }
83
84 // ── List bold fonts ──
85 println!("\n=== First 5 Bold Fonts ===");
86 for (meta, id) in cache
87 .list()
88 .into_iter()
89 .filter(|(m, _)| matches!(m.weight, FcWeight::Bold | FcWeight::ExtraBold | FcWeight::Black))
90 .take(5)
91 {
92 println!(
93 " {:?}: {:?}",
94 id,
95 meta.name.as_ref().or(meta.family.as_ref())
96 );
97 }
98
99 // ── Search by name substring ──
100 println!("\n=== Fonts with 'Mono' in name ===");
101 for (meta, _id) in cache.list().into_iter().filter(|(m, _)| {
102 m.name
103 .as_ref()
104 .map(|n| n.contains("Mono"))
105 .unwrap_or(false)
106 || m.family
107 .as_ref()
108 .map(|f| f.contains("Mono"))
109 .unwrap_or(false)
110 }).take(10) {
111 println!(
112 " {:?}",
113 meta.name.as_ref().or(meta.family.as_ref())
114 );
115 }
116}11fn main() {
12 println!("=== Font Integration Pipeline ===\n");
13
14 // ── Step 1: Build font cache ──
15 println!("Step 1: Build font cache");
16 let cache = FcFontCache::build();
17 println!(" {} fonts loaded\n", cache.list().len());
18
19 // ── Step 2: Resolve CSS font-family ──
20 let css_families = vec![
21 "Helvetica".to_string(),
22 "Arial".to_string(),
23 "sans-serif".to_string(),
24 ];
25 println!("Step 2: Resolve font-family: {:?}\n", css_families);
26
27 let chain = cache.resolve_font_chain(
28 &css_families,
29 FcWeight::Normal,
30 PatternMatch::False,
31 PatternMatch::False,
32 &mut Vec::new(),
33 );
34
35 for (i, group) in chain.css_fallbacks.iter().enumerate() {
36 print!(" [{}] '{}': {} fonts", i + 1, group.css_name, group.fonts.len());
37 if let Some(first) = group.fonts.first() {
38 if let Some(meta) = cache.get_metadata_by_id(&first.id) {
39 print!(
40 " (first: {:?})",
41 meta.name.as_ref().or(meta.family.as_ref())
42 );
43 }
44 }
45 println!();
46 }
47 println!(
48 " + {} unicode fallback fonts\n",
49 chain.unicode_fallbacks.len()
50 );
51
52 // ── Step 3: Resolve text to font runs ──
53 let text = "Hello 世界! Привет мир";
54 println!("Step 3: Resolve text: '{}'\n", text);
55
56 let resolved = chain.resolve_text(&cache, text);
57
58 // Group by runs of same font
59 let mut runs: Vec<(String, Option<FontId>)> = Vec::new();
60 let mut current_text = String::new();
61 let mut current_id: Option<FontId> = None;
62
63 for (ch, info) in &resolved {
64 let this_id = info.as_ref().map(|(id, _)| *id);
65 if this_id != current_id {
66 if !current_text.is_empty() {
67 runs.push((current_text.clone(), current_id));
68 current_text.clear();
69 }
70 current_id = this_id;
71 }
72 current_text.push(*ch);
73 }
74 if !current_text.is_empty() {
75 runs.push((current_text, current_id));
76 }
77
78 println!(" Font runs:");
79 for (run_text, font_id) in &runs {
80 let name = font_id
81 .as_ref()
82 .and_then(|id| cache.get_metadata_by_id(id))
83 .and_then(|m| m.name.clone().or(m.family.clone()))
84 .unwrap_or_else(|| "[NO FONT]".into());
85 println!(" '{}' -> {}", run_text, name);
86 }
87
88 // ── Step 4: Load font bytes ──
89 let unique_fonts: std::collections::HashSet<_> =
90 runs.iter().filter_map(|(_, id)| *id).collect();
91
92 println!(
93 "\nStep 4: Load fonts ({} unique needed)\n",
94 unique_fonts.len()
95 );
96 for font_id in &unique_fonts {
97 if let Some(meta) = cache.get_metadata_by_id(font_id) {
98 let name = meta
99 .name
100 .as_ref()
101 .or(meta.family.as_ref())
102 .map(|s| s.as_str())
103 .unwrap_or("?");
104 if let Some(source) = cache.get_font_by_id(font_id) {
105 match source {
106 rust_fontconfig::FontSource::Disk(path) => {
107 println!(" {} -> {}", name, path.path);
108 }
109 rust_fontconfig::FontSource::Memory(font) => {
110 println!(" {} -> memory (id: {})", name, font.id);
111 }
112 }
113 }
114 }
115 }
116
117 println!("\nPipeline summary:");
118 println!(" 1. FcFontCache::build() — once at startup");
119 println!(" 2. cache.resolve_font_chain() — per CSS font-family");
120 println!(" 3. chain.resolve_text() — per text run");
121 println!(" 4. cache.get_font_by_id() — load bytes for shaping");
122}11fn main() {
12 println!("=== System Font Italic Race Condition Test ===\n");
13
14 // Build the cache (scans all system fonts)
15 println!("Building font cache (scanning system fonts)...");
16 let cache = FcFontCache::build();
17 let all_fonts = cache.list();
18 println!("Cache built: {} patterns\n", all_fonts.len());
19
20 // FIRST: dump ALL fonts that contain "system" or "sfns" in their name
21 println!("--- ALL fonts containing 'system' or 'sfns' in name/family ---");
22 for (pattern, id) in all_fonts {
23 let name_lower = pattern.name.as_deref().unwrap_or("").to_lowercase();
24 let family_lower = pattern.family.as_deref().unwrap_or("").to_lowercase();
25 if name_lower.contains("system") || name_lower.contains("sfns")
26 || family_lower.contains("system") || family_lower.contains("sfns") {
27 let style_score = FcFontCache::calculate_style_score(
28 &FcPattern {
29 weight: FcWeight::Normal,
30 italic: PatternMatch::False,
31 oblique: PatternMatch::False,
32 ..Default::default()
33 },
34 pattern,
35 );
36 println!(
37 " id={} name={:?} family={:?} italic={:?} bold={:?} weight={:?} stretch={:?} style_score={} subfamily={:?}",
38 id,
39 pattern.name.as_deref().unwrap_or("?"),
40 pattern.family.as_deref().unwrap_or("?"),
41 pattern.italic,
42 pattern.bold,
43 pattern.weight,
44 pattern.stretch,
45 style_score,
46 pattern.metadata.font_subfamily.as_deref().unwrap_or("?"),
47 );
48 }
49 }
50 println!();
51
52 // Query "System Font" with italic=False (what azul does for button text)
53 let families = vec!["System Font".to_string()];
54 let mut trace = Vec::new();
55
56 let chain = cache.resolve_font_chain(
57 &families,
58 FcWeight::Normal,
59 PatternMatch::False, // italic=False
60 PatternMatch::False, // oblique=False
61 &mut trace,
62 );
63
64 println!("Font chain for 'System Font' (italic=False, weight=Normal):");
65 for group in &chain.css_fallbacks {
66 println!(" CSS name: '{}'", group.css_name);
67 for (i, font_match) in group.fonts.iter().enumerate() {
68 let meta = cache.get_metadata_by_id(&font_match.id);
69 if let Some(pattern) = meta {
70 let style_score = FcFontCache::calculate_style_score(
71 &FcPattern {
72 weight: FcWeight::Normal,
73 italic: PatternMatch::False,
74 oblique: PatternMatch::False,
75 ..Default::default()
76 },
77 pattern,
78 );
79 println!(
80 " [{}] id={} name={:?} family={:?} italic={:?} bold={:?} weight={:?} style_score={} subfamily={:?}",
81 i,
82 font_match.id,
83 pattern.name.as_deref().unwrap_or("?"),
84 pattern.family.as_deref().unwrap_or("?"),
85 pattern.italic,
86 pattern.bold,
87 pattern.weight,
88 style_score,
89 pattern.metadata.font_subfamily.as_deref().unwrap_or("?"),
90 );
91 } else {
92 println!(" [{}] id={} (no metadata)", i, font_match.id);
93 }
94 }
95 }
96
97 // Verify: the first matched font should NOT be italic
98 let first_font = chain.css_fallbacks
99 .iter()
100 .flat_map(|g| g.fonts.iter())
101 .next();
102
103 if let Some(font) = first_font {
104 if let Some(meta) = cache.get_metadata_by_id(&font.id) {
105 if meta.italic == PatternMatch::True {
106 eprintln!("\n❌ FAIL: First matched font is ITALIC!");
107 eprintln!(" Font: {:?} (id={})", meta.name, font.id);
108 eprintln!(" This is the italic race condition bug.");
109 std::process::exit(1);
110 } else {
111 println!("\n✅ PASS: First matched font is NOT italic.");
112 println!(" Font: {:?} (id={})", meta.name, font.id);
113 }
114 }
115 } else {
116 eprintln!("\n⚠ WARNING: No fonts matched 'System Font'");
117 }
118
119 // Also test with DontCare (original default before the fix)
120 println!("\n--- Comparison: italic=DontCare ---");
121 let mut trace2 = Vec::new();
122 let chain2 = cache.resolve_font_chain(
123 &families,
124 FcWeight::Normal,
125 PatternMatch::DontCare,
126 PatternMatch::DontCare,
127 &mut trace2,
128 );
129
130 for group in &chain2.css_fallbacks {
131 for (i, font_match) in group.fonts.iter().enumerate() {
132 if let Some(pattern) = cache.get_metadata_by_id(&font_match.id) {
133 println!(
134 " [{}] {:?} italic={:?} weight={:?}",
135 i,
136 pattern.name.as_deref().unwrap_or("?"),
137 pattern.italic,
138 pattern.weight,
139 );
140 }
141 }
142 }
143
144 // Run the test 10 times to verify determinism
145 println!("\n--- Determinism check (10 runs) ---");
146 let mut all_same = true;
147 let mut first_result = None;
148
149 for run in 0..10 {
150 // Clear chain cache to force re-resolution
151 let mut trace_n = Vec::new();
152 let chain_n = cache.resolve_font_chain(
153 &families,
154 FcWeight::Normal,
155 PatternMatch::False,
156 PatternMatch::False,
157 &mut trace_n,
158 );
159
160 let first_id = chain_n.css_fallbacks
161 .iter()
162 .flat_map(|g| g.fonts.iter())
163 .next()
164 .map(|f| f.id);
165
166 if let Some(id) = first_id {
167 if first_result.is_none() {
168 first_result = Some(id);
169 } else if first_result != Some(id) {
170 println!(" Run {}: id={} — DIFFERENT from first run!", run, id);
171 all_same = false;
172 }
173 }
174 }
175
176 if all_same {
177 println!(" All 10 runs returned the same first font. ✅");
178 } else {
179 println!(" Results varied across runs! ❌");
180 }
181
182 println!("\n=== Test complete ===");
183}Sourcepub fn build_with_families(families: &[impl AsRef<str>]) -> Self
pub fn build_with_families(families: &[impl AsRef<str>]) -> Self
Builds a font cache with only specific font families (and their fallbacks).
This is a performance optimization for applications that know ahead of time which fonts they need. Instead of scanning all system fonts (which can be slow on systems with many fonts), only fonts matching the specified families are loaded.
Generic family names like “sans-serif”, “serif”, “monospace” are expanded to OS-specific font names (e.g., “sans-serif” on macOS becomes “Helvetica Neue”, “San Francisco”, etc.).
Note: This will NOT automatically load fallback fonts for scripts not covered by the requested families. If you need Arabic, CJK, or emoji support, either:
- Add those families explicitly to the filter
- Use
with_memory_fonts()to add bundled fonts - Use
build()to load all system fonts
§Arguments
families- Font family names to load (e.g., [“Arial”, “sans-serif”])
§Example
// Only load Arial and sans-serif fallback fonts
let cache = FcFontCache::build_with_families(&["Arial", "sans-serif"]);Sourcepub fn is_memory_font(&self, id: &FontId) -> bool
pub fn is_memory_font(&self, id: &FontId) -> bool
Check if a font ID is a memory font (preferred over disk fonts)
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?
13fn main() {
14 // Build the cache — scans and parses ALL system fonts
15 let start = Instant::now();
16 let cache = FcFontCache::build();
17 let build_time = start.elapsed();
18
19 println!("Cache built: {} fonts in {:?}\n", cache.list().len(), build_time);
20
21 // Query various fonts to showcase fuzzy matching
22 let queries = [
23 ("Arial", FcWeight::Normal, "Common sans-serif"),
24 ("Helvetica", FcWeight::Bold, "Bold variant"),
25 ("Courier", FcWeight::Normal, "Monospace"),
26 ("Georgia", FcWeight::Normal, "Serif"),
27 ("sans-serif", FcWeight::Normal, "Generic family"),
28 ];
29
30 for (name, weight, desc) in &queries {
31 let t = Instant::now();
32 let result = cache.query(
33 &FcPattern {
34 name: Some(name.to_string()),
35 weight: *weight,
36 ..Default::default()
37 },
38 &mut Vec::new(),
39 );
40
41 match result {
42 Some(fm) => {
43 let found = cache
44 .get_metadata_by_id(&fm.id)
45 .and_then(|m| m.name.clone().or(m.family.clone()))
46 .unwrap_or_else(|| format!("{:?}", fm.id));
47 println!(
48 " ✓ '{}' ({}) -> {} [{:?}]",
49 name, desc, found, t.elapsed()
50 );
51 }
52 None => println!(" ✗ '{}' ({}) -> NOT FOUND [{:?}]", name, desc, t.elapsed()),
53 }
54 }
55}More examples
11fn main() {
12 println!("Building font cache...");
13 let cache = FcFontCache::build();
14 println!("Font cache built with {} fonts\n", cache.list().len());
15
16 // ── Query by family name ──
17 println!("=== Query by Family Name ===");
18 let mut trace = Vec::new();
19 let pattern = FcPattern {
20 family: Some("Arial".to_string()),
21 ..Default::default()
22 };
23
24 if let Some(result) = cache.query(&pattern, &mut trace) {
25 println!("Found Arial:");
26 println!(" Font ID: {:?}", result.id);
27 if let Some(meta) = cache.get_metadata_by_id(&result.id) {
28 println!(" Family: {:?}", meta.family);
29 println!(" Weight: {:?}", meta.weight);
30 println!(" Italic: {:?}", meta.italic);
31 }
32 if let Some(source) = cache.get_font_by_id(&result.id) {
33 match source {
34 rust_fontconfig::FontSource::Disk(path) => println!(" Path: {}", path.path),
35 rust_fontconfig::FontSource::Memory(font) => {
36 println!(" Memory font: {}", font.id)
37 }
38 }
39 }
40 } else {
41 println!("Arial not found");
42 }
43
44 // ── Query generic family ──
45 println!("\n=== Query Generic 'serif' ===");
46 trace.clear();
47 if let Some(result) = cache.query(
48 &FcPattern {
49 family: Some("serif".to_string()),
50 ..Default::default()
51 },
52 &mut trace,
53 ) {
54 if let Some(meta) = cache.get_metadata_by_id(&result.id) {
55 println!(
56 "Found: {:?}",
57 meta.name.as_ref().or(meta.family.as_ref())
58 );
59 }
60 }
61
62 // ── Query by style (bold + italic) ──
63 println!("\n=== Query Bold Italic ===");
64 trace.clear();
65 if let Some(result) = cache.query(
66 &FcPattern {
67 family: Some("sans-serif".to_string()),
68 weight: FcWeight::Bold,
69 italic: PatternMatch::True,
70 ..Default::default()
71 },
72 &mut trace,
73 ) {
74 if let Some(meta) = cache.get_metadata_by_id(&result.id) {
75 println!(
76 "Found: {:?} weight={:?} italic={:?}",
77 meta.name.as_ref().or(meta.family.as_ref()),
78 meta.weight,
79 meta.italic
80 );
81 }
82 }
83
84 // ── List bold fonts ──
85 println!("\n=== First 5 Bold Fonts ===");
86 for (meta, id) in cache
87 .list()
88 .into_iter()
89 .filter(|(m, _)| matches!(m.weight, FcWeight::Bold | FcWeight::ExtraBold | FcWeight::Black))
90 .take(5)
91 {
92 println!(
93 " {:?}: {:?}",
94 id,
95 meta.name.as_ref().or(meta.family.as_ref())
96 );
97 }
98
99 // ── Search by name substring ──
100 println!("\n=== Fonts with 'Mono' in name ===");
101 for (meta, _id) in cache.list().into_iter().filter(|(m, _)| {
102 m.name
103 .as_ref()
104 .map(|n| n.contains("Mono"))
105 .unwrap_or(false)
106 || m.family
107 .as_ref()
108 .map(|f| f.contains("Mono"))
109 .unwrap_or(false)
110 }).take(10) {
111 println!(
112 " {:?}",
113 meta.name.as_ref().or(meta.family.as_ref())
114 );
115 }
116}11fn main() {
12 println!("=== Font Integration Pipeline ===\n");
13
14 // ── Step 1: Build font cache ──
15 println!("Step 1: Build font cache");
16 let cache = FcFontCache::build();
17 println!(" {} fonts loaded\n", cache.list().len());
18
19 // ── Step 2: Resolve CSS font-family ──
20 let css_families = vec![
21 "Helvetica".to_string(),
22 "Arial".to_string(),
23 "sans-serif".to_string(),
24 ];
25 println!("Step 2: Resolve font-family: {:?}\n", css_families);
26
27 let chain = cache.resolve_font_chain(
28 &css_families,
29 FcWeight::Normal,
30 PatternMatch::False,
31 PatternMatch::False,
32 &mut Vec::new(),
33 );
34
35 for (i, group) in chain.css_fallbacks.iter().enumerate() {
36 print!(" [{}] '{}': {} fonts", i + 1, group.css_name, group.fonts.len());
37 if let Some(first) = group.fonts.first() {
38 if let Some(meta) = cache.get_metadata_by_id(&first.id) {
39 print!(
40 " (first: {:?})",
41 meta.name.as_ref().or(meta.family.as_ref())
42 );
43 }
44 }
45 println!();
46 }
47 println!(
48 " + {} unicode fallback fonts\n",
49 chain.unicode_fallbacks.len()
50 );
51
52 // ── Step 3: Resolve text to font runs ──
53 let text = "Hello 世界! Привет мир";
54 println!("Step 3: Resolve text: '{}'\n", text);
55
56 let resolved = chain.resolve_text(&cache, text);
57
58 // Group by runs of same font
59 let mut runs: Vec<(String, Option<FontId>)> = Vec::new();
60 let mut current_text = String::new();
61 let mut current_id: Option<FontId> = None;
62
63 for (ch, info) in &resolved {
64 let this_id = info.as_ref().map(|(id, _)| *id);
65 if this_id != current_id {
66 if !current_text.is_empty() {
67 runs.push((current_text.clone(), current_id));
68 current_text.clear();
69 }
70 current_id = this_id;
71 }
72 current_text.push(*ch);
73 }
74 if !current_text.is_empty() {
75 runs.push((current_text, current_id));
76 }
77
78 println!(" Font runs:");
79 for (run_text, font_id) in &runs {
80 let name = font_id
81 .as_ref()
82 .and_then(|id| cache.get_metadata_by_id(id))
83 .and_then(|m| m.name.clone().or(m.family.clone()))
84 .unwrap_or_else(|| "[NO FONT]".into());
85 println!(" '{}' -> {}", run_text, name);
86 }
87
88 // ── Step 4: Load font bytes ──
89 let unique_fonts: std::collections::HashSet<_> =
90 runs.iter().filter_map(|(_, id)| *id).collect();
91
92 println!(
93 "\nStep 4: Load fonts ({} unique needed)\n",
94 unique_fonts.len()
95 );
96 for font_id in &unique_fonts {
97 if let Some(meta) = cache.get_metadata_by_id(font_id) {
98 let name = meta
99 .name
100 .as_ref()
101 .or(meta.family.as_ref())
102 .map(|s| s.as_str())
103 .unwrap_or("?");
104 if let Some(source) = cache.get_font_by_id(font_id) {
105 match source {
106 rust_fontconfig::FontSource::Disk(path) => {
107 println!(" {} -> {}", name, path.path);
108 }
109 rust_fontconfig::FontSource::Memory(font) => {
110 println!(" {} -> memory (id: {})", name, font.id);
111 }
112 }
113 }
114 }
115 }
116
117 println!("\nPipeline summary:");
118 println!(" 1. FcFontCache::build() — once at startup");
119 println!(" 2. cache.resolve_font_chain() — per CSS font-family");
120 println!(" 3. chain.resolve_text() — per text run");
121 println!(" 4. cache.get_font_by_id() — load bytes for shaping");
122}11fn main() {
12 println!("=== System Font Italic Race Condition Test ===\n");
13
14 // Build the cache (scans all system fonts)
15 println!("Building font cache (scanning system fonts)...");
16 let cache = FcFontCache::build();
17 let all_fonts = cache.list();
18 println!("Cache built: {} patterns\n", all_fonts.len());
19
20 // FIRST: dump ALL fonts that contain "system" or "sfns" in their name
21 println!("--- ALL fonts containing 'system' or 'sfns' in name/family ---");
22 for (pattern, id) in all_fonts {
23 let name_lower = pattern.name.as_deref().unwrap_or("").to_lowercase();
24 let family_lower = pattern.family.as_deref().unwrap_or("").to_lowercase();
25 if name_lower.contains("system") || name_lower.contains("sfns")
26 || family_lower.contains("system") || family_lower.contains("sfns") {
27 let style_score = FcFontCache::calculate_style_score(
28 &FcPattern {
29 weight: FcWeight::Normal,
30 italic: PatternMatch::False,
31 oblique: PatternMatch::False,
32 ..Default::default()
33 },
34 pattern,
35 );
36 println!(
37 " id={} name={:?} family={:?} italic={:?} bold={:?} weight={:?} stretch={:?} style_score={} subfamily={:?}",
38 id,
39 pattern.name.as_deref().unwrap_or("?"),
40 pattern.family.as_deref().unwrap_or("?"),
41 pattern.italic,
42 pattern.bold,
43 pattern.weight,
44 pattern.stretch,
45 style_score,
46 pattern.metadata.font_subfamily.as_deref().unwrap_or("?"),
47 );
48 }
49 }
50 println!();
51
52 // Query "System Font" with italic=False (what azul does for button text)
53 let families = vec!["System Font".to_string()];
54 let mut trace = Vec::new();
55
56 let chain = cache.resolve_font_chain(
57 &families,
58 FcWeight::Normal,
59 PatternMatch::False, // italic=False
60 PatternMatch::False, // oblique=False
61 &mut trace,
62 );
63
64 println!("Font chain for 'System Font' (italic=False, weight=Normal):");
65 for group in &chain.css_fallbacks {
66 println!(" CSS name: '{}'", group.css_name);
67 for (i, font_match) in group.fonts.iter().enumerate() {
68 let meta = cache.get_metadata_by_id(&font_match.id);
69 if let Some(pattern) = meta {
70 let style_score = FcFontCache::calculate_style_score(
71 &FcPattern {
72 weight: FcWeight::Normal,
73 italic: PatternMatch::False,
74 oblique: PatternMatch::False,
75 ..Default::default()
76 },
77 pattern,
78 );
79 println!(
80 " [{}] id={} name={:?} family={:?} italic={:?} bold={:?} weight={:?} style_score={} subfamily={:?}",
81 i,
82 font_match.id,
83 pattern.name.as_deref().unwrap_or("?"),
84 pattern.family.as_deref().unwrap_or("?"),
85 pattern.italic,
86 pattern.bold,
87 pattern.weight,
88 style_score,
89 pattern.metadata.font_subfamily.as_deref().unwrap_or("?"),
90 );
91 } else {
92 println!(" [{}] id={} (no metadata)", i, font_match.id);
93 }
94 }
95 }
96
97 // Verify: the first matched font should NOT be italic
98 let first_font = chain.css_fallbacks
99 .iter()
100 .flat_map(|g| g.fonts.iter())
101 .next();
102
103 if let Some(font) = first_font {
104 if let Some(meta) = cache.get_metadata_by_id(&font.id) {
105 if meta.italic == PatternMatch::True {
106 eprintln!("\n❌ FAIL: First matched font is ITALIC!");
107 eprintln!(" Font: {:?} (id={})", meta.name, font.id);
108 eprintln!(" This is the italic race condition bug.");
109 std::process::exit(1);
110 } else {
111 println!("\n✅ PASS: First matched font is NOT italic.");
112 println!(" Font: {:?} (id={})", meta.name, font.id);
113 }
114 }
115 } else {
116 eprintln!("\n⚠ WARNING: No fonts matched 'System Font'");
117 }
118
119 // Also test with DontCare (original default before the fix)
120 println!("\n--- Comparison: italic=DontCare ---");
121 let mut trace2 = Vec::new();
122 let chain2 = cache.resolve_font_chain(
123 &families,
124 FcWeight::Normal,
125 PatternMatch::DontCare,
126 PatternMatch::DontCare,
127 &mut trace2,
128 );
129
130 for group in &chain2.css_fallbacks {
131 for (i, font_match) in group.fonts.iter().enumerate() {
132 if let Some(pattern) = cache.get_metadata_by_id(&font_match.id) {
133 println!(
134 " [{}] {:?} italic={:?} weight={:?}",
135 i,
136 pattern.name.as_deref().unwrap_or("?"),
137 pattern.italic,
138 pattern.weight,
139 );
140 }
141 }
142 }
143
144 // Run the test 10 times to verify determinism
145 println!("\n--- Determinism check (10 runs) ---");
146 let mut all_same = true;
147 let mut first_result = None;
148
149 for run in 0..10 {
150 // Clear chain cache to force re-resolution
151 let mut trace_n = Vec::new();
152 let chain_n = cache.resolve_font_chain(
153 &families,
154 FcWeight::Normal,
155 PatternMatch::False,
156 PatternMatch::False,
157 &mut trace_n,
158 );
159
160 let first_id = chain_n.css_fallbacks
161 .iter()
162 .flat_map(|g| g.fonts.iter())
163 .next()
164 .map(|f| f.id);
165
166 if let Some(id) = first_id {
167 if first_result.is_none() {
168 first_result = Some(id);
169 } else if first_result != Some(id) {
170 println!(" Run {}: id={} — DIFFERENT from first run!", run, id);
171 all_same = false;
172 }
173 }
174 }
175
176 if all_same {
177 println!(" All 10 runs returned the same first font. ✅");
178 } else {
179 println!(" Results varied across runs! ❌");
180 }
181
182 println!("\n=== Test complete ===");
183}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) Memory fonts are always preferred over disk fonts with the same match quality.
Examples found in repository?
13fn main() {
14 // Build the cache — scans and parses ALL system fonts
15 let start = Instant::now();
16 let cache = FcFontCache::build();
17 let build_time = start.elapsed();
18
19 println!("Cache built: {} fonts in {:?}\n", cache.list().len(), build_time);
20
21 // Query various fonts to showcase fuzzy matching
22 let queries = [
23 ("Arial", FcWeight::Normal, "Common sans-serif"),
24 ("Helvetica", FcWeight::Bold, "Bold variant"),
25 ("Courier", FcWeight::Normal, "Monospace"),
26 ("Georgia", FcWeight::Normal, "Serif"),
27 ("sans-serif", FcWeight::Normal, "Generic family"),
28 ];
29
30 for (name, weight, desc) in &queries {
31 let t = Instant::now();
32 let result = cache.query(
33 &FcPattern {
34 name: Some(name.to_string()),
35 weight: *weight,
36 ..Default::default()
37 },
38 &mut Vec::new(),
39 );
40
41 match result {
42 Some(fm) => {
43 let found = cache
44 .get_metadata_by_id(&fm.id)
45 .and_then(|m| m.name.clone().or(m.family.clone()))
46 .unwrap_or_else(|| format!("{:?}", fm.id));
47 println!(
48 " ✓ '{}' ({}) -> {} [{:?}]",
49 name, desc, found, t.elapsed()
50 );
51 }
52 None => println!(" ✗ '{}' ({}) -> NOT FOUND [{:?}]", name, desc, t.elapsed()),
53 }
54 }
55}More examples
11fn main() {
12 let cache = FcFontCache::build();
13
14 // Create a font chain with typical web defaults
15 let families = vec!["system-ui".to_string(), "sans-serif".to_string()];
16
17 let chain = cache.resolve_font_chain(
18 &families,
19 FcWeight::Normal,
20 PatternMatch::False,
21 PatternMatch::False,
22 &mut Vec::new(),
23 );
24
25 // Test characters from different Unicode blocks
26 let test_chars = [
27 ('A', "Latin Capital Letter A"),
28 ('a', "Latin Small Letter A"),
29 ('0', "Digit Zero"),
30 ('€', "Euro Sign"),
31 ('→', "Rightwards Arrow"),
32 ('中', "CJK Ideograph - China"),
33 ('日', "CJK Ideograph - Sun/Day"),
34 ('あ', "Hiragana Letter A"),
35 ('ア', "Katakana Letter A"),
36 ('한', "Hangul Syllable Han"),
37 ('א', "Hebrew Letter Alef"),
38 ('ا', "Arabic Letter Alef"),
39 ('α', "Greek Small Letter Alpha"),
40 ('я', "Cyrillic Small Letter Ya"),
41 ('🙂', "Slightly Smiling Face"),
42 ('♠', "Black Spade Suit"),
43 ('∑', "N-ary Summation"),
44 ('∞', "Infinity"),
45 ];
46
47 println!("Character resolution results:\n");
48 println!("{:<6} {:<30} {:<40}", "Char", "Description", "Font");
49 println!("{}", "-".repeat(76));
50
51 for (ch, description) in &test_chars {
52 let text = ch.to_string();
53 let resolved = chain.resolve_text(&cache, &text);
54
55 let font_name = resolved
56 .first()
57 .and_then(|(_, info)| info.as_ref())
58 .and_then(|(id, _)| cache.get_metadata_by_id(id))
59 .and_then(|m| m.name.clone().or(m.family.clone()))
60 .unwrap_or_else(|| "⚠ NOT FOUND".to_string());
61
62 println!("{:<6} {:<30} {}", ch, description, font_name);
63 }
64
65 // Check specific font coverage
66 println!("\n\nArial coverage check:");
67 let arial_chain = cache.resolve_font_chain(
68 &["Arial".to_string()],
69 FcWeight::Normal,
70 PatternMatch::False,
71 PatternMatch::False,
72 &mut Vec::new(),
73 );
74
75 let arial_result = cache.query(
76 &rust_fontconfig::FcPattern {
77 family: Some("Arial".to_string()),
78 ..Default::default()
79 },
80 &mut Vec::new(),
81 );
82
83 if let Some(arial_match) = arial_result {
84 for ch in ['A', '中', '🙂', '→'] {
85 let resolved = arial_chain.resolve_text(&cache, &ch.to_string());
86 let in_arial = resolved
87 .first()
88 .and_then(|(_, info)| info.as_ref())
89 .map(|(id, _)| id == &arial_match.id)
90 .unwrap_or(false);
91
92 println!(
93 " {} '{}' (U+{:04X})",
94 if in_arial { "✓" } else { "✗" },
95 ch,
96 ch as u32
97 );
98 }
99 } else {
100 println!(" Arial not found on this system");
101 }
102}11fn main() {
12 println!("Building font cache...");
13 let cache = FcFontCache::build();
14 println!("Font cache built with {} fonts\n", cache.list().len());
15
16 // ── Query by family name ──
17 println!("=== Query by Family Name ===");
18 let mut trace = Vec::new();
19 let pattern = FcPattern {
20 family: Some("Arial".to_string()),
21 ..Default::default()
22 };
23
24 if let Some(result) = cache.query(&pattern, &mut trace) {
25 println!("Found Arial:");
26 println!(" Font ID: {:?}", result.id);
27 if let Some(meta) = cache.get_metadata_by_id(&result.id) {
28 println!(" Family: {:?}", meta.family);
29 println!(" Weight: {:?}", meta.weight);
30 println!(" Italic: {:?}", meta.italic);
31 }
32 if let Some(source) = cache.get_font_by_id(&result.id) {
33 match source {
34 rust_fontconfig::FontSource::Disk(path) => println!(" Path: {}", path.path),
35 rust_fontconfig::FontSource::Memory(font) => {
36 println!(" Memory font: {}", font.id)
37 }
38 }
39 }
40 } else {
41 println!("Arial not found");
42 }
43
44 // ── Query generic family ──
45 println!("\n=== Query Generic 'serif' ===");
46 trace.clear();
47 if let Some(result) = cache.query(
48 &FcPattern {
49 family: Some("serif".to_string()),
50 ..Default::default()
51 },
52 &mut trace,
53 ) {
54 if let Some(meta) = cache.get_metadata_by_id(&result.id) {
55 println!(
56 "Found: {:?}",
57 meta.name.as_ref().or(meta.family.as_ref())
58 );
59 }
60 }
61
62 // ── Query by style (bold + italic) ──
63 println!("\n=== Query Bold Italic ===");
64 trace.clear();
65 if let Some(result) = cache.query(
66 &FcPattern {
67 family: Some("sans-serif".to_string()),
68 weight: FcWeight::Bold,
69 italic: PatternMatch::True,
70 ..Default::default()
71 },
72 &mut trace,
73 ) {
74 if let Some(meta) = cache.get_metadata_by_id(&result.id) {
75 println!(
76 "Found: {:?} weight={:?} italic={:?}",
77 meta.name.as_ref().or(meta.family.as_ref()),
78 meta.weight,
79 meta.italic
80 );
81 }
82 }
83
84 // ── List bold fonts ──
85 println!("\n=== First 5 Bold Fonts ===");
86 for (meta, id) in cache
87 .list()
88 .into_iter()
89 .filter(|(m, _)| matches!(m.weight, FcWeight::Bold | FcWeight::ExtraBold | FcWeight::Black))
90 .take(5)
91 {
92 println!(
93 " {:?}: {:?}",
94 id,
95 meta.name.as_ref().or(meta.family.as_ref())
96 );
97 }
98
99 // ── Search by name substring ──
100 println!("\n=== Fonts with 'Mono' in name ===");
101 for (meta, _id) in cache.list().into_iter().filter(|(m, _)| {
102 m.name
103 .as_ref()
104 .map(|n| n.contains("Mono"))
105 .unwrap_or(false)
106 || m.family
107 .as_ref()
108 .map(|f| f.contains("Mono"))
109 .unwrap_or(false)
110 }).take(10) {
111 println!(
112 " {:?}",
113 meta.name.as_ref().or(meta.family.as_ref())
114 );
115 }
116}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 query_matches_internal(
k: &FcPattern,
pattern: &FcPattern,
trace: &mut Vec<TraceMsg>,
) -> bool
pub fn query_matches_internal( k: &FcPattern, pattern: &FcPattern, trace: &mut Vec<TraceMsg>, ) -> bool
Check if a pattern matches the query, with detailed tracing
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?
11fn main() {
12 let cache = FcFontCache::build();
13
14 println!("=== Unicode-Aware Font Selection ===\n");
15
16 // Create a font chain for sans-serif
17 let chain = cache.resolve_font_chain(
18 &["sans-serif".to_string()],
19 FcWeight::Normal,
20 PatternMatch::False,
21 PatternMatch::False,
22 &mut Vec::new(),
23 );
24
25 println!(
26 "Font chain: {} CSS fallbacks, {} unicode fallbacks\n",
27 chain.css_fallbacks.len(),
28 chain.unicode_fallbacks.len()
29 );
30
31 // Resolve different scripts
32 let texts = [
33 ("Latin", "Hello World"),
34 ("CJK", "你好世界"),
35 ("Japanese", "こんにちは世界"),
36 ("Arabic", "مرحبا بالعالم"),
37 ("Cyrillic", "Привет мир"),
38 ("Mixed", "Hello 世界 Привет"),
39 ];
40
41 for (label, text) in &texts {
42 println!("{} text: '{}'", label, text);
43 print_resolution(&cache, &chain, text);
44 println!();
45 }
46
47 println!("Workflow:");
48 println!(" 1. resolve_font_chain() — creates fallback chain from CSS font-family");
49 println!(" 2. chain.resolve_text() — maps each character to a font");
50 println!(" 3. Use font IDs to load and render glyphs");
51}More examples
11fn main() {
12 let cache = FcFontCache::build();
13
14 // Create a font chain with typical web defaults
15 let families = vec!["system-ui".to_string(), "sans-serif".to_string()];
16
17 let chain = cache.resolve_font_chain(
18 &families,
19 FcWeight::Normal,
20 PatternMatch::False,
21 PatternMatch::False,
22 &mut Vec::new(),
23 );
24
25 // Test characters from different Unicode blocks
26 let test_chars = [
27 ('A', "Latin Capital Letter A"),
28 ('a', "Latin Small Letter A"),
29 ('0', "Digit Zero"),
30 ('€', "Euro Sign"),
31 ('→', "Rightwards Arrow"),
32 ('中', "CJK Ideograph - China"),
33 ('日', "CJK Ideograph - Sun/Day"),
34 ('あ', "Hiragana Letter A"),
35 ('ア', "Katakana Letter A"),
36 ('한', "Hangul Syllable Han"),
37 ('א', "Hebrew Letter Alef"),
38 ('ا', "Arabic Letter Alef"),
39 ('α', "Greek Small Letter Alpha"),
40 ('я', "Cyrillic Small Letter Ya"),
41 ('🙂', "Slightly Smiling Face"),
42 ('♠', "Black Spade Suit"),
43 ('∑', "N-ary Summation"),
44 ('∞', "Infinity"),
45 ];
46
47 println!("Character resolution results:\n");
48 println!("{:<6} {:<30} {:<40}", "Char", "Description", "Font");
49 println!("{}", "-".repeat(76));
50
51 for (ch, description) in &test_chars {
52 let text = ch.to_string();
53 let resolved = chain.resolve_text(&cache, &text);
54
55 let font_name = resolved
56 .first()
57 .and_then(|(_, info)| info.as_ref())
58 .and_then(|(id, _)| cache.get_metadata_by_id(id))
59 .and_then(|m| m.name.clone().or(m.family.clone()))
60 .unwrap_or_else(|| "⚠ NOT FOUND".to_string());
61
62 println!("{:<6} {:<30} {}", ch, description, font_name);
63 }
64
65 // Check specific font coverage
66 println!("\n\nArial coverage check:");
67 let arial_chain = cache.resolve_font_chain(
68 &["Arial".to_string()],
69 FcWeight::Normal,
70 PatternMatch::False,
71 PatternMatch::False,
72 &mut Vec::new(),
73 );
74
75 let arial_result = cache.query(
76 &rust_fontconfig::FcPattern {
77 family: Some("Arial".to_string()),
78 ..Default::default()
79 },
80 &mut Vec::new(),
81 );
82
83 if let Some(arial_match) = arial_result {
84 for ch in ['A', '中', '🙂', '→'] {
85 let resolved = arial_chain.resolve_text(&cache, &ch.to_string());
86 let in_arial = resolved
87 .first()
88 .and_then(|(_, info)| info.as_ref())
89 .map(|(id, _)| id == &arial_match.id)
90 .unwrap_or(false);
91
92 println!(
93 " {} '{}' (U+{:04X})",
94 if in_arial { "✓" } else { "✗" },
95 ch,
96 ch as u32
97 );
98 }
99 } else {
100 println!(" Arial not found on this system");
101 }
102}11fn main() {
12 println!("=== Font Integration Pipeline ===\n");
13
14 // ── Step 1: Build font cache ──
15 println!("Step 1: Build font cache");
16 let cache = FcFontCache::build();
17 println!(" {} fonts loaded\n", cache.list().len());
18
19 // ── Step 2: Resolve CSS font-family ──
20 let css_families = vec![
21 "Helvetica".to_string(),
22 "Arial".to_string(),
23 "sans-serif".to_string(),
24 ];
25 println!("Step 2: Resolve font-family: {:?}\n", css_families);
26
27 let chain = cache.resolve_font_chain(
28 &css_families,
29 FcWeight::Normal,
30 PatternMatch::False,
31 PatternMatch::False,
32 &mut Vec::new(),
33 );
34
35 for (i, group) in chain.css_fallbacks.iter().enumerate() {
36 print!(" [{}] '{}': {} fonts", i + 1, group.css_name, group.fonts.len());
37 if let Some(first) = group.fonts.first() {
38 if let Some(meta) = cache.get_metadata_by_id(&first.id) {
39 print!(
40 " (first: {:?})",
41 meta.name.as_ref().or(meta.family.as_ref())
42 );
43 }
44 }
45 println!();
46 }
47 println!(
48 " + {} unicode fallback fonts\n",
49 chain.unicode_fallbacks.len()
50 );
51
52 // ── Step 3: Resolve text to font runs ──
53 let text = "Hello 世界! Привет мир";
54 println!("Step 3: Resolve text: '{}'\n", text);
55
56 let resolved = chain.resolve_text(&cache, text);
57
58 // Group by runs of same font
59 let mut runs: Vec<(String, Option<FontId>)> = Vec::new();
60 let mut current_text = String::new();
61 let mut current_id: Option<FontId> = None;
62
63 for (ch, info) in &resolved {
64 let this_id = info.as_ref().map(|(id, _)| *id);
65 if this_id != current_id {
66 if !current_text.is_empty() {
67 runs.push((current_text.clone(), current_id));
68 current_text.clear();
69 }
70 current_id = this_id;
71 }
72 current_text.push(*ch);
73 }
74 if !current_text.is_empty() {
75 runs.push((current_text, current_id));
76 }
77
78 println!(" Font runs:");
79 for (run_text, font_id) in &runs {
80 let name = font_id
81 .as_ref()
82 .and_then(|id| cache.get_metadata_by_id(id))
83 .and_then(|m| m.name.clone().or(m.family.clone()))
84 .unwrap_or_else(|| "[NO FONT]".into());
85 println!(" '{}' -> {}", run_text, name);
86 }
87
88 // ── Step 4: Load font bytes ──
89 let unique_fonts: std::collections::HashSet<_> =
90 runs.iter().filter_map(|(_, id)| *id).collect();
91
92 println!(
93 "\nStep 4: Load fonts ({} unique needed)\n",
94 unique_fonts.len()
95 );
96 for font_id in &unique_fonts {
97 if let Some(meta) = cache.get_metadata_by_id(font_id) {
98 let name = meta
99 .name
100 .as_ref()
101 .or(meta.family.as_ref())
102 .map(|s| s.as_str())
103 .unwrap_or("?");
104 if let Some(source) = cache.get_font_by_id(font_id) {
105 match source {
106 rust_fontconfig::FontSource::Disk(path) => {
107 println!(" {} -> {}", name, path.path);
108 }
109 rust_fontconfig::FontSource::Memory(font) => {
110 println!(" {} -> memory (id: {})", name, font.id);
111 }
112 }
113 }
114 }
115 }
116
117 println!("\nPipeline summary:");
118 println!(" 1. FcFontCache::build() — once at startup");
119 println!(" 2. cache.resolve_font_chain() — per CSS font-family");
120 println!(" 3. chain.resolve_text() — per text run");
121 println!(" 4. cache.get_font_by_id() — load bytes for shaping");
122}11fn main() {
12 println!("=== System Font Italic Race Condition Test ===\n");
13
14 // Build the cache (scans all system fonts)
15 println!("Building font cache (scanning system fonts)...");
16 let cache = FcFontCache::build();
17 let all_fonts = cache.list();
18 println!("Cache built: {} patterns\n", all_fonts.len());
19
20 // FIRST: dump ALL fonts that contain "system" or "sfns" in their name
21 println!("--- ALL fonts containing 'system' or 'sfns' in name/family ---");
22 for (pattern, id) in all_fonts {
23 let name_lower = pattern.name.as_deref().unwrap_or("").to_lowercase();
24 let family_lower = pattern.family.as_deref().unwrap_or("").to_lowercase();
25 if name_lower.contains("system") || name_lower.contains("sfns")
26 || family_lower.contains("system") || family_lower.contains("sfns") {
27 let style_score = FcFontCache::calculate_style_score(
28 &FcPattern {
29 weight: FcWeight::Normal,
30 italic: PatternMatch::False,
31 oblique: PatternMatch::False,
32 ..Default::default()
33 },
34 pattern,
35 );
36 println!(
37 " id={} name={:?} family={:?} italic={:?} bold={:?} weight={:?} stretch={:?} style_score={} subfamily={:?}",
38 id,
39 pattern.name.as_deref().unwrap_or("?"),
40 pattern.family.as_deref().unwrap_or("?"),
41 pattern.italic,
42 pattern.bold,
43 pattern.weight,
44 pattern.stretch,
45 style_score,
46 pattern.metadata.font_subfamily.as_deref().unwrap_or("?"),
47 );
48 }
49 }
50 println!();
51
52 // Query "System Font" with italic=False (what azul does for button text)
53 let families = vec!["System Font".to_string()];
54 let mut trace = Vec::new();
55
56 let chain = cache.resolve_font_chain(
57 &families,
58 FcWeight::Normal,
59 PatternMatch::False, // italic=False
60 PatternMatch::False, // oblique=False
61 &mut trace,
62 );
63
64 println!("Font chain for 'System Font' (italic=False, weight=Normal):");
65 for group in &chain.css_fallbacks {
66 println!(" CSS name: '{}'", group.css_name);
67 for (i, font_match) in group.fonts.iter().enumerate() {
68 let meta = cache.get_metadata_by_id(&font_match.id);
69 if let Some(pattern) = meta {
70 let style_score = FcFontCache::calculate_style_score(
71 &FcPattern {
72 weight: FcWeight::Normal,
73 italic: PatternMatch::False,
74 oblique: PatternMatch::False,
75 ..Default::default()
76 },
77 pattern,
78 );
79 println!(
80 " [{}] id={} name={:?} family={:?} italic={:?} bold={:?} weight={:?} style_score={} subfamily={:?}",
81 i,
82 font_match.id,
83 pattern.name.as_deref().unwrap_or("?"),
84 pattern.family.as_deref().unwrap_or("?"),
85 pattern.italic,
86 pattern.bold,
87 pattern.weight,
88 style_score,
89 pattern.metadata.font_subfamily.as_deref().unwrap_or("?"),
90 );
91 } else {
92 println!(" [{}] id={} (no metadata)", i, font_match.id);
93 }
94 }
95 }
96
97 // Verify: the first matched font should NOT be italic
98 let first_font = chain.css_fallbacks
99 .iter()
100 .flat_map(|g| g.fonts.iter())
101 .next();
102
103 if let Some(font) = first_font {
104 if let Some(meta) = cache.get_metadata_by_id(&font.id) {
105 if meta.italic == PatternMatch::True {
106 eprintln!("\n❌ FAIL: First matched font is ITALIC!");
107 eprintln!(" Font: {:?} (id={})", meta.name, font.id);
108 eprintln!(" This is the italic race condition bug.");
109 std::process::exit(1);
110 } else {
111 println!("\n✅ PASS: First matched font is NOT italic.");
112 println!(" Font: {:?} (id={})", meta.name, font.id);
113 }
114 }
115 } else {
116 eprintln!("\n⚠ WARNING: No fonts matched 'System Font'");
117 }
118
119 // Also test with DontCare (original default before the fix)
120 println!("\n--- Comparison: italic=DontCare ---");
121 let mut trace2 = Vec::new();
122 let chain2 = cache.resolve_font_chain(
123 &families,
124 FcWeight::Normal,
125 PatternMatch::DontCare,
126 PatternMatch::DontCare,
127 &mut trace2,
128 );
129
130 for group in &chain2.css_fallbacks {
131 for (i, font_match) in group.fonts.iter().enumerate() {
132 if let Some(pattern) = cache.get_metadata_by_id(&font_match.id) {
133 println!(
134 " [{}] {:?} italic={:?} weight={:?}",
135 i,
136 pattern.name.as_deref().unwrap_or("?"),
137 pattern.italic,
138 pattern.weight,
139 );
140 }
141 }
142 }
143
144 // Run the test 10 times to verify determinism
145 println!("\n--- Determinism check (10 runs) ---");
146 let mut all_same = true;
147 let mut first_result = None;
148
149 for run in 0..10 {
150 // Clear chain cache to force re-resolution
151 let mut trace_n = Vec::new();
152 let chain_n = cache.resolve_font_chain(
153 &families,
154 FcWeight::Normal,
155 PatternMatch::False,
156 PatternMatch::False,
157 &mut trace_n,
158 );
159
160 let first_id = chain_n.css_fallbacks
161 .iter()
162 .flat_map(|g| g.fonts.iter())
163 .next()
164 .map(|f| f.id);
165
166 if let Some(id) = first_id {
167 if first_result.is_none() {
168 first_result = Some(id);
169 } else if first_result != Some(id) {
170 println!(" Run {}: id={} — DIFFERENT from first run!", run, id);
171 all_same = false;
172 }
173 }
174 }
175
176 if all_same {
177 println!(" All 10 runs returned the same first font. ✅");
178 } else {
179 println!(" Results varied across runs! ❌");
180 }
181
182 println!("\n=== Test complete ===");
183}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)
Sourcepub fn extract_font_name_tokens(name: &str) -> Vec<String>
pub fn extract_font_name_tokens(name: &str) -> Vec<String>
Extract tokens from a font name E.g., “NotoSansJP” -> [“Noto”, “Sans”, “JP”] E.g., “Noto Sans CJK JP” -> [“Noto”, “Sans”, “CJK”, “JP”]
Sourcepub fn calculate_unicode_coverage(ranges: &[UnicodeRange]) -> u64
pub fn calculate_unicode_coverage(ranges: &[UnicodeRange]) -> u64
Find fallback fonts for a given pattern
Sourcepub fn calculate_unicode_compatibility(
requested: &[UnicodeRange],
available: &[UnicodeRange],
) -> i32
pub fn calculate_unicode_compatibility( requested: &[UnicodeRange], available: &[UnicodeRange], ) -> i32
Calculate how well a font’s Unicode ranges cover the requested ranges Returns a compatibility score (higher is better, 0 means no overlap)
Sourcepub fn calculate_style_score(original: &FcPattern, candidate: &FcPattern) -> i32
pub fn calculate_style_score(original: &FcPattern, candidate: &FcPattern) -> i32
Examples found in repository?
11fn main() {
12 println!("=== System Font Italic Race Condition Test ===\n");
13
14 // Build the cache (scans all system fonts)
15 println!("Building font cache (scanning system fonts)...");
16 let cache = FcFontCache::build();
17 let all_fonts = cache.list();
18 println!("Cache built: {} patterns\n", all_fonts.len());
19
20 // FIRST: dump ALL fonts that contain "system" or "sfns" in their name
21 println!("--- ALL fonts containing 'system' or 'sfns' in name/family ---");
22 for (pattern, id) in all_fonts {
23 let name_lower = pattern.name.as_deref().unwrap_or("").to_lowercase();
24 let family_lower = pattern.family.as_deref().unwrap_or("").to_lowercase();
25 if name_lower.contains("system") || name_lower.contains("sfns")
26 || family_lower.contains("system") || family_lower.contains("sfns") {
27 let style_score = FcFontCache::calculate_style_score(
28 &FcPattern {
29 weight: FcWeight::Normal,
30 italic: PatternMatch::False,
31 oblique: PatternMatch::False,
32 ..Default::default()
33 },
34 pattern,
35 );
36 println!(
37 " id={} name={:?} family={:?} italic={:?} bold={:?} weight={:?} stretch={:?} style_score={} subfamily={:?}",
38 id,
39 pattern.name.as_deref().unwrap_or("?"),
40 pattern.family.as_deref().unwrap_or("?"),
41 pattern.italic,
42 pattern.bold,
43 pattern.weight,
44 pattern.stretch,
45 style_score,
46 pattern.metadata.font_subfamily.as_deref().unwrap_or("?"),
47 );
48 }
49 }
50 println!();
51
52 // Query "System Font" with italic=False (what azul does for button text)
53 let families = vec!["System Font".to_string()];
54 let mut trace = Vec::new();
55
56 let chain = cache.resolve_font_chain(
57 &families,
58 FcWeight::Normal,
59 PatternMatch::False, // italic=False
60 PatternMatch::False, // oblique=False
61 &mut trace,
62 );
63
64 println!("Font chain for 'System Font' (italic=False, weight=Normal):");
65 for group in &chain.css_fallbacks {
66 println!(" CSS name: '{}'", group.css_name);
67 for (i, font_match) in group.fonts.iter().enumerate() {
68 let meta = cache.get_metadata_by_id(&font_match.id);
69 if let Some(pattern) = meta {
70 let style_score = FcFontCache::calculate_style_score(
71 &FcPattern {
72 weight: FcWeight::Normal,
73 italic: PatternMatch::False,
74 oblique: PatternMatch::False,
75 ..Default::default()
76 },
77 pattern,
78 );
79 println!(
80 " [{}] id={} name={:?} family={:?} italic={:?} bold={:?} weight={:?} style_score={} subfamily={:?}",
81 i,
82 font_match.id,
83 pattern.name.as_deref().unwrap_or("?"),
84 pattern.family.as_deref().unwrap_or("?"),
85 pattern.italic,
86 pattern.bold,
87 pattern.weight,
88 style_score,
89 pattern.metadata.font_subfamily.as_deref().unwrap_or("?"),
90 );
91 } else {
92 println!(" [{}] id={} (no metadata)", i, font_match.id);
93 }
94 }
95 }
96
97 // Verify: the first matched font should NOT be italic
98 let first_font = chain.css_fallbacks
99 .iter()
100 .flat_map(|g| g.fonts.iter())
101 .next();
102
103 if let Some(font) = first_font {
104 if let Some(meta) = cache.get_metadata_by_id(&font.id) {
105 if meta.italic == PatternMatch::True {
106 eprintln!("\n❌ FAIL: First matched font is ITALIC!");
107 eprintln!(" Font: {:?} (id={})", meta.name, font.id);
108 eprintln!(" This is the italic race condition bug.");
109 std::process::exit(1);
110 } else {
111 println!("\n✅ PASS: First matched font is NOT italic.");
112 println!(" Font: {:?} (id={})", meta.name, font.id);
113 }
114 }
115 } else {
116 eprintln!("\n⚠ WARNING: No fonts matched 'System Font'");
117 }
118
119 // Also test with DontCare (original default before the fix)
120 println!("\n--- Comparison: italic=DontCare ---");
121 let mut trace2 = Vec::new();
122 let chain2 = cache.resolve_font_chain(
123 &families,
124 FcWeight::Normal,
125 PatternMatch::DontCare,
126 PatternMatch::DontCare,
127 &mut trace2,
128 );
129
130 for group in &chain2.css_fallbacks {
131 for (i, font_match) in group.fonts.iter().enumerate() {
132 if let Some(pattern) = cache.get_metadata_by_id(&font_match.id) {
133 println!(
134 " [{}] {:?} italic={:?} weight={:?}",
135 i,
136 pattern.name.as_deref().unwrap_or("?"),
137 pattern.italic,
138 pattern.weight,
139 );
140 }
141 }
142 }
143
144 // Run the test 10 times to verify determinism
145 println!("\n--- Determinism check (10 runs) ---");
146 let mut all_same = true;
147 let mut first_result = None;
148
149 for run in 0..10 {
150 // Clear chain cache to force re-resolution
151 let mut trace_n = Vec::new();
152 let chain_n = cache.resolve_font_chain(
153 &families,
154 FcWeight::Normal,
155 PatternMatch::False,
156 PatternMatch::False,
157 &mut trace_n,
158 );
159
160 let first_id = chain_n.css_fallbacks
161 .iter()
162 .flat_map(|g| g.fonts.iter())
163 .next()
164 .map(|f| f.id);
165
166 if let Some(id) = first_id {
167 if first_result.is_none() {
168 first_result = Some(id);
169 } else if first_result != Some(id) {
170 println!(" Run {}: id={} — DIFFERENT from first run!", run, id);
171 all_same = false;
172 }
173 }
174 }
175
176 if all_same {
177 println!(" All 10 runs returned the same first font. ✅");
178 } else {
179 println!(" Results varied across runs! ❌");
180 }
181
182 println!("\n=== Test complete ===");
183}Trait Implementations§
Source§impl Clone for FcFontCache
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