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