use cosmic_text::{Attrs, AttrsList, Buffer, Family, FontSystem, Metrics, SwashCache}; use image::{Rgba, RgbaImage}; use crate::{ layout::{Align, TextureHandle, Textures, UiColor, Vec2}, util::HashMap, }; /// TODO: properly wrap this pub mod text_lib { pub use cosmic_text::*; } pub struct TextData { pub font_system: FontSystem, pub swash_cache: SwashCache, } impl Default for TextData { fn default() -> Self { Self { font_system: FontSystem::new(), swash_cache: SwashCache::new(), } } } #[derive(Clone, Copy)] pub struct TextAttrs { pub color: UiColor, pub font_size: f32, pub line_height: f32, pub family: Family<'static>, pub wrap: bool, /// inner alignment of text region (within where its drawn) pub align: Align, } impl TextAttrs { pub fn apply(&self, font_system: &mut FontSystem, buf: &mut Buffer, width: Option) { buf.set_metrics_and_size( font_system, Metrics::new(self.font_size, self.line_height), width, None, ); let attrs = Attrs::new().family(self.family); let list = AttrsList::new(&attrs); for line in &mut buf.lines { line.set_attrs_list(list.clone()); } } } pub type TextBuffer = Buffer; impl Default for TextAttrs { fn default() -> Self { let size = 14.0; Self { color: UiColor::WHITE, font_size: size, line_height: size * 1.2, family: Family::SansSerif, wrap: false, align: Align::Center, } } } impl TextData { pub fn draw( &mut self, buffer: &mut TextBuffer, attrs: &TextAttrs, textures: &mut Textures, ) -> TextTexture { // TODO: either this or the layout stuff (or both) is super slow, // should probably do texture packing and things if possible. // very visible if you add just a couple of wrapping texts and resize window // should also be timed to figure out exactly what points need to be sped up let mut pixels = HashMap::default(); let mut min_x = 0; let mut min_y = 0; let mut max_x = 0; let mut max_y = 0; let cosmic_color = { let c = attrs.color; cosmic_text::Color::rgba(c.r, c.g, c.b, c.a) }; let mut max_width = 0.0f32; let mut height = 0.0; for run in buffer.layout_runs() { for glyph in run.glyphs.iter() { let physical_glyph = glyph.physical((0., 0.), 1.0); let glyph_color = match glyph.color_opt { Some(some) => some, None => cosmic_color, }; self.swash_cache.with_pixels( &mut self.font_system, physical_glyph.cache_key, glyph_color, |x, y, color| { let x = physical_glyph.x + x; let y = run.line_y as i32 + physical_glyph.y + y; min_x = min_x.min(x); min_y = min_y.min(y); max_x = max_x.max(x); max_y = max_y.max(y); pixels.insert((x, y), Rgba(color.as_rgba())); }, ); } max_width = max_width.max(run.line_w); height += run.line_height; } let img_width = (max_x - min_x + 1) as u32; let img_height = (max_y - min_y + 1) as u32; let mut image = RgbaImage::new(img_width, img_height); for ((x, y), color) in pixels { let x = (x - min_x) as u32; let y = (y - min_y) as u32; image.put_pixel(x, y, color); } TextTexture { handle: textures.add(image), top_left: Vec2::new(min_x as f32, min_y as f32), bot_right: Vec2::new(max_width - max_x as f32, height - max_y as f32), } } } #[derive(Clone)] pub struct TextTexture { pub handle: TextureHandle, pub top_left: Vec2, pub bot_right: Vec2, } impl TextTexture { pub fn size(&self) -> Vec2 { self.handle.size() - self.top_left + self.bot_right } }