i_slint_renderer_software/
fonts.rs1use 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#[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 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
87pub 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(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}