use std::simd::{Simd, num::SimdUint}; use crate::layout::{Align, RegionAlign, TextureHandle, Textures, UiColor, Vec2}; use cosmic_text::{ Attrs, AttrsList, Buffer, CacheKey, Color, Family, FontSystem, Metrics, Placement, SwashCache, SwashContent, }; use image::{GenericImageView, RgbaImage}; /// TODO: properly wrap this pub mod text_lib { pub use cosmic_text::*; } pub struct TextData { pub font_system: FontSystem, pub swash_cache: SwashCache, glyph_cache: Vec<(Placement, CacheKey, Color)>, } impl Default for TextData { fn default() -> Self { Self { font_system: FontSystem::new(), swash_cache: SwashCache::new(), glyph_cache: Default::default(), } } } #[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 it's drawn) pub align: RegionAlign, } 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 * LINE_HEIGHT_MULT, family: Family::SansSerif, wrap: false, align: Align::CENTER_LEFT, } } } pub const LINE_HEIGHT_MULT: f32 = 1.1; impl TextData { pub fn draw( &mut self, buffer: &mut TextBuffer, attrs: &TextAttrs, textures: &mut Textures, ) -> RenderedText { // 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::<_, [u8; 4]>::default(); let mut min_x = 0; let mut min_y = 0; let mut max_x = 0; let mut max_y = 0; let text_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 => text_color, }; if let Some(img) = self .swash_cache .get_image(&mut self.font_system, physical_glyph.cache_key) { let mut pos = img.placement; pos.left += physical_glyph.x; pos.top = physical_glyph.y + run.line_y as i32 - pos.top; min_x = min_x.min(pos.left); min_y = min_y.min(pos.top); max_x = max_x.max(pos.left + pos.width as i32); max_y = max_y.max(pos.top + pos.height as i32); self.glyph_cache .push((pos, physical_glyph.cache_key, glyph_color)); } } 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 (pos, key, color) in self.glyph_cache.drain(..) { let img = self .swash_cache .get_image(&mut self.font_system, key) .as_ref() .unwrap(); let mut merge = |i, color: [u8; 4]| { let i = i as i32; let x = (i % pos.width as i32 + pos.left - min_x) as u32; let y = (i / pos.width as i32 + pos.top - min_y) as u32; let pixel = &mut image[(x, y)].0; // TODO: no clue if proper alpha blending should be done *pixel = Simd::from(color).saturating_add(Simd::from(*pixel)).into(); }; match img.content { SwashContent::Mask => { for (i, a) in img.data.iter().enumerate() { let mut color = color.as_rgba(); color[3] = ((color[3] as u32 * *a as u32) / u8::MAX as u32) as u8; merge(i, color); } } SwashContent::SubpixelMask => todo!("subpixel mask text rendering"), SwashContent::Color => { let (colors, _) = img.data.as_chunks::<4>(); for (i, color) in colors.iter().enumerate() { merge(i, *color); } } } } let max_dim = 8192; if image.width() > max_dim || image.height() > max_dim { let width = image.width().min(max_dim); let height = image.height().min(max_dim); eprintln!( "WARNING: image of size {:?} cropped to {:?} (texture too big)", image.dimensions(), (width, height) ); image = image.view(0, 0, width, height).to_image(); } RenderedText { handle: textures.add(image), top_left_offset: Vec2::new(min_x as f32, min_y as f32), size: Vec2::new(max_width, height), } } } #[derive(Clone)] pub struct RenderedText { pub handle: TextureHandle, pub top_left_offset: Vec2, pub size: Vec2, }