use crate::atlas::allocator::GlyphAtlas;
use crate::atlas::cache::GlyphCache;
use crate::font::registry::FontRegistry;
use crate::font::resolve::resolve_font;
use crate::shaping::shaper::font_metrics_px;
use crate::types::{FontFaceId, TextFormat};
#[derive(Debug)]
pub struct AtlasSnapshot<'a> {
pub dirty: bool,
pub width: u32,
pub height: u32,
pub pixels: &'a [u8],
pub glyphs_evicted: bool,
}
pub struct TextFontService {
pub(crate) font_registry: FontRegistry,
pub(crate) atlas: GlyphAtlas,
pub(crate) glyph_cache: GlyphCache,
pub(crate) scale_context: swash::scale::ScaleContext,
pub(crate) scale_factor: f32,
pub(crate) scale_generation: u64,
pub(crate) eviction_epoch: u64,
}
impl TextFontService {
pub fn new() -> Self {
Self::with_registry(FontRegistry::new())
}
pub fn new_without_system_fonts() -> Self {
Self::with_registry(FontRegistry::new_without_system_fonts())
}
fn with_registry(font_registry: FontRegistry) -> Self {
Self {
font_registry,
atlas: GlyphAtlas::new(),
glyph_cache: GlyphCache::new(),
scale_context: swash::scale::ScaleContext::new(),
scale_factor: 1.0,
scale_generation: 0,
eviction_epoch: 0,
}
}
pub fn register_font(&mut self, data: &[u8]) -> FontFaceId {
let ids = self.font_registry.register_font(data);
ids.into_iter()
.next()
.expect("font data contained no faces")
}
pub fn register_font_shared(&mut self, data: crate::font::SharedFontData) -> FontFaceId {
let ids = self.font_registry.register_font_shared(data);
ids.into_iter()
.next()
.expect("font data contained no faces")
}
pub fn register_font_as(
&mut self,
data: &[u8],
family: &str,
weight: u16,
italic: bool,
) -> FontFaceId {
let ids = self
.font_registry
.register_font_as(data, family, weight, italic);
ids.into_iter()
.next()
.expect("font data contained no faces")
}
pub fn register_font_shared_as(
&mut self,
data: crate::font::SharedFontData,
family: &str,
weight: u16,
italic: bool,
) -> FontFaceId {
let ids = self
.font_registry
.register_font_shared_as(data, family, weight, italic);
ids.into_iter()
.next()
.expect("font data contained no faces")
}
pub fn set_default_font(&mut self, face: FontFaceId, size_px: f32) {
self.font_registry.set_default_font(face, size_px);
}
pub fn set_generic_family(&mut self, generic: &str, family: &str) {
self.font_registry.set_generic_family(generic, family);
}
pub fn font_family_name(&self, face_id: FontFaceId) -> Option<String> {
self.font_registry.font_family_name(face_id)
}
pub fn font_registry(&self) -> &FontRegistry {
&self.font_registry
}
pub fn families(&self) -> Vec<crate::font::FontFamilyInfo> {
self.font_registry.families()
}
pub fn family_names(&self) -> Vec<String> {
self.font_registry.family_names()
}
pub fn family_is_monospaced(&self, family: &str) -> bool {
self.font_registry.family_is_monospaced(family)
}
pub fn writing_system_index_builder(&self) -> crate::font::WritingSystemIndexBuilder {
self.font_registry.writing_system_index_builder()
}
pub fn default_line_height(&self) -> f32 {
self.measure_line_height(&TextFormat::default())
}
pub fn measure_line_height(&self, format: &TextFormat) -> f32 {
let font_point_size = format.font_size.map(|s| s as u32);
let resolved = match resolve_font(
&self.font_registry,
format.font_family.as_deref(),
format.font_weight,
format.font_bold,
format.font_italic,
font_point_size,
self.scale_factor,
1.0, ) {
Some(r) => r,
None => return 0.0,
};
match font_metrics_px(&self.font_registry, &resolved) {
Some(m) => m.ascent + m.descent + m.leading,
None => 0.0,
}
}
pub fn set_scale_factor(&mut self, scale_factor: f32) {
let sf = scale_factor.clamp(0.25, 8.0);
if (self.scale_factor - sf).abs() <= f32::EPSILON {
return;
}
self.scale_factor = sf;
self.glyph_cache.entries.clear();
self.atlas = GlyphAtlas::new();
self.scale_generation = self.scale_generation.wrapping_add(1);
self.eviction_epoch = self.eviction_epoch.wrapping_add(1);
}
pub fn scale_factor(&self) -> f32 {
self.scale_factor
}
pub fn scale_generation(&self) -> u64 {
self.scale_generation
}
pub fn eviction_epoch(&self) -> u64 {
self.eviction_epoch
}
pub fn atlas_snapshot(&mut self, advance_generation: bool) -> AtlasSnapshot<'_> {
let mut glyphs_evicted = false;
if advance_generation {
self.glyph_cache.advance_generation();
let evicted = self.glyph_cache.evict_unused();
glyphs_evicted = !evicted.is_empty();
if glyphs_evicted {
self.eviction_epoch = self.eviction_epoch.wrapping_add(1);
}
for glyph in evicted {
self.atlas.deallocate(glyph.alloc_id);
#[cfg(debug_assertions)]
self.atlas.debug_poison_rect(
glyph.atlas_x,
glyph.atlas_y,
glyph.width,
glyph.height,
);
}
}
let dirty = self.atlas.dirty;
let width = self.atlas.width;
let height = self.atlas.height;
if dirty {
self.atlas.dirty = false;
}
AtlasSnapshot {
dirty,
width,
height,
pixels: &self.atlas.pixels[..],
glyphs_evicted,
}
}
pub fn touch_glyphs(&mut self, keys: &[crate::atlas::cache::GlyphCacheKey]) {
self.glyph_cache.touch(keys);
}
pub fn peek_glyph_rect(&self, key: &crate::atlas::cache::GlyphCacheKey) -> Option<[u32; 4]> {
self.glyph_cache
.peek(key)
.map(|g| [g.atlas_x, g.atlas_y, g.width, g.height])
}
#[doc(hidden)]
pub fn debug_set_glyph_rect(
&mut self,
key: &crate::atlas::cache::GlyphCacheKey,
rect: [u32; 4],
) -> bool {
match self.glyph_cache.entries.get_mut(key) {
Some(glyph) => {
glyph.atlas_x = rect[0];
glyph.atlas_y = rect[1];
glyph.width = rect[2];
glyph.height = rect[3];
true
}
None => false,
}
}
pub fn atlas_dirty(&self) -> bool {
self.atlas.dirty
}
pub fn atlas_width(&self) -> u32 {
self.atlas.width
}
pub fn atlas_height(&self) -> u32 {
self.atlas.height
}
pub fn atlas_pixels(&self) -> &[u8] {
&self.atlas.pixels
}
pub fn mark_atlas_clean(&mut self) {
self.atlas.dirty = false;
}
}
impl Default for TextFontService {
fn default() -> Self {
Self::new()
}
}