Skip to main content

i_slint_renderer_software/fonts/
vectorfont.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 core::num::NonZeroU16;
5
6use alloc::rc::Rc;
7use alloc::vec::Vec;
8use skrifa::MetadataProvider;
9
10use crate::PhysicalLength;
11use crate::fixed::Fixed;
12use i_slint_common::sharedfontique::fontique;
13use i_slint_core::lengths::PhysicalPx;
14use i_slint_core::textlayout::{Glyph, TextShaper};
15
16use super::RenderableVectorGlyph;
17
18// A length in font design space.
19struct FontUnit;
20type FontLength = euclid::Length<i32, FontUnit>;
21type FontScaleFactor = euclid::Scale<f32, FontUnit, PhysicalPx>;
22
23/// Cache key includes blob id, font index, pixel size, glyph id, and a hash of normalized
24/// variation coordinates so that different variable font instances produce distinct cache entries.
25type GlyphCacheKey = (u64, u32, PhysicalLength, core::num::NonZeroU16, u64);
26
27struct RenderableGlyphWeightScale;
28
29impl clru::WeightScale<GlyphCacheKey, RenderableVectorGlyph> for RenderableGlyphWeightScale {
30    fn weight(&self, _: &GlyphCacheKey, value: &RenderableVectorGlyph) -> usize {
31        value.alpha_map.len()
32    }
33}
34
35type GlyphCache = clru::CLruCache<
36    GlyphCacheKey,
37    RenderableVectorGlyph,
38    std::collections::hash_map::RandomState,
39    RenderableGlyphWeightScale,
40>;
41
42i_slint_core::thread_local!(static GLYPH_CACHE: core::cell::RefCell<GlyphCache>  =
43    core::cell::RefCell::new(
44        clru::CLruCache::with_config(
45            clru::CLruCacheConfig::new(core::num::NonZeroUsize::new(1024 * 1024).unwrap())
46                .with_scale(RenderableGlyphWeightScale)
47        )
48    )
49);
50
51pub struct VectorFont {
52    font_index: u32,
53    font_blob: fontique::Blob<u8>,
54    swash_key: swash::CacheKey,
55    swash_offset: u32,
56    ascender: PhysicalLength,
57    descender: PhysicalLength,
58    height: PhysicalLength,
59    pixel_size: PhysicalLength,
60    x_height: PhysicalLength,
61    cap_height: PhysicalLength,
62    /// Normalized variation coordinates (F2Dot14, fvar axis order) for variable font rendering.
63    normalized_coords: Vec<i16>,
64    /// Hash of normalized_coords for use in the glyph cache key.
65    coords_hash: u64,
66}
67
68fn hash_coords(coords: &[i16]) -> u64 {
69    use core::hash::{Hash, Hasher};
70    let mut hasher = std::collections::hash_map::DefaultHasher::new();
71    coords.hash(&mut hasher);
72    hasher.finish()
73}
74
75impl VectorFont {
76    fn swash_font_ref(&self) -> swash::FontRef<'_> {
77        swash::FontRef {
78            data: self.font_blob.data(),
79            offset: self.swash_offset,
80            key: self.swash_key,
81        }
82    }
83
84    pub fn new(
85        font: fontique::QueryFont,
86        swash_key: swash::CacheKey,
87        swash_offset: u32,
88        pixel_size: PhysicalLength,
89    ) -> Self {
90        Self::new_from_blob_and_index(font.blob, font.index, swash_key, swash_offset, pixel_size)
91    }
92
93    pub fn new_from_blob_and_index(
94        font_blob: fontique::Blob<u8>,
95        font_index: u32,
96        swash_key: swash::CacheKey,
97        swash_offset: u32,
98        pixel_size: PhysicalLength,
99    ) -> Self {
100        Self::new_from_blob_and_index_with_coords(
101            font_blob,
102            font_index,
103            swash_key,
104            swash_offset,
105            pixel_size,
106            &[],
107        )
108    }
109
110    pub fn new_from_blob_and_index_with_coords(
111        font_blob: fontique::Blob<u8>,
112        font_index: u32,
113        swash_key: swash::CacheKey,
114        swash_offset: u32,
115        pixel_size: PhysicalLength,
116        normalized_coords: &[i16],
117    ) -> Self {
118        let face = skrifa::FontRef::from_index(font_blob.data(), font_index).unwrap();
119
120        let skrifa_coords: Vec<skrifa::instance::NormalizedCoord> = normalized_coords
121            .iter()
122            .map(|&c| skrifa::instance::NormalizedCoord::from_bits(c))
123            .collect();
124        let location = skrifa::instance::LocationRef::new(&skrifa_coords);
125
126        let metrics = face.metrics(skrifa::instance::Size::unscaled(), location);
127
128        let ascender = FontLength::new(metrics.ascent as _);
129        let descender = FontLength::new(metrics.descent as _);
130        let height = FontLength::new((metrics.ascent - metrics.descent) as _);
131        let x_height = FontLength::new(metrics.x_height.unwrap_or_default() as _);
132        let cap_height = FontLength::new(metrics.cap_height.unwrap_or_default() as _);
133        let units_per_em = metrics.units_per_em;
134        let scale = FontScaleFactor::new(pixel_size.get() as f32 / units_per_em as f32);
135        let coords_hash = hash_coords(normalized_coords);
136        Self {
137            font_index,
138            font_blob,
139            swash_key,
140            swash_offset,
141            ascender: (ascender.cast() * scale).cast(),
142            descender: (descender.cast() * scale).cast(),
143            height: (height.cast() * scale).cast(),
144            pixel_size,
145            x_height: (x_height.cast() * scale).cast(),
146            cap_height: (cap_height.cast() * scale).cast(),
147            normalized_coords: normalized_coords.to_vec(),
148            coords_hash,
149        }
150    }
151
152    pub fn render_vector_glyph(
153        &self,
154        glyph_id: core::num::NonZeroU16,
155        slint_context: &i_slint_core::SlintContext,
156    ) -> Option<RenderableVectorGlyph> {
157        GLYPH_CACHE.with(|cache| {
158            let mut cache = cache.borrow_mut();
159
160            let cache_key =
161                (self.font_blob.id(), self.font_index, self.pixel_size, glyph_id, self.coords_hash);
162
163            if let Some(entry) = cache.get(&cache_key) {
164                return Some(entry.clone());
165            }
166
167            let glyph = {
168                let font_ref = self.swash_font_ref();
169                let mut ctx = slint_context.swash_scale_context().borrow_mut();
170                let mut scaler = ctx
171                    .builder(font_ref)
172                    .size(self.pixel_size.get() as f32)
173                    .normalized_coords(&self.normalized_coords)
174                    .build();
175                let image = swash::scale::Render::new(&[swash::scale::Source::Outline])
176                    .format(swash::zeno::Format::Alpha)
177                    .render(&mut scaler, glyph_id.get())?;
178
179                let placement = image.placement;
180                let alpha_map: Rc<[u8]> = image.data.into();
181
182                Some(RenderableVectorGlyph {
183                    x: Fixed::from_integer(placement.left),
184                    y: Fixed::from_integer(placement.top - placement.height as i32),
185                    width: PhysicalLength::new(placement.width.try_into().unwrap()),
186                    height: PhysicalLength::new(placement.height.try_into().unwrap()),
187                    alpha_map,
188                    pixel_stride: placement.width.try_into().unwrap(),
189                    glyph_origin_x: placement.left as f32,
190                })
191            };
192
193            if let Some(ref glyph) = glyph {
194                cache.put_with_weight(cache_key, glyph.clone()).ok();
195            }
196            glyph
197        })
198    }
199}
200
201impl TextShaper for VectorFont {
202    type LengthPrimitive = i16;
203    type Length = PhysicalLength;
204    fn shape_text<GlyphStorage: core::iter::Extend<Glyph<PhysicalLength>>>(
205        &self,
206        text: &str,
207        glyphs: &mut GlyphStorage,
208    ) {
209        let font_ref = self.swash_font_ref();
210        let charmap = font_ref.charmap();
211        let gm = font_ref.glyph_metrics(&[]);
212        let metrics = font_ref.metrics(&[]);
213        let scale = self.pixel_size.get() as f32 / metrics.units_per_em as f32;
214
215        glyphs.extend(text.char_indices().map(|(byte_offset, char)| {
216            let glyph_id = NonZeroU16::try_from(charmap.map(char)).ok();
217            let x_advance = glyph_id.map_or_else(
218                || self.pixel_size.get(),
219                |id| (gm.advance_width(id.get()) * scale) as _,
220            );
221
222            Glyph {
223                glyph_id,
224                advance: PhysicalLength::new(x_advance),
225                text_byte_offset: byte_offset,
226                ..Default::default()
227            }
228        }));
229    }
230
231    fn glyph_for_char(&self, ch: char) -> Option<Glyph<PhysicalLength>> {
232        let font_ref = self.swash_font_ref();
233        let charmap = font_ref.charmap();
234        let gm = font_ref.glyph_metrics(&[]);
235        let metrics = font_ref.metrics(&[]);
236        let scale = self.pixel_size.get() as f32 / metrics.units_per_em as f32;
237
238        NonZeroU16::try_from(charmap.map(ch)).ok().map(|glyph_id| Glyph {
239            glyph_id: Some(glyph_id),
240            advance: PhysicalLength::new((gm.advance_width(glyph_id.get()) * scale) as _),
241            ..Default::default()
242        })
243    }
244
245    fn max_lines(&self, max_height: PhysicalLength) -> usize {
246        (max_height / self.height).get() as _
247    }
248}
249
250impl i_slint_core::textlayout::FontMetrics<PhysicalLength> for VectorFont {
251    fn ascent(&self) -> PhysicalLength {
252        self.ascender
253    }
254
255    fn height(&self) -> PhysicalLength {
256        self.height
257    }
258
259    fn descent(&self) -> PhysicalLength {
260        self.descender
261    }
262
263    fn x_height(&self) -> PhysicalLength {
264        self.x_height
265    }
266
267    fn cap_height(&self) -> PhysicalLength {
268        self.cap_height
269    }
270}
271
272impl super::GlyphRenderer for VectorFont {
273    fn render_glyph(
274        &self,
275        glyph_id: core::num::NonZeroU16,
276        slint_context: &i_slint_core::SlintContext,
277    ) -> Option<super::RenderableGlyph> {
278        self.render_vector_glyph(glyph_id, slint_context).map(|glyph| super::RenderableGlyph {
279            x: glyph.x,
280            y: glyph.y,
281            width: glyph.width,
282            height: glyph.height,
283            alpha_map: glyph.alpha_map.into(),
284            pixel_stride: glyph.pixel_stride,
285            sdf: false,
286        })
287    }
288
289    fn scale_delta(&self) -> super::Fixed<u16, 8> {
290        super::Fixed::from_integer(1)
291    }
292}