pub struct FontFallbackChain {
pub css_fallbacks: Vec<CssFallbackGroup>,
pub unicode_fallbacks: Vec<FontMatch>,
pub original_stack: Vec<String>,
}Expand description
Resolved font fallback chain for a CSS font-family stack This represents the complete chain of fonts to use for rendering text
Fields§
§css_fallbacks: Vec<CssFallbackGroup>CSS-based fallbacks: Each CSS font expanded to its system fallbacks Example: [“NotoSansJP” -> [Hiragino Sans, PingFang SC], “sans-serif” -> [Helvetica]]
unicode_fallbacks: Vec<FontMatch>Unicode-based fallbacks: Fonts added to cover missing Unicode ranges Only populated if css_fallbacks don’t cover all requested characters
original_stack: Vec<String>The original CSS font-family stack that was requested
Implementations§
Source§impl FontFallbackChain
impl FontFallbackChain
Sourcepub fn resolve_char(
&self,
cache: &FcFontCache,
ch: char,
) -> Option<(FontId, String)>
pub fn resolve_char( &self, cache: &FcFontCache, ch: char, ) -> Option<(FontId, String)>
Resolve which font should be used for a specific character Returns (FontId, css_source_name) where css_source_name indicates which CSS font matched Returns None if no font in the chain can render this character
Sourcepub fn resolve_text(
&self,
cache: &FcFontCache,
text: &str,
) -> Vec<(char, Option<(FontId, String)>)>
pub fn resolve_text( &self, cache: &FcFontCache, text: &str, ) -> Vec<(char, Option<(FontId, String)>)>
Resolve all characters in a text string to their fonts Returns a vector of (character, FontId, css_source) tuples
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
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 query_for_text(
&self,
cache: &FcFontCache,
text: &str,
) -> Vec<ResolvedFontRun>
pub fn query_for_text( &self, cache: &FcFontCache, text: &str, ) -> Vec<ResolvedFontRun>
Query which fonts should be used for a text string, grouped by font Returns runs of consecutive characters that use the same font This is the main API for text shaping - call this to get font runs, then shape each run
Trait Implementations§
Source§impl Clone for FontFallbackChain
impl Clone for FontFallbackChain
Source§fn clone(&self) -> FontFallbackChain
fn clone(&self) -> FontFallbackChain
1.0.0 · Source§fn clone_from(&mut self, source: &Self)
fn clone_from(&mut self, source: &Self)
source. Read moreSource§impl Debug for FontFallbackChain
impl Debug for FontFallbackChain
Source§impl PartialEq for FontFallbackChain
impl PartialEq for FontFallbackChain
impl Eq for FontFallbackChain
impl StructuralPartialEq for FontFallbackChain
Auto Trait Implementations§
impl Freeze for FontFallbackChain
impl RefUnwindSafe for FontFallbackChain
impl Send for FontFallbackChain
impl Sync for FontFallbackChain
impl Unpin for FontFallbackChain
impl UnwindSafe for FontFallbackChain
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