diff --git a/src/core/text/edit.rs b/src/core/text/edit.rs index 3f014cf..ad8985b 100644 --- a/src/core/text/edit.rs +++ b/src/core/text/edit.rs @@ -33,9 +33,7 @@ impl TextEdit { impl Widget for TextEdit { fn draw(&mut self, painter: &mut Painter) { - let tex = self.view.draw(&mut painter.size_ctx()); - let region = text_region(&tex, self.align); - painter.texture_within(&tex.handle, region); + let region = self.view.draw(painter); if let Some(cursor) = &self.cursor && let Some(offset) = cursor_pos(cursor, &self.buf) @@ -49,11 +47,11 @@ impl Widget for TextEdit { } fn desired_width(&mut self, ctx: &mut SizeCtx) -> Len { - Len::abs(self.view.draw(ctx).size.x) + Len::abs(self.view.render(ctx).size.x) } fn desired_height(&mut self, ctx: &mut SizeCtx) -> Len { - Len::abs(self.view.draw(ctx).size.y) + Len::abs(self.view.render(ctx).size.y) } } diff --git a/src/core/text/mod.rs b/src/core/text/mod.rs index e2601f7..d32a439 100644 --- a/src/core/text/mod.rs +++ b/src/core/text/mod.rs @@ -19,7 +19,7 @@ pub struct TextView { pub attrs: MutDetect, pub buf: MutDetect, // cache - tex: Option, + tex: Option, width: Option, } @@ -32,7 +32,7 @@ impl TextView { width: None, } } - pub fn draw(&mut self, ctx: &mut SizeCtx) -> TextTexture { + pub fn render(&mut self, ctx: &mut SizeCtx) -> RenderedText { let width = if self.attrs.wrap { Some(ctx.px_size().x) } else { @@ -55,9 +55,25 @@ impl TextView { self.buf.changed = false; tex } - pub fn tex(&self) -> Option<&TextTexture> { + pub fn tex(&self) -> Option<&RenderedText> { self.tex.as_ref() } + pub fn desired_width(&mut self, ctx: &mut SizeCtx) -> Len { + Len::abs(self.render(ctx).size.x) + } + pub fn desired_height(&mut self, ctx: &mut SizeCtx) -> Len { + Len::abs(self.render(ctx).size.y) + } + pub fn draw(&mut self, painter: &mut Painter) -> UiRegion { + let tex = self.render(&mut painter.size_ctx()); + let region = tex.size.align(self.align); + let dims = tex.handle.size(); + let mut region = region.offset(tex.top_left_offset); + region.x.end = region.x.start + UiScalar::abs(dims.x); + region.y.end = region.y.start + UiScalar::abs(dims.y); + painter.texture_within(&tex.handle, region); + region + } } impl Text { @@ -69,7 +85,7 @@ impl Text { view: TextView::new(buf, attrs), } } - fn update_buf(&mut self, ctx: &mut SizeCtx) -> TextTexture { + fn update_buf(&mut self, ctx: &mut SizeCtx) { if self.content.changed { self.content.changed = false; self.view.buf.set_text( @@ -80,36 +96,26 @@ impl Text { None, ); } - self.view.draw(ctx) } } impl Widget for Text { fn draw(&mut self, painter: &mut Painter) { - let tex = self.update_buf(&mut painter.size_ctx()); - let region = text_region(&tex, self.align); - painter.texture_within(&tex.handle, region); + self.update_buf(&mut painter.size_ctx()); + self.view.draw(painter); } fn desired_width(&mut self, ctx: &mut SizeCtx) -> Len { - Len::abs(self.update_buf(ctx).size.x) + self.update_buf(ctx); + self.view.desired_width(ctx) } fn desired_height(&mut self, ctx: &mut SizeCtx) -> Len { - Len::abs(self.update_buf(ctx).size.y) + self.update_buf(ctx); + self.view.desired_height(ctx) } } -pub fn text_region(tex: &TextTexture, align: RegionAlign) -> UiRegion { - let tex_dims = tex.handle.size(); - let mut region = tex.size.align(align); - region.x.start.abs += tex.top_left_offset.x; - region.y.start.abs += tex.top_left_offset.y; - region.x.end.abs = region.x.start.abs + tex_dims.x; - region.y.end.abs = region.y.start.abs + tex_dims.y; - region -} - impl Deref for Text { type Target = TextAttrs; diff --git a/src/layout/color.rs b/src/layout/color.rs index c7d9ea6..0b94b09 100644 --- a/src/layout/color.rs +++ b/src/layout/color.rs @@ -2,7 +2,7 @@ /// stored in linear for sane manipulation #[repr(C)] -#[derive(Clone, Copy, bytemuck::Zeroable, Debug)] +#[derive(Clone, Copy, Hash, PartialEq, Eq, bytemuck::Zeroable, Debug)] pub struct Color { pub r: T, pub g: T, diff --git a/src/layout/painter.rs b/src/layout/painter.rs index bc14650..0ed875d 100644 --- a/src/layout/painter.rs +++ b/src/layout/painter.rs @@ -1,6 +1,6 @@ use crate::{ layout::{ - Axis, Layers, Len, Modules, Size, TextAttrs, TextBuffer, TextData, TextTexture, + Axis, Layers, Len, Modules, Size, TextAttrs, TextBuffer, TextData, RenderedText, TextureHandle, Textures, UiRegion, UiVec2, Vec2, WidgetId, Widgets, }, render::{Mask, MaskIdx, Primitive, PrimitiveHandle, PrimitiveInst}, @@ -422,7 +422,7 @@ impl<'a, 'c> Painter<'a, 'c> { } /// returns (handle, offset from top left) - pub fn render_text(&mut self, buffer: &mut TextBuffer, attrs: &TextAttrs) -> TextTexture { + pub fn render_text(&mut self, buffer: &mut TextBuffer, attrs: &TextAttrs) -> RenderedText { self.ctx.text.draw(buffer, attrs, self.ctx.textures) } @@ -539,7 +539,6 @@ impl SizeCtx<'_> { // if let Some(&(outer, len)) = self.cache_height.get(&id) // && outer == self.outer // { - // prntln!("FAIL {id:?}"); // self.checked_height.insert(id, (self.outer, len)); // return len; // } @@ -584,7 +583,7 @@ impl SizeCtx<'_> { self.output_size } - pub fn draw_text(&mut self, buffer: &mut TextBuffer, attrs: &TextAttrs) -> TextTexture { + pub fn draw_text(&mut self, buffer: &mut TextBuffer, attrs: &TextAttrs) -> RenderedText { self.text.draw(buffer, attrs, self.textures) } diff --git a/src/layout/text.rs b/src/layout/text.rs index c2b4295..0998407 100644 --- a/src/layout/text.rs +++ b/src/layout/text.rs @@ -1,12 +1,11 @@ -use cosmic_text::{ - Attrs, AttrsList, Buffer, Family, FontSystem, Metrics, SwashCache, SwashContent, -}; -use image::{Rgba, RgbaImage}; +use std::simd::{Simd, num::SimdUint}; -use crate::{ - layout::{Align, RegionAlign, TextureHandle, Textures, UiColor, Vec2}, - util::HashMap, +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 { @@ -16,6 +15,7 @@ pub mod text_lib { pub struct TextData { pub font_system: FontSystem, pub swash_cache: SwashCache, + glyph_cache: Vec<(Placement, CacheKey, Color)>, } impl Default for TextData { @@ -23,6 +23,7 @@ impl Default for TextData { Self { font_system: FontSystem::new(), swash_cache: SwashCache::new(), + glyph_cache: Default::default(), } } } @@ -76,29 +77,30 @@ impl TextData { buffer: &mut TextBuffer, attrs: &TextAttrs, textures: &mut Textures, - ) -> TextTexture { + ) -> 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 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 cosmic_color = { + 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 => cosmic_color, + None => text_color, }; if let Some(img) = self @@ -112,35 +114,8 @@ impl TextData { 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); - let mut merge = |i, color: [u8; 4]| { - let x = i % pos.width as usize; - let y = i / pos.width as usize; - let pos = (x as i32 + pos.left, y as i32 + pos.top); - if let Some(pixel) = pixels.get_mut(&pos) { - for i in 0..4 { - // TODO: no clue if proper alpha blending should be done - pixel[i] = color[i].saturating_add(pixel[i]); - } - } else { - pixels.insert(pos, color); - } - }; - match img.content { - SwashContent::Mask => { - for (i, a) in img.data.iter().enumerate() { - let mut color = glyph_color.as_rgba(); - color[3] = ((color[3] as u32 * *a as u32) / u8::MAX as u32) as u8; - merge(i, color); - } - } - SwashContent::SubpixelMask => todo!(), - SwashContent::Color => { - let (colors, _) = img.data.as_chunks::<4>(); - for (i, color) in colors.iter().enumerate() { - merge(i, *color); - } - } - } + self.glyph_cache + .push((pos, physical_glyph.cache_key, glyph_color)); } } max_width = max_width.max(run.line_w); @@ -150,12 +125,52 @@ impl TextData { 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, Rgba(color)); + 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); + } + } + } } - TextTexture { + + 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), @@ -164,9 +179,8 @@ impl TextData { } #[derive(Clone)] -pub struct TextTexture { +pub struct RenderedText { pub handle: TextureHandle, pub top_left_offset: Vec2, pub size: Vec2, } - diff --git a/src/lib.rs b/src/lib.rs index ae4731f..89b8a85 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -7,6 +7,7 @@ #![feature(fn_traits)] #![feature(const_cmp)] #![feature(const_destruct)] +#![feature(portable_simd)] pub mod core; pub mod layout; diff --git a/src/util/math.rs b/src/util/math.rs index e4e6600..4bcdbce 100644 --- a/src/util/math.rs +++ b/src/util/math.rs @@ -48,7 +48,7 @@ macro_rules! impl_op { } impl const $opa for $T { fn $fna(&mut self, rhs: Self) { - $(self.$field.$fna(rhs.$field);)* + *self = self.$fn(rhs); } } impl const $op for $T { @@ -71,7 +71,7 @@ macro_rules! impl_op { } impl const $opa for $T { fn $fna(&mut self, rhs: f32) { - $(self.$field.$fna(rhs);)* + *self = self.$fn(rhs); } } }