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