diff options
Diffstat (limited to 'tiny_skia/src')
-rw-r--r-- | tiny_skia/src/backend.rs | 150 | ||||
-rw-r--r-- | tiny_skia/src/text.rs | 54 | ||||
-rw-r--r-- | tiny_skia/src/window/compositor.rs | 76 |
3 files changed, 220 insertions, 60 deletions
diff --git a/tiny_skia/src/backend.rs b/tiny_skia/src/backend.rs index 9d0fc527..ba038052 100644 --- a/tiny_skia/src/backend.rs +++ b/tiny_skia/src/backend.rs @@ -174,7 +174,18 @@ impl Backend { ) .post_scale(scale_factor, scale_factor); - let path = rounded_rectangle(*bounds, *border_radius); + // Make sure the border radius is not larger than the bounds + let border_width = border_width + .min(bounds.width / 2.0) + .min(bounds.height / 2.0); + + let mut fill_border_radius = *border_radius; + for radius in &mut fill_border_radius { + *radius = (*radius) + .min(bounds.width / 2.0) + .min(bounds.height / 2.0); + } + let path = rounded_rectangle(*bounds, fill_border_radius); pixels.fill_path( &path, @@ -236,23 +247,120 @@ impl Backend { clip_mask, ); - if *border_width > 0.0 { - pixels.stroke_path( - &path, - &tiny_skia::Paint { - shader: tiny_skia::Shader::SolidColor(into_color( - *border_color, - )), - anti_alias: true, - ..tiny_skia::Paint::default() - }, - &tiny_skia::Stroke { - width: *border_width, - ..tiny_skia::Stroke::default() - }, - transform, - clip_mask, - ); + if border_width > 0.0 { + // Border path is offset by half the border width + let border_bounds = Rectangle { + x: bounds.x + border_width / 2.0, + y: bounds.y + border_width / 2.0, + width: bounds.width - border_width, + height: bounds.height - border_width, + }; + + // Make sure the border radius is correct + let mut border_radius = *border_radius; + let mut is_simple_border = true; + + for radius in &mut border_radius { + *radius = if *radius == 0.0 { + // Path should handle this fine + 0.0 + } else if *radius > border_width / 2.0 { + *radius - border_width / 2.0 + } else { + is_simple_border = false; + 0.0 + } + .min(border_bounds.width / 2.0) + .min(border_bounds.height / 2.0); + } + + // Stroking a path works well in this case + if is_simple_border { + let border_path = + rounded_rectangle(border_bounds, border_radius); + + pixels.stroke_path( + &border_path, + &tiny_skia::Paint { + shader: tiny_skia::Shader::SolidColor( + into_color(*border_color), + ), + anti_alias: true, + ..tiny_skia::Paint::default() + }, + &tiny_skia::Stroke { + width: border_width, + ..tiny_skia::Stroke::default() + }, + transform, + clip_mask, + ); + } else { + // Draw corners that have too small border radii as having no border radius, + // but mask them with the rounded rectangle with the correct border radius. + let mut temp_pixmap = tiny_skia::Pixmap::new( + bounds.width as u32, + bounds.height as u32, + ) + .unwrap(); + + let mut quad_mask = tiny_skia::Mask::new( + bounds.width as u32, + bounds.height as u32, + ) + .unwrap(); + + let zero_bounds = Rectangle { + x: 0.0, + y: 0.0, + width: bounds.width, + height: bounds.height, + }; + let path = + rounded_rectangle(zero_bounds, fill_border_radius); + + quad_mask.fill_path( + &path, + tiny_skia::FillRule::EvenOdd, + true, + transform, + ); + let path_bounds = Rectangle { + x: border_width / 2.0, + y: border_width / 2.0, + width: bounds.width - border_width, + height: bounds.height - border_width, + }; + + let border_radius_path = + rounded_rectangle(path_bounds, border_radius); + + temp_pixmap.stroke_path( + &border_radius_path, + &tiny_skia::Paint { + shader: tiny_skia::Shader::SolidColor( + into_color(*border_color), + ), + anti_alias: true, + ..tiny_skia::Paint::default() + }, + &tiny_skia::Stroke { + width: border_width, + ..tiny_skia::Stroke::default() + }, + transform, + Some(&quad_mask), + ); + + pixels.draw_pixmap( + bounds.x as i32, + bounds.y as i32, + temp_pixmap.as_ref(), + &tiny_skia::PixmapPaint::default(), + transform, + clip_mask, + ); + } } } Primitive::Text { @@ -658,12 +766,6 @@ fn adjust_clip_mask(clip_mask: &mut tiny_skia::Mask, bounds: Rectangle) { ); } -impl iced_graphics::Backend for Backend { - fn trim_measurements(&mut self) { - self.text_pipeline.trim_measurement_cache(); - } -} - impl backend::Text for Backend { const ICON_FONT: Font = Font::with_name("Iced-Icons"); const CHECKMARK_ICON: char = '\u{f00c}'; diff --git a/tiny_skia/src/text.rs b/tiny_skia/src/text.rs index a34c7317..3441da8f 100644 --- a/tiny_skia/src/text.rs +++ b/tiny_skia/src/text.rs @@ -14,8 +14,7 @@ use std::sync::Arc; pub struct Pipeline { font_system: RefCell<cosmic_text::FontSystem>, glyph_cache: GlyphCache, - measurement_cache: RefCell<Cache>, - render_cache: Cache, + cache: RefCell<Cache>, } impl Pipeline { @@ -23,14 +22,12 @@ impl Pipeline { Pipeline { font_system: RefCell::new(cosmic_text::FontSystem::new_with_fonts( [cosmic_text::fontdb::Source::Binary(Arc::new( - include_bytes!("../../wgpu/fonts/Iced-Icons.ttf") - .as_slice(), + include_bytes!("../fonts/Iced-Icons.ttf").as_slice(), ))] .into_iter(), )), glyph_cache: GlyphCache::new(), - measurement_cache: RefCell::new(Cache::new()), - render_cache: Cache::new(), + cache: RefCell::new(Cache::new()), } } @@ -38,6 +35,8 @@ impl Pipeline { self.font_system.get_mut().db_mut().load_font_source( cosmic_text::fontdb::Source::Binary(Arc::new(bytes.into_owned())), ); + + self.cache = RefCell::new(Cache::new()); } pub fn draw( @@ -55,20 +54,11 @@ impl Pipeline { pixels: &mut tiny_skia::PixmapMut<'_>, clip_mask: Option<&tiny_skia::Mask>, ) { - let line_height = - f32::from(line_height.to_absolute(Pixels(size))) * scale_factor; - - let bounds = bounds * scale_factor; - let size = size * scale_factor; + let line_height = f32::from(line_height.to_absolute(Pixels(size))); let font_system = self.font_system.get_mut(); let key = Key { - bounds: { - let size = bounds.size(); - - // TODO: Reuse buffers from layouting - Size::new(size.width.ceil(), size.height.ceil()) - }, + bounds: bounds.size(), content, font, size, @@ -76,7 +66,7 @@ impl Pipeline { shaping, }; - let (_, buffer) = self.render_cache.allocate(font_system, key); + let (_, buffer) = self.cache.get_mut().allocate(font_system, key); let (total_lines, max_width) = buffer .layout_runs() @@ -85,7 +75,10 @@ impl Pipeline { (i + 1, buffer.line_w.max(max)) }); - let total_height = total_lines as f32 * line_height; + let total_height = total_lines as f32 * line_height * scale_factor; + let max_width = max_width * scale_factor; + + let bounds = bounds * scale_factor; let x = match horizontal_alignment { alignment::Horizontal::Left => bounds.x, @@ -99,16 +92,14 @@ impl Pipeline { alignment::Vertical::Bottom => bounds.y - total_height, }; - // TODO: Subpixel glyph positioning - let x = x.round() as i32; - let y = y.round() as i32; - let mut swash = cosmic_text::SwashCache::new(); for run in buffer.layout_runs() { for glyph in run.glyphs { + let physical_glyph = glyph.physical((x, y), scale_factor); + if let Some((buffer, placement)) = self.glyph_cache.allocate( - glyph.cache_key, + physical_glyph.cache_key, color, font_system, &mut swash, @@ -121,8 +112,9 @@ impl Pipeline { .expect("Create glyph pixel map"); pixels.draw_pixmap( - x + glyph.x_int + placement.left, - y - glyph.y_int - placement.top + run.line_y as i32, + physical_glyph.x + placement.left, + physical_glyph.y - placement.top + + (run.line_y * scale_factor).round() as i32, pixmap, &tiny_skia::PixmapPaint::default(), tiny_skia::Transform::identity(), @@ -134,7 +126,7 @@ impl Pipeline { } pub fn trim_cache(&mut self) { - self.render_cache.trim(); + self.cache.get_mut().trim(); self.glyph_cache.trim(); } @@ -147,7 +139,7 @@ impl Pipeline { bounds: Size, shaping: Shaping, ) -> (f32, f32) { - let mut measurement_cache = self.measurement_cache.borrow_mut(); + let mut measurement_cache = self.cache.borrow_mut(); let line_height = f32::from(line_height.to_absolute(Pixels(size))); @@ -184,7 +176,7 @@ impl Pipeline { point: Point, _nearest_only: bool, ) -> Option<Hit> { - let mut measurement_cache = self.measurement_cache.borrow_mut(); + let mut measurement_cache = self.cache.borrow_mut(); let line_height = f32::from(line_height.to_absolute(Pixels(size))); @@ -204,10 +196,6 @@ impl Pipeline { Some(Hit::CharOffset(cursor.index)) } - - pub fn trim_measurement_cache(&mut self) { - self.measurement_cache.borrow_mut().trim(); - } } fn to_family(family: font::Family) -> cosmic_text::Family<'static> { diff --git a/tiny_skia/src/window/compositor.rs b/tiny_skia/src/window/compositor.rs index 9999a188..f3be3f16 100644 --- a/tiny_skia/src/window/compositor.rs +++ b/tiny_skia/src/window/compositor.rs @@ -1,5 +1,5 @@ -use crate::core::{Color, Rectangle}; -use crate::graphics::compositor::{self, Information, SurfaceError}; +use crate::core::{Color, Rectangle, Size}; +use crate::graphics::compositor::{self, Information}; use crate::graphics::damage; use crate::graphics::{Error, Primitive, Viewport}; use crate::{Backend, Renderer, Settings}; @@ -79,7 +79,7 @@ impl<Theme> crate::graphics::Compositor for Compositor<Theme> { viewport: &Viewport, background_color: Color, overlay: &[T], - ) -> Result<(), SurfaceError> { + ) -> Result<(), compositor::SurfaceError> { renderer.with_primitives(|backend, primitives| { present( backend, @@ -91,6 +91,26 @@ impl<Theme> crate::graphics::Compositor for Compositor<Theme> { ) }) } + + fn screenshot<T: AsRef<str>>( + &mut self, + renderer: &mut Self::Renderer, + surface: &mut Self::Surface, + viewport: &Viewport, + background_color: Color, + overlay: &[T], + ) -> Vec<u8> { + renderer.with_primitives(|backend, primitives| { + screenshot( + surface, + backend, + primitives, + viewport, + background_color, + overlay, + ) + }) + } } pub fn new<Theme>(settings: Settings) -> (Compositor<Theme>, Backend) { @@ -156,3 +176,53 @@ pub fn present<T: AsRef<str>>( Ok(()) } + +pub fn screenshot<T: AsRef<str>>( + surface: &mut Surface, + backend: &mut Backend, + primitives: &[Primitive], + viewport: &Viewport, + background_color: Color, + overlay: &[T], +) -> Vec<u8> { + let size = viewport.physical_size(); + + let mut offscreen_buffer: Vec<u32> = + vec![0; size.width as usize * size.height as usize]; + + backend.draw( + &mut tiny_skia::PixmapMut::from_bytes( + bytemuck::cast_slice_mut(&mut offscreen_buffer), + size.width, + size.height, + ) + .expect("Create offscreen pixel map"), + &mut surface.clip_mask, + primitives, + viewport, + &[Rectangle::with_size(Size::new( + size.width as f32, + size.height as f32, + ))], + background_color, + overlay, + ); + + offscreen_buffer.iter().fold( + Vec::with_capacity(offscreen_buffer.len() * 4), + |mut acc, pixel| { + const A_MASK: u32 = 0xFF_00_00_00; + const R_MASK: u32 = 0x00_FF_00_00; + const G_MASK: u32 = 0x00_00_FF_00; + const B_MASK: u32 = 0x00_00_00_FF; + + let a = ((A_MASK & pixel) >> 24) as u8; + let r = ((R_MASK & pixel) >> 16) as u8; + let g = ((G_MASK & pixel) >> 8) as u8; + let b = (B_MASK & pixel) as u8; + + acc.extend([r, g, b, a]); + acc + }, + ) +} |