slint/internal/core/software_renderer/fonts.rs
Olivier Goffart c98d234b9e Janitor: Always use `#![no_std] for runtime lib
And call `extern crate std` when the feature is enabled.
I've read this is the good practice on how to do it.
So that the std prelude is no longer included automatically.
There is then less difference between std and and no-std build which
should avoid surprises in the CI when we use things from the prelude.

The downside is that there is a bit of churn in the tests
2025-01-27 19:22:00 +01:00

234 lines
7.9 KiB
Rust

// Copyright © SixtyFPS GmbH <info@slint.dev>
// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0
use alloc::rc::Rc;
use alloc::vec::Vec;
use core::cell::RefCell;
use super::{Fixed, PhysicalLength, PhysicalSize};
use crate::graphics::{BitmapFont, FontRequest};
use crate::items::TextWrap;
use crate::lengths::{LogicalLength, LogicalSize, ScaleFactor};
use crate::textlayout::{FontMetrics, TextLayout};
use crate::Coord;
crate::thread_local! {
static BITMAP_FONTS: RefCell<Vec<&'static BitmapFont>> = RefCell::default()
}
#[derive(derive_more::From, Clone)]
pub enum GlyphAlphaMap {
Static(&'static [u8]),
Shared(Rc<[u8]>),
}
#[derive(Clone)]
pub struct RenderableGlyph {
pub x: Fixed<i32, 8>,
pub y: Fixed<i32, 8>,
pub width: PhysicalLength,
pub height: PhysicalLength,
pub alpha_map: GlyphAlphaMap,
pub pixel_stride: u16,
pub sdf: bool,
}
impl RenderableGlyph {
pub fn size(&self) -> PhysicalSize {
PhysicalSize::from_lengths(self.width, self.height)
}
}
pub trait GlyphRenderer {
fn render_glyph(&self, glyph_id: core::num::NonZeroU16) -> Option<RenderableGlyph>;
/// The amount of pixel in the original image that correspond to one pixel in the rendered image
fn scale_delta(&self) -> Fixed<u16, 8>;
}
pub(super) const DEFAULT_FONT_SIZE: LogicalLength = LogicalLength::new(12 as Coord);
mod pixelfont;
#[cfg(feature = "software-renderer-systemfonts")]
pub mod vectorfont;
#[cfg(feature = "software-renderer-systemfonts")]
pub mod systemfonts;
#[derive(derive_more::From)]
pub enum Font {
PixelFont(pixelfont::PixelFont),
#[cfg(feature = "software-renderer-systemfonts")]
VectorFont(vectorfont::VectorFont),
}
impl crate::textlayout::FontMetrics<PhysicalLength> for Font {
fn ascent(&self) -> PhysicalLength {
match self {
Font::PixelFont(pixel_font) => pixel_font.ascent(),
#[cfg(feature = "software-renderer-systemfonts")]
Font::VectorFont(vector_font) => vector_font.ascent(),
}
}
fn height(&self) -> PhysicalLength {
match self {
Font::PixelFont(pixel_font) => pixel_font.height(),
#[cfg(feature = "software-renderer-systemfonts")]
Font::VectorFont(vector_font) => vector_font.height(),
}
}
fn descent(&self) -> PhysicalLength {
match self {
Font::PixelFont(pixel_font) => pixel_font.descent(),
#[cfg(feature = "software-renderer-systemfonts")]
Font::VectorFont(vector_font) => vector_font.descent(),
}
}
fn x_height(&self) -> PhysicalLength {
match self {
Font::PixelFont(pixel_font) => pixel_font.x_height(),
#[cfg(feature = "software-renderer-systemfonts")]
Font::VectorFont(vector_font) => vector_font.x_height(),
}
}
fn cap_height(&self) -> PhysicalLength {
match self {
Font::PixelFont(pixel_font) => pixel_font.cap_height(),
#[cfg(feature = "software-renderer-systemfonts")]
Font::VectorFont(vector_font) => vector_font.cap_height(),
}
}
}
pub fn match_font(request: &FontRequest, scale_factor: ScaleFactor) -> Font {
let requested_weight = request
.weight
.and_then(|weight| weight.try_into().ok())
.unwrap_or(/* CSS normal */ 400);
let bitmap_font = BITMAP_FONTS.with(|fonts| {
let fonts = fonts.borrow();
request.family.as_ref().and_then(|requested_family| {
fonts
.iter()
.filter(|bitmap_font| {
core::str::from_utf8(bitmap_font.family_name.as_slice()).unwrap()
== requested_family.as_str()
&& bitmap_font.italic == request.italic
})
.min_by_key(|bitmap_font| bitmap_font.weight.abs_diff(requested_weight))
.copied()
})
});
let font = match bitmap_font {
Some(bitmap_font) => bitmap_font,
None => {
#[cfg(feature = "software-renderer-systemfonts")]
if let Some(vectorfont) = systemfonts::match_font(request, scale_factor) {
return vectorfont.into();
}
if let Some(fallback_bitmap_font) = BITMAP_FONTS.with(|fonts| {
let fonts = fonts.borrow();
fonts
.iter()
.cloned()
.filter(|bitmap_font| bitmap_font.italic == request.italic)
.min_by_key(|bitmap_font| bitmap_font.weight.abs_diff(requested_weight))
.or_else(|| fonts.first().cloned())
}) {
fallback_bitmap_font
} else {
#[cfg(feature = "software-renderer-systemfonts")]
return systemfonts::fallbackfont(request, scale_factor).into();
#[cfg(not(feature = "software-renderer-systemfonts"))]
panic!("No font fallback found. The software renderer requires enabling the `EmbedForSoftwareRenderer` option when compiling slint files.")
}
}
};
let requested_pixel_size: PhysicalLength =
(request.pixel_size.unwrap_or(DEFAULT_FONT_SIZE).cast() * 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];
let pixel_size = if font.sdf { requested_pixel_size } else { matching_glyphs.pixel_size() };
pixelfont::PixelFont { bitmap_font: font, glyphs: matching_glyphs, pixel_size }.into()
}
pub fn text_layout_for_font<'a, Font>(
font: &'a Font,
font_request: &FontRequest,
scale_factor: ScaleFactor,
) -> TextLayout<'a, Font>
where
Font: crate::textlayout::AbstractFont + crate::textlayout::TextShaper<Length = PhysicalLength>,
{
let letter_spacing =
font_request.letter_spacing.map(|spacing| (spacing.cast() * scale_factor).cast());
TextLayout { font, letter_spacing }
}
pub fn register_bitmap_font(font_data: &'static BitmapFont) {
BITMAP_FONTS.with(|fonts| fonts.borrow_mut().push(font_data))
}
pub fn text_size(
font_request: FontRequest,
text: &str,
max_width: Option<LogicalLength>,
scale_factor: ScaleFactor,
text_wrap: TextWrap,
) -> LogicalSize {
let font = match_font(&font_request, scale_factor);
let (longest_line_width, height) = match font {
Font::PixelFont(pf) => {
let layout = text_layout_for_font(&pf, &font_request, scale_factor);
layout.text_size(
text,
max_width.map(|max_width| (max_width.cast() * scale_factor).cast()),
text_wrap,
)
}
#[cfg(feature = "software-renderer-systemfonts")]
Font::VectorFont(vf) => {
let layout = text_layout_for_font(&vf, &font_request, scale_factor);
layout.text_size(
text,
max_width.map(|max_width| (max_width.cast() * scale_factor).cast()),
text_wrap,
)
}
};
(PhysicalSize::from_lengths(longest_line_width, height).cast() / scale_factor).cast()
}
pub fn font_metrics(
font_request: FontRequest,
scale_factor: ScaleFactor,
) -> crate::items::FontMetrics {
let font = match_font(&font_request, scale_factor);
let ascent: LogicalLength = (font.ascent().cast() / scale_factor).cast();
let descent: LogicalLength = (font.descent().cast() / scale_factor).cast();
let x_height: LogicalLength = (font.x_height().cast() / scale_factor).cast();
let cap_height: LogicalLength = (font.cap_height().cast() / scale_factor).cast();
crate::items::FontMetrics {
ascent: ascent.get() as _,
descent: descent.get() as _,
x_height: x_height.get() as _,
cap_height: cap_height.get() as _,
}
}