Skip to main content

text_typeset/font/
resolve.rs

1use crate::font::registry::FontRegistry;
2use crate::types::FontFaceId;
3
4/// A resolved font face with all parameters needed for shaping and rasterization.
5pub struct ResolvedFont {
6    pub font_face_id: FontFaceId,
7    pub size_px: f32,
8    pub face_index: u32,
9    pub swash_cache_key: swash::CacheKey,
10}
11
12/// Resolve a font from text formatting parameters.
13///
14/// The resolution order is:
15/// 1. If font_family is set, query by family name (with generic mapping)
16/// 2. Apply font_weight (or font_bold as weight 700)
17/// 3. Apply font_italic
18/// 4. Fall back to the default font if no match
19pub fn resolve_font(
20    registry: &FontRegistry,
21    font_family: Option<&str>,
22    font_weight: Option<u32>,
23    font_bold: Option<bool>,
24    font_italic: Option<bool>,
25    font_point_size: Option<u32>,
26) -> Option<ResolvedFont> {
27    let weight = resolve_weight(font_weight, font_bold);
28    let italic = font_italic.unwrap_or(false);
29    let size_px = font_point_size
30        .map(|s| s as f32)
31        .unwrap_or(registry.default_size_px());
32
33    // Try the specified family first
34    if let Some(family) = font_family
35        && let Some(face_id) = registry.query_font(family, weight, italic)
36    {
37        let entry = registry.get(face_id)?;
38        return Some(ResolvedFont {
39            font_face_id: face_id,
40            size_px,
41            face_index: entry.face_index,
42            swash_cache_key: entry.swash_cache_key,
43        });
44    }
45
46    // Fall back to default font, but still try to match weight/italic
47    // by querying for a variant of the default font's family.
48    let default_id = registry.default_font()?;
49    if (weight != 400 || italic)
50        && let Some(variant_id) = registry.query_variant(default_id, weight, italic)
51    {
52        let variant_entry = registry.get(variant_id)?;
53        return Some(ResolvedFont {
54            font_face_id: variant_id,
55            size_px,
56            face_index: variant_entry.face_index,
57            swash_cache_key: variant_entry.swash_cache_key,
58        });
59    }
60    let entry = registry.get(default_id)?;
61    Some(ResolvedFont {
62        font_face_id: default_id,
63        size_px,
64        face_index: entry.face_index,
65        swash_cache_key: entry.swash_cache_key,
66    })
67}
68
69/// Check if a font has a glyph for the given character.
70/// Used for glyph fallback -trying other registered fonts when
71/// the primary font doesn't cover a character.
72pub fn font_has_glyph(registry: &FontRegistry, face_id: FontFaceId, ch: char) -> bool {
73    let entry = match registry.get(face_id) {
74        Some(e) => e,
75        None => return false,
76    };
77    let font_ref = match swash::FontRef::from_index(&entry.data, entry.face_index as usize) {
78        Some(f) => f,
79        None => return false,
80    };
81    font_ref.charmap().map(ch) != 0
82}
83
84/// Find a fallback font that has the given character.
85pub fn find_fallback_font(
86    registry: &FontRegistry,
87    ch: char,
88    exclude: FontFaceId,
89) -> Option<FontFaceId> {
90    for (face_id, entry) in registry.all_entries() {
91        if face_id == exclude {
92            continue;
93        }
94        let font_ref = match swash::FontRef::from_index(&entry.data, entry.face_index as usize) {
95            Some(f) => f,
96            None => continue,
97        };
98        if font_ref.charmap().map(ch) != 0 {
99            return Some(face_id);
100        }
101    }
102    None
103}
104
105/// Convert TextFormat weight fields to a u16 weight value for fontdb.
106fn resolve_weight(font_weight: Option<u32>, font_bold: Option<bool>) -> u16 {
107    if let Some(w) = font_weight {
108        return w.min(1000) as u16;
109    }
110    if font_bold == Some(true) {
111        return 700;
112    }
113    400 // Normal weight
114}