use crate::font_family_query::FontFamilyCache;
use crate::state::{AlphaMode, TextState};
use crate::Id;
use ahash::{HashMap, HashSet, HashSetExt};
use cosmic_text::{fontdb, FontSystem, SwashCache};
use std::sync::Arc;
pub struct TextContext {
pub font_system: FontSystem,
pub swash_cache: SwashCache,
pub scale_factor: f32,
pub usage_tracker: TextUsageTracker,
pub font_family_cache: FontFamilyCache,
}
impl Default for TextContext {
fn default() -> Self {
Self {
font_system: FontSystem::new(),
swash_cache: SwashCache::new(),
scale_factor: 1.0,
usage_tracker: TextUsageTracker::new(),
font_family_cache: FontFamilyCache::new(),
}
}
}
#[derive(Default)]
pub struct TextManager<TMetadata = ()> {
pub text_states: HashMap<Id, TextState<TMetadata>>,
pub text_context: TextContext,
}
impl<TMetadata> TextManager<TMetadata> {
pub fn new() -> Self {
Self {
text_states: HashMap::default(),
text_context: TextContext::default(),
}
}
pub fn load_fonts(&mut self, fonts: impl Iterator<Item = fontdb::Source>) {
self.text_context.load_fonts(fonts);
}
pub fn load_fonts_from_bytes<'a>(&mut self, fonts: impl Iterator<Item = &'a [u8]>) {
self.text_context.load_fonts_from_bytes(fonts);
}
pub fn create_state(&mut self, id: Id, text: impl Into<String>, metadata: TMetadata) {
let state = TextState::new_with_text(text, &mut self.text_context.font_system, metadata);
self.text_states.insert(id, state);
}
pub fn start_frame(&mut self) {
self.text_context.usage_tracker.clear();
}
pub fn end_frame(&mut self, removed_ids: &mut Vec<Id>) {
let accessed_states = self.text_context.usage_tracker.accessed_states();
self.text_states.retain(|id, _| {
let accessed = accessed_states.contains(id);
if !accessed {
removed_ids.push(*id);
}
accessed
});
}
pub fn set_scale_factor(&mut self, scale: f32) {
let scale = scale.max(0.01);
if (self.text_context.scale_factor - scale).abs() < 0.0001 {
return;
}
self.text_context.scale_factor = scale;
for state in self.text_states.values_mut() {
state.set_scale_factor(scale);
}
}
pub fn rasterize_all_textures(&mut self, alpha_mode: AlphaMode) -> Vec<RasterizedTextureInfo> {
let mut changes = Vec::new();
for (id, state) in self.text_states.iter_mut() {
let old_w = state.rasterized_texture().width;
let old_h = state.rasterized_texture().height;
state.recalculate(&mut self.text_context);
let rerasterized = state.rasterize_into_texture(&mut self.text_context, alpha_mode);
if rerasterized {
let new_w = state.rasterized_texture().width;
let new_h = state.rasterized_texture().height;
let resized = new_w != old_w || new_h != old_h;
changes.push(RasterizedTextureInfo {
id: *id,
width: new_w,
height: new_h,
resized,
});
}
}
changes
}
}
#[derive(Debug, Clone, Copy)]
pub struct RasterizedTextureInfo {
pub id: Id,
pub width: u32,
pub height: u32,
pub resized: bool,
}
impl TextContext {
pub fn load_fonts(&mut self, fonts: impl Iterator<Item = fontdb::Source>) {
let db = self.font_system.db_mut();
for source in fonts {
db.load_font_source(source);
}
}
pub fn load_fonts_from_bytes<'a>(&mut self, fonts: impl Iterator<Item = &'a [u8]>) {
let db = self.font_system.db_mut();
for font_bytes in fonts {
let source = fontdb::Source::Binary(Arc::new(font_bytes.to_vec()));
db.load_font_source(source);
}
}
}
pub struct TextUsageTracker {
accessed_states: HashSet<Id>,
}
impl Default for TextUsageTracker {
fn default() -> Self {
Self::new()
}
}
impl TextUsageTracker {
pub fn new() -> Self {
Self {
accessed_states: HashSet::new(),
}
}
pub fn mark_accessed(&mut self, id: Id) {
self.accessed_states.insert(id);
}
pub fn clear(&mut self) {
self.accessed_states.clear();
}
pub fn accessed_states(&self) -> &HashSet<Id> {
&self.accessed_states
}
}