FontFallbackChain

Struct FontFallbackChain 

Source
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

Source

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

Source

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

pub fn query_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

Source§

fn clone(&self) -> FontFallbackChain

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

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

Performs copy-assignment from source. Read more
Source§

impl Debug for FontFallbackChain

Source§

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

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

impl PartialEq for FontFallbackChain

Source§

fn eq(&self, other: &FontFallbackChain) -> bool

Tests for self and other values to be equal, and is used by ==.
1.0.0 · Source§

fn ne(&self, other: &Rhs) -> bool

Tests for !=. The default implementation is almost always sufficient, and should not be overridden without very good reason.
Source§

impl Eq for FontFallbackChain

Source§

impl StructuralPartialEq for FontFallbackChain

Auto Trait Implementations§

Blanket Implementations§

Source§

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

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

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

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

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

Source§

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

Mutably borrows from an owned value. Read more
Source§

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

Source§

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

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

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

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

Source§

fn into(self) -> U

Calls U::from(self).

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

Source§

impl<T> IntoEither for T

Source§

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

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

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

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

impl<T> Pointable for T

Source§

const ALIGN: usize

The alignment of pointer.
Source§

type Init = T

The type for initializers.
Source§

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

Initializes a with the given initializer. Read more
Source§

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

Dereferences the given pointer. Read more
Source§

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

Mutably dereferences the given pointer. Read more
Source§

unsafe fn drop(ptr: usize)

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

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

Source§

type Owned = T

The resulting type after obtaining ownership.
Source§

fn to_owned(&self) -> T

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

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

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

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

Source§

type Error = Infallible

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

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

Performs the conversion.
Source§

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

Source§

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

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

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

Performs the conversion.