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