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;
5
6use crate::lengths::PhysicalPx;
7use crate::software_renderer::fixed::Fixed;
8use crate::software_renderer::PhysicalLength;
9use crate::textlayout::{Glyph, TextShaper};
10use i_slint_common::sharedfontdb::{self, fontdb};
11
12use super::RenderableGlyph;
13
14// A length in font design space.
15struct FontUnit;
16type FontLength = euclid::Length<i32, FontUnit>;
17type FontScaleFactor = euclid::Scale<f32, FontUnit, PhysicalPx>;
18
19type GlyphCacheKey = (fontdb::ID, PhysicalLength, core::num::NonZeroU16);
20
21struct RenderableGlyphWeightScale;
22
23impl clru::WeightScale<GlyphCacheKey, RenderableGlyph> for RenderableGlyphWeightScale {
24 fn weight(&self, _: &GlyphCacheKey, value: &RenderableGlyph) -> usize {
25 match &value.alpha_map {
26 super::GlyphAlphaMap::Static(_) => 0,
27 super::GlyphAlphaMap::Shared(data: &Rc<[u8]>) => data.len(),
28 }
29 }
30}
31
32type GlyphCache = clru::CLruCache<
33 GlyphCacheKey,
34 RenderableGlyph,
35 std::collections::hash_map::RandomState,
36 RenderableGlyphWeightScale,
37>;
38
39crate::thread_local!(static GLYPH_CACHE: core::cell::RefCell<GlyphCache> =
40 core::cell::RefCell::new(
41 clru::CLruCache::with_config(
42 clru::CLruCacheConfig::new(core::num::NonZeroUsize::new(1024 * 1024).unwrap())
43 .with_scale(RenderableGlyphWeightScale)
44 )
45 )
46);
47
48pub struct VectorFont {
49 id: fontdb::ID,
50 fontdue_font: Rc<fontdue::Font>,
51 ascender: PhysicalLength,
52 descender: PhysicalLength,
53 height: PhysicalLength,
54 scale: FontScaleFactor,
55 pixel_size: PhysicalLength,
56 x_height: PhysicalLength,
57 cap_height: PhysicalLength,
58}
59
60impl VectorFont {
61 pub fn new(
62 id: fontdb::ID,
63 fontdue_font: Rc<fontdue::Font>,
64 pixel_size: PhysicalLength,
65 ) -> Self {
66 sharedfontdb::FONT_DB.with(|db| {
67 db.borrow()
68 .with_face_data(id, |face_data, font_index| {
69 let face = rustybuzz::ttf_parser::Face::parse(face_data, font_index).unwrap();
70
71 let ascender = FontLength::new(face.ascender() as _);
72 let descender = FontLength::new(face.descender() as _);
73 let height = FontLength::new(face.height() as _);
74 let x_height = FontLength::new(face.x_height().unwrap_or_default() as _);
75 let cap_height =
76 FontLength::new(face.capital_height().unwrap_or_default() as _);
77 let units_per_em = face.units_per_em();
78 let scale = FontScaleFactor::new(pixel_size.get() as f32 / units_per_em as f32);
79 Self {
80 id,
81 fontdue_font,
82 ascender: (ascender.cast() * scale).cast(),
83 descender: (descender.cast() * scale).cast(),
84 height: (height.cast() * scale).cast(),
85 scale,
86 pixel_size,
87 x_height: (x_height.cast() * scale).cast(),
88 cap_height: (cap_height.cast() * scale).cast(),
89 }
90 })
91 .unwrap()
92 })
93 }
94}
95
96impl TextShaper for VectorFont {
97 type LengthPrimitive = i16;
98 type Length = PhysicalLength;
99 fn shape_text<GlyphStorage: core::iter::Extend<Glyph<PhysicalLength>>>(
100 &self,
101 text: &str,
102 glyphs: &mut GlyphStorage,
103 ) {
104 let mut buffer = rustybuzz::UnicodeBuffer::new();
105 buffer.push_str(text);
106
107 sharedfontdb::FONT_DB.with(|db| {
108 db.borrow()
109 .with_face_data(self.id, |face_data, font_index| {
110 let face = rustybuzz::ttf_parser::Face::parse(face_data, font_index).unwrap();
111 let rb_face = rustybuzz::Face::from_face(face);
112
113 let glyph_buffer = rustybuzz::shape(&rb_face, &[], buffer);
114
115 let output_glyph_generator = glyph_buffer
116 .glyph_infos()
117 .iter()
118 .zip(glyph_buffer.glyph_positions().iter())
119 .map(|(info, position)| {
120 let mut out_glyph = Glyph::<PhysicalLength>::default();
121
122 out_glyph.glyph_id = core::num::NonZeroU16::new(info.glyph_id as u16);
123
124 out_glyph.offset_x =
125 (FontLength::new(position.x_offset).cast() * self.scale).cast();
126 out_glyph.offset_y =
127 (FontLength::new(position.y_offset).cast() * self.scale).cast();
128 out_glyph.advance =
129 (FontLength::new(position.x_advance).cast() * self.scale).cast();
130
131 out_glyph.text_byte_offset = info.cluster as usize;
132
133 out_glyph
134 });
135
136 // Cannot return impl Iterator, so extend argument instead
137 glyphs.extend(output_glyph_generator);
138 })
139 .unwrap()
140 })
141 }
142
143 fn glyph_for_char(&self, ch: char) -> Option<Glyph<PhysicalLength>> {
144 sharedfontdb::FONT_DB.with(|db| {
145 db.borrow()
146 .with_face_data(self.id, |face_data, font_index| {
147 let face = rustybuzz::ttf_parser::Face::parse(face_data, font_index).unwrap();
148 face.glyph_index(ch).map(|glyph_index| {
149 let mut out_glyph = Glyph::default();
150
151 out_glyph.glyph_id = core::num::NonZeroU16::new(glyph_index.0);
152
153 out_glyph.advance = (FontLength::new(
154 face.glyph_hor_advance(glyph_index).unwrap_or_default() as _,
155 )
156 .cast()
157 * self.scale)
158 .cast();
159
160 out_glyph
161 })
162 })
163 .unwrap()
164 })
165 }
166
167 fn max_lines(&self, max_height: PhysicalLength) -> usize {
168 (max_height / self.height).get() as _
169 }
170}
171
172impl crate::textlayout::FontMetrics<PhysicalLength> for VectorFont {
173 fn ascent(&self) -> PhysicalLength {
174 self.ascender
175 }
176
177 fn height(&self) -> PhysicalLength {
178 self.height
179 }
180
181 fn descent(&self) -> PhysicalLength {
182 self.descender
183 }
184
185 fn x_height(&self) -> PhysicalLength {
186 self.x_height
187 }
188
189 fn cap_height(&self) -> PhysicalLength {
190 self.cap_height
191 }
192}
193
194impl super::GlyphRenderer for VectorFont {
195 fn render_glyph(&self, glyph_id: core::num::NonZeroU16) -> Option<super::RenderableGlyph> {
196 GLYPH_CACHE.with(|cache| {
197 let mut cache = cache.borrow_mut();
198
199 let cache_key = (self.id, self.pixel_size, glyph_id);
200
201 if let Some(entry) = cache.get(&cache_key) {
202 Some(entry.clone())
203 } else {
204 let (metrics, alpha_map) =
205 self.fontdue_font.rasterize_indexed(glyph_id.get(), self.pixel_size.get() as _);
206
207 let alpha_map: Rc<[u8]> = alpha_map.into();
208
209 let glyph = super::RenderableGlyph {
210 x: Fixed::from_integer(metrics.xmin.try_into().unwrap()),
211 y: Fixed::from_integer(metrics.ymin.try_into().unwrap()),
212 width: PhysicalLength::new(metrics.width.try_into().unwrap()),
213 height: PhysicalLength::new(metrics.height.try_into().unwrap()),
214 alpha_map: alpha_map.into(),
215 sdf: false,
216 pixel_stride: metrics.width.try_into().unwrap(),
217 };
218
219 cache.put_with_weight(cache_key, glyph.clone()).ok();
220 Some(glyph)
221 }
222 })
223 }
224
225 fn scale_delta(&self) -> super::Fixed<u16, 8> {
226 super::Fixed::from_integer(1)
227 }
228}
229