// Copyright © SixtyFPS GmbH // SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-commercial use alloc::vec::Vec; use core::cell::RefCell; #[cfg(all(not(feature = "std"), feature = "unsafe_single_core"))] use i_slint_core::thread_local_ as thread_local; use crate::{LogicalLength, LogicalSize, PhysicalLength, PhysicalSize, ScaleFactor}; use euclid::num::Zero; use i_slint_core::{ graphics::{BitmapFont, BitmapGlyph, BitmapGlyphs, FontRequest}, slice::Slice, }; thread_local! { static FONTS: RefCell> = RefCell::default() } #[derive(Copy, Clone)] pub struct Glyph(&'static BitmapGlyph); impl Glyph { pub fn x(&self) -> PhysicalLength { PhysicalLength::new(self.0.x) } pub fn y(&self) -> PhysicalLength { PhysicalLength::new(self.0.y) } pub fn width(&self) -> PhysicalLength { PhysicalLength::new(self.0.width) } pub fn height(&self) -> PhysicalLength { PhysicalLength::new(self.0.height) } pub fn size(&self) -> PhysicalSize { PhysicalSize::from_lengths(self.width(), self.height()) } pub fn x_advance(&self) -> PhysicalLength { PhysicalLength::new(self.0.x_advance) } pub fn data(&self) -> &Slice<'static, u8> { &self.0.data } } pub trait FontMetrics { fn ascent(&self, font: &BitmapFont) -> PhysicalLength; fn height(&self, font: &BitmapFont) -> PhysicalLength; fn pixel_size(&self) -> PhysicalLength; } impl FontMetrics for BitmapGlyphs { fn ascent(&self, font: &BitmapFont) -> PhysicalLength { (PhysicalLength::new(self.pixel_size).cast() * font.ascent / font.units_per_em).cast() } fn height(&self, font: &BitmapFont) -> PhysicalLength { // The descent is negative (relative to the baseline) (PhysicalLength::new(self.pixel_size).cast() * (font.ascent - font.descent) / font.units_per_em) .cast() } fn pixel_size(&self) -> PhysicalLength { PhysicalLength::new(self.pixel_size) } } pub const DEFAULT_FONT_SIZE: f32 = 12.0; pub fn match_font( request: &FontRequest, scale_factor: ScaleFactor, ) -> (&'static BitmapFont, &'static BitmapGlyphs) { let font = FONTS.with(|fonts| { let fonts = fonts.borrow(); let fallback_font = *fonts.first().expect("internal error: cannot render text without fonts"); request.family.as_ref().map_or(fallback_font, |requested_family| { fonts .iter() .find(|bitmap_font| { core::str::from_utf8(bitmap_font.family_name.as_slice()).unwrap() == requested_family.as_str() }) .unwrap_or(&fallback_font) }) }); let requested_pixel_size: PhysicalLength = (LogicalLength::new(request.pixel_size.unwrap_or(DEFAULT_FONT_SIZE)) * scale_factor).cast(); let nearest_pixel_size = font .glyphs .partition_point(|glyphs| glyphs.pixel_size() <= requested_pixel_size) .saturating_sub(1); let matching_glyphs = &font.glyphs[nearest_pixel_size]; (font, matching_glyphs) } pub fn register_bitmap_font(font_data: &'static BitmapFont) { FONTS.with(|fonts| fonts.borrow_mut().push(font_data)) } pub fn glyphs_for_text<'a>( font: &'static BitmapFont, glyphs: &'static BitmapGlyphs, text: &'a str, ) -> impl Iterator + 'a { let mut x: PhysicalLength = PhysicalLength::zero(); text.chars().filter_map(move |char| { if let Some(glyph_index) = font .character_map .binary_search_by_key(&char, |char_map_entry| char_map_entry.code_point) .ok() .map(|char_map_index| font.character_map[char_map_index].glyph_index) { let glyph = Glyph(&glyphs.glyph_data[glyph_index as usize]); let glyph_x = x; x += glyph.x_advance(); Some((glyph_x, glyph)) } else { x += glyphs.pixel_size(); None } }) } pub fn text_size( font_request: FontRequest, text: &str, _max_width: Option, scale_factor: ScaleFactor, ) -> LogicalSize { let (font, glyphs) = match_font(&font_request, scale_factor); let width = glyphs_for_text(font, glyphs, text) .last() .map_or(PhysicalLength::zero(), |(last_x, last_glyph)| last_x + last_glyph.x_advance()); PhysicalSize::from_lengths(width, glyphs.height(font)).cast() / scale_factor }