Skip to main content

i_slint_renderer_software/
fonts.rs

1// Copyright © SixtyFPS GmbH <info@slint.dev>
2// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0
3
4use alloc::rc::Rc;
5use alloc::vec::Vec;
6use core::cell::RefCell;
7
8use super::{Fixed, PhysicalLength, PhysicalSize};
9use i_slint_core::Coord;
10use i_slint_core::graphics::{BitmapFont, FontRequest};
11use i_slint_core::lengths::{LogicalLength, ScaleFactor};
12use i_slint_core::textlayout::TextLayout;
13
14i_slint_core::thread_local! {
15    static BITMAP_FONTS: RefCell<Vec<&'static BitmapFont>> = RefCell::default()
16}
17
18#[derive(derive_more::From, Clone)]
19pub enum GlyphAlphaMap {
20    Static(&'static [u8]),
21    Shared(Rc<[u8]>),
22}
23
24#[derive(Clone)]
25pub struct RenderableGlyph {
26    pub x: Fixed<i32, 8>,
27    pub y: Fixed<i32, 8>,
28    pub width: PhysicalLength,
29    pub height: PhysicalLength,
30    pub alpha_map: GlyphAlphaMap,
31    pub pixel_stride: u16,
32    pub sdf: bool,
33}
34
35impl RenderableGlyph {
36    pub fn size(&self) -> PhysicalSize {
37        PhysicalSize::from_lengths(self.width, self.height)
38    }
39}
40
41// Subset of `RenderableGlyph`, specfically for VectorFonts.
42#[cfg(feature = "systemfonts")]
43#[derive(Clone)]
44pub struct RenderableVectorGlyph {
45    pub x: Fixed<i32, 8>,
46    pub y: Fixed<i32, 8>,
47    pub width: PhysicalLength,
48    pub height: PhysicalLength,
49    pub alpha_map: Rc<[u8]>,
50    pub pixel_stride: u16,
51    pub glyph_origin_x: f32,
52}
53
54#[cfg(feature = "systemfonts")]
55impl RenderableVectorGlyph {
56    pub fn size(&self) -> PhysicalSize {
57        PhysicalSize::from_lengths(self.width, self.height)
58    }
59}
60
61pub trait GlyphRenderer {
62    fn render_glyph(
63        &self,
64        glyph_id: core::num::NonZeroU16,
65        slint_context: &i_slint_core::SlintContext,
66    ) -> Option<RenderableGlyph>;
67    /// The amount of pixel in the original image that correspond to one pixel in the rendered image
68    fn scale_delta(&self) -> Fixed<u16, 8>;
69}
70
71pub(super) const DEFAULT_FONT_SIZE: LogicalLength = LogicalLength::new(12 as Coord);
72
73mod pixelfont;
74#[cfg(feature = "systemfonts")]
75pub mod vectorfont;
76
77#[cfg(feature = "systemfonts")]
78pub mod systemfonts;
79
80#[derive(derive_more::From)]
81pub enum Font {
82    PixelFont(pixelfont::PixelFont),
83    #[cfg(feature = "systemfonts")]
84    VectorFont(vectorfont::VectorFont),
85}
86
87/// Returns the size of the pre-rendered font in pixels.
88pub fn pixel_size(glyphs: &i_slint_core::graphics::BitmapGlyphs) -> PhysicalLength {
89    PhysicalLength::new(glyphs.pixel_size)
90}
91
92impl i_slint_core::textlayout::FontMetrics<PhysicalLength> for Font {
93    fn ascent(&self) -> PhysicalLength {
94        match self {
95            Font::PixelFont(pixel_font) => pixel_font.ascent(),
96            #[cfg(feature = "systemfonts")]
97            Font::VectorFont(vector_font) => vector_font.ascent(),
98        }
99    }
100
101    fn height(&self) -> PhysicalLength {
102        match self {
103            Font::PixelFont(pixel_font) => pixel_font.height(),
104            #[cfg(feature = "systemfonts")]
105            Font::VectorFont(vector_font) => vector_font.height(),
106        }
107    }
108
109    fn descent(&self) -> PhysicalLength {
110        match self {
111            Font::PixelFont(pixel_font) => pixel_font.descent(),
112            #[cfg(feature = "systemfonts")]
113            Font::VectorFont(vector_font) => vector_font.descent(),
114        }
115    }
116
117    fn x_height(&self) -> PhysicalLength {
118        match self {
119            Font::PixelFont(pixel_font) => pixel_font.x_height(),
120            #[cfg(feature = "systemfonts")]
121            Font::VectorFont(vector_font) => vector_font.x_height(),
122        }
123    }
124
125    fn cap_height(&self) -> PhysicalLength {
126        match self {
127            Font::PixelFont(pixel_font) => pixel_font.cap_height(),
128            #[cfg(feature = "systemfonts")]
129            Font::VectorFont(vector_font) => vector_font.cap_height(),
130        }
131    }
132}
133
134pub fn match_font(
135    request: &FontRequest,
136    scale_factor: ScaleFactor,
137    #[cfg(feature = "systemfonts")]
138    font_context: &mut i_slint_core::textlayout::sharedparley::parley::FontContext,
139) -> Font {
140    let requested_weight = request
141        .weight
142        .and_then(|weight| weight.try_into().ok())
143        .unwrap_or(/* CSS normal */ 400);
144
145    let bitmap_font = BITMAP_FONTS.with(|fonts| {
146        let fonts = fonts.borrow();
147
148        request.family.as_ref().and_then(|requested_family| {
149            fonts
150                .iter()
151                .filter(|bitmap_font| {
152                    core::str::from_utf8(bitmap_font.family_name.as_slice()).unwrap()
153                        == requested_family.as_str()
154                        && bitmap_font.italic == request.italic
155                })
156                .min_by_key(|bitmap_font| bitmap_font.weight.abs_diff(requested_weight))
157                .copied()
158        })
159    });
160
161    let font = match bitmap_font {
162        Some(bitmap_font) => bitmap_font,
163        None => {
164            #[cfg(feature = "systemfonts")]
165            if let Some(vectorfont) = systemfonts::match_font(
166                request,
167                scale_factor,
168                &mut font_context.collection,
169                &mut font_context.source_cache,
170            ) {
171                return vectorfont.into();
172            }
173            if let Some(fallback_bitmap_font) = BITMAP_FONTS.with(|fonts| {
174                let fonts = fonts.borrow();
175                fonts
176                    .iter()
177                    .cloned()
178                    .filter(|bitmap_font| bitmap_font.italic == request.italic)
179                    .min_by_key(|bitmap_font| bitmap_font.weight.abs_diff(requested_weight))
180                    .or_else(|| fonts.first().cloned())
181            }) {
182                fallback_bitmap_font
183            } else {
184                #[cfg(feature = "systemfonts")]
185                return systemfonts::fallbackfont(
186                    request,
187                    scale_factor,
188                    &mut font_context.collection,
189                    &mut font_context.source_cache,
190                )
191                .into();
192                #[cfg(not(feature = "systemfonts"))]
193                panic!(
194                    "No font fallback found. The software renderer requires enabling the `EmbedForSoftwareRenderer` option when compiling slint files."
195                )
196            }
197        }
198    };
199
200    let requested_pixel_size: PhysicalLength =
201        (request.pixel_size.unwrap_or(DEFAULT_FONT_SIZE).cast() * scale_factor).cast();
202
203    let nearest_pixel_size = font
204        .glyphs
205        .partition_point(|glyphs| pixel_size(glyphs) <= requested_pixel_size)
206        .saturating_sub(1);
207    let matching_glyphs = &font.glyphs[nearest_pixel_size];
208
209    let pixel_size = if font.sdf { requested_pixel_size } else { pixel_size(matching_glyphs) };
210
211    pixelfont::PixelFont { bitmap_font: font, glyphs: matching_glyphs, pixel_size }.into()
212}
213
214pub fn text_layout_for_font<'a, Font>(
215    font: &'a Font,
216    font_request: &FontRequest,
217    scale_factor: ScaleFactor,
218) -> TextLayout<'a, Font>
219where
220    Font: i_slint_core::textlayout::AbstractFont
221        + i_slint_core::textlayout::TextShaper<Length = PhysicalLength>,
222{
223    let letter_spacing =
224        font_request.letter_spacing.map(|spacing| (spacing.cast() * scale_factor).cast());
225
226    TextLayout { font, letter_spacing }
227}
228
229pub fn register_bitmap_font(font_data: &'static BitmapFont) {
230    BITMAP_FONTS.with(|fonts| fonts.borrow_mut().push(font_data))
231}