diff options
Diffstat (limited to 'tiny_skia/src')
-rw-r--r-- | tiny_skia/src/backend.rs | 102 | ||||
-rw-r--r-- | tiny_skia/src/geometry.rs | 122 | ||||
-rw-r--r-- | tiny_skia/src/primitive.rs | 4 | ||||
-rw-r--r-- | tiny_skia/src/raster.rs | 12 | ||||
-rw-r--r-- | tiny_skia/src/text.rs | 90 | ||||
-rw-r--r-- | tiny_skia/src/vector.rs | 14 | ||||
-rw-r--r-- | tiny_skia/src/window/compositor.rs | 141 |
7 files changed, 353 insertions, 132 deletions
diff --git a/tiny_skia/src/backend.rs b/tiny_skia/src/backend.rs index 65aca4b0..d1393b4d 100644 --- a/tiny_skia/src/backend.rs +++ b/tiny_skia/src/backend.rs @@ -1,7 +1,7 @@ use crate::core::{Background, Color, Gradient, Rectangle, Vector}; use crate::graphics::backend; use crate::graphics::text; -use crate::graphics::{Damage, Viewport}; +use crate::graphics::Viewport; use crate::primitive::{self, Primitive}; use std::borrow::Cow; @@ -362,11 +362,10 @@ impl Backend { paragraph, position, color, + clip_bounds: text_clip_bounds, } => { let physical_bounds = - (Rectangle::new(*position, paragraph.min_bounds) - + translation) - * scale_factor; + (*text_clip_bounds + translation) * scale_factor; if !clip_bounds.intersects(&physical_bounds) { return; @@ -384,6 +383,31 @@ impl Backend { clip_mask, ); } + Primitive::Editor { + editor, + position, + color, + clip_bounds: text_clip_bounds, + } => { + let physical_bounds = + (*text_clip_bounds + translation) * scale_factor; + + if !clip_bounds.intersects(&physical_bounds) { + return; + } + + let clip_mask = (!physical_bounds.is_within(&clip_bounds)) + .then_some(clip_mask as &_); + + self.text_pipeline.draw_editor( + editor, + *position + translation, + *color, + scale_factor, + pixels, + clip_mask, + ); + } Primitive::Text { content, bounds, @@ -394,9 +418,10 @@ impl Backend { horizontal_alignment, vertical_alignment, shaping, + clip_bounds: text_clip_bounds, } => { let physical_bounds = - (primitive.bounds() + translation) * scale_factor; + (*text_clip_bounds + translation) * scale_factor; if !clip_bounds.intersects(&physical_bounds) { return; @@ -420,8 +445,41 @@ impl Backend { clip_mask, ); } + Primitive::RawText(text::Raw { + buffer, + position, + color, + clip_bounds: text_clip_bounds, + }) => { + let Some(buffer) = buffer.upgrade() else { + return; + }; + + let physical_bounds = + (*text_clip_bounds + translation) * scale_factor; + + if !clip_bounds.intersects(&physical_bounds) { + return; + } + + let clip_mask = (!physical_bounds.is_within(&clip_bounds)) + .then_some(clip_mask as &_); + + self.text_pipeline.draw_raw( + &buffer, + *position + translation, + *color, + scale_factor, + pixels, + clip_mask, + ); + } #[cfg(feature = "image")] - Primitive::Image { handle, bounds } => { + Primitive::Image { + handle, + filter_method, + bounds, + } => { let physical_bounds = (*bounds + translation) * scale_factor; if !clip_bounds.intersects(&physical_bounds) { @@ -437,8 +495,14 @@ impl Backend { ) .post_scale(scale_factor, scale_factor); - self.raster_pipeline - .draw(handle, *bounds, pixels, transform, clip_mask); + self.raster_pipeline.draw( + handle, + *filter_method, + *bounds, + pixels, + transform, + clip_mask, + ); } #[cfg(not(feature = "image"))] Primitive::Image { .. } => { @@ -479,7 +543,6 @@ impl Backend { path, paint, rule, - transform, }) => { let bounds = path.bounds(); @@ -502,9 +565,11 @@ impl Backend { path, paint, *rule, - transform - .post_translate(translation.x, translation.y) - .post_scale(scale_factor, scale_factor), + tiny_skia::Transform::from_translate( + translation.x, + translation.y, + ) + .post_scale(scale_factor, scale_factor), clip_mask, ); } @@ -512,7 +577,6 @@ impl Backend { path, paint, stroke, - transform, }) => { let bounds = path.bounds(); @@ -535,9 +599,11 @@ impl Backend { path, paint, stroke, - transform - .post_translate(translation.x, translation.y) - .post_scale(scale_factor, scale_factor), + tiny_skia::Transform::from_translate( + translation.x, + translation.y, + ) + .post_scale(scale_factor, scale_factor), clip_mask, ); } @@ -803,10 +869,6 @@ impl iced_graphics::Backend for Backend { } impl backend::Text for Backend { - fn font_system(&self) -> &text::FontSystem { - self.text_pipeline.font_system() - } - fn load_font(&mut self, font: Cow<'static, [u8]>) { self.text_pipeline.load_font(font); } diff --git a/tiny_skia/src/geometry.rs b/tiny_skia/src/geometry.rs index 1d14aa03..74a08d38 100644 --- a/tiny_skia/src/geometry.rs +++ b/tiny_skia/src/geometry.rs @@ -1,4 +1,5 @@ -use crate::core::{Point, Rectangle, Size, Vector}; +use crate::core::text::LineHeight; +use crate::core::{Pixels, Point, Rectangle, Size, Vector}; use crate::graphics::geometry::fill::{self, Fill}; use crate::graphics::geometry::stroke::{self, Stroke}; use crate::graphics::geometry::{Path, Style, Text}; @@ -39,17 +40,22 @@ impl Frame { } pub fn fill(&mut self, path: &Path, fill: impl Into<Fill>) { - let Some(path) = convert_path(path) else { + let Some(path) = + convert_path(path).and_then(|path| path.transform(self.transform)) + else { return; }; + let fill = fill.into(); + let mut paint = into_paint(fill.style); + paint.shader.transform(self.transform); + self.primitives .push(Primitive::Custom(primitive::Custom::Fill { path, - paint: into_paint(fill.style), + paint, rule: into_fill_rule(fill.rule), - transform: self.transform, })); } @@ -59,73 +65,111 @@ impl Frame { size: Size, fill: impl Into<Fill>, ) { - let Some(path) = convert_path(&Path::rectangle(top_left, size)) else { + let Some(path) = convert_path(&Path::rectangle(top_left, size)) + .and_then(|path| path.transform(self.transform)) + else { return; }; + let fill = fill.into(); + let mut paint = tiny_skia::Paint { + anti_alias: false, + ..into_paint(fill.style) + }; + paint.shader.transform(self.transform); + self.primitives .push(Primitive::Custom(primitive::Custom::Fill { path, - paint: tiny_skia::Paint { - anti_alias: false, - ..into_paint(fill.style) - }, + paint, rule: into_fill_rule(fill.rule), - transform: self.transform, })); } pub fn stroke<'a>(&mut self, path: &Path, stroke: impl Into<Stroke<'a>>) { - let Some(path) = convert_path(path) else { + let Some(path) = + convert_path(path).and_then(|path| path.transform(self.transform)) + else { return; }; let stroke = stroke.into(); let skia_stroke = into_stroke(&stroke); + let mut paint = into_paint(stroke.style); + paint.shader.transform(self.transform); + self.primitives .push(Primitive::Custom(primitive::Custom::Stroke { path, - paint: into_paint(stroke.style), + paint, stroke: skia_stroke, - transform: self.transform, })); } pub fn fill_text(&mut self, text: impl Into<Text>) { let text = text.into(); - let position = if self.transform.is_identity() { - text.position - } else { - let mut transformed = [tiny_skia::Point { - x: text.position.x, - y: text.position.y, - }]; - - self.transform.map_points(&mut transformed); - - Point::new(transformed[0].x, transformed[0].y) - }; - - // TODO: Use vectorial text instead of primitive - self.primitives.push(Primitive::Text { - content: text.content, - bounds: Rectangle { + let (scale_x, scale_y) = self.transform.get_scale(); + + if self.transform.is_scale_translate() + && scale_x == scale_y + && scale_x > 0.0 + && scale_y > 0.0 + { + let (position, size, line_height) = if self.transform.is_identity() + { + (text.position, text.size, text.line_height) + } else { + let mut position = [tiny_skia::Point { + x: text.position.x, + y: text.position.y, + }]; + + self.transform.map_points(&mut position); + + let size = text.size.0 * scale_y; + + let line_height = match text.line_height { + LineHeight::Absolute(size) => { + LineHeight::Absolute(Pixels(size.0 * scale_y)) + } + LineHeight::Relative(factor) => { + LineHeight::Relative(factor) + } + }; + + ( + Point::new(position[0].x, position[0].y), + size.into(), + line_height, + ) + }; + + let bounds = Rectangle { x: position.x, y: position.y, width: f32::INFINITY, height: f32::INFINITY, - }, - color: text.color, - size: text.size, - line_height: text.line_height, - font: text.font, - horizontal_alignment: text.horizontal_alignment, - vertical_alignment: text.vertical_alignment, - shaping: text.shaping, - }); + }; + + // TODO: Honor layering! + self.primitives.push(Primitive::Text { + content: text.content, + bounds, + color: text.color, + size, + line_height, + font: text.font, + horizontal_alignment: text.horizontal_alignment, + vertical_alignment: text.vertical_alignment, + shaping: text.shaping, + clip_bounds: Rectangle::with_size(Size::INFINITY), + }); + } else { + text.draw_with(|path, color| self.fill(&path, color)); + } } pub fn push_transform(&mut self) { diff --git a/tiny_skia/src/primitive.rs b/tiny_skia/src/primitive.rs index 0ed24969..7718d542 100644 --- a/tiny_skia/src/primitive.rs +++ b/tiny_skia/src/primitive.rs @@ -13,8 +13,6 @@ pub enum Custom { paint: tiny_skia::Paint<'static>, /// The fill rule to follow. rule: tiny_skia::FillRule, - /// The transform to apply to the path. - transform: tiny_skia::Transform, }, /// A path stroked with some paint. Stroke { @@ -24,8 +22,6 @@ pub enum Custom { paint: tiny_skia::Paint<'static>, /// The stroke settings. stroke: tiny_skia::Stroke, - /// The transform to apply to the path. - transform: tiny_skia::Transform, }, } diff --git a/tiny_skia/src/raster.rs b/tiny_skia/src/raster.rs index d13b1167..5f17ae60 100644 --- a/tiny_skia/src/raster.rs +++ b/tiny_skia/src/raster.rs @@ -28,6 +28,7 @@ impl Pipeline { pub fn draw( &mut self, handle: &raster::Handle, + filter_method: raster::FilterMethod, bounds: Rectangle, pixels: &mut tiny_skia::PixmapMut<'_>, transform: tiny_skia::Transform, @@ -39,12 +40,21 @@ impl Pipeline { let transform = transform.pre_scale(width_scale, height_scale); + let quality = match filter_method { + raster::FilterMethod::Linear => { + tiny_skia::FilterQuality::Bilinear + } + raster::FilterMethod::Nearest => { + tiny_skia::FilterQuality::Nearest + } + }; + pixels.draw_pixmap( (bounds.x / width_scale) as i32, (bounds.y / height_scale) as i32, image, &tiny_skia::PixmapPaint { - quality: tiny_skia::FilterQuality::Bilinear, + quality, ..Default::default() }, transform, diff --git a/tiny_skia/src/text.rs b/tiny_skia/src/text.rs index cb3ef54c..9413e311 100644 --- a/tiny_skia/src/text.rs +++ b/tiny_skia/src/text.rs @@ -1,9 +1,10 @@ use crate::core::alignment; use crate::core::text::{LineHeight, Shaping}; -use crate::core::{Color, Font, Pixels, Point, Rectangle}; +use crate::core::{Color, Font, Pixels, Point, Rectangle, Size}; use crate::graphics::text::cache::{self, Cache}; +use crate::graphics::text::editor; +use crate::graphics::text::font_system; use crate::graphics::text::paragraph; -use crate::graphics::text::FontSystem; use rustc_hash::{FxHashMap, FxHashSet}; use std::borrow::Cow; @@ -12,7 +13,6 @@ use std::collections::hash_map; #[allow(missing_debug_implementations)] pub struct Pipeline { - font_system: FontSystem, glyph_cache: GlyphCache, cache: RefCell<Cache>, } @@ -20,18 +20,16 @@ pub struct Pipeline { impl Pipeline { pub fn new() -> Self { Pipeline { - font_system: FontSystem::new(), glyph_cache: GlyphCache::new(), cache: RefCell::new(Cache::new()), } } - pub fn font_system(&self) -> &FontSystem { - &self.font_system - } - pub fn load_font(&mut self, bytes: Cow<'static, [u8]>) { - self.font_system.load_font(bytes); + font_system() + .write() + .expect("Write font system") + .load_font(bytes); self.cache = RefCell::new(Cache::new()); } @@ -51,8 +49,10 @@ impl Pipeline { return; }; + let mut font_system = font_system().write().expect("Write font system"); + draw( - self.font_system.get_mut(), + font_system.raw(), &mut self.glyph_cache, paragraph.buffer(), Rectangle::new(position, paragraph.min_bounds()), @@ -65,6 +65,37 @@ impl Pipeline { ); } + pub fn draw_editor( + &mut self, + editor: &editor::Weak, + position: Point, + color: Color, + scale_factor: f32, + pixels: &mut tiny_skia::PixmapMut<'_>, + clip_mask: Option<&tiny_skia::Mask>, + ) { + use crate::core::text::Editor as _; + + let Some(editor) = editor.upgrade() else { + return; + }; + + let mut font_system = font_system().write().expect("Write font system"); + + draw( + font_system.raw(), + &mut self.glyph_cache, + editor.buffer(), + Rectangle::new(position, editor.bounds()), + color, + alignment::Horizontal::Left, + alignment::Vertical::Top, + scale_factor, + pixels, + clip_mask, + ); + } + pub fn draw_cached( &mut self, content: &str, @@ -82,7 +113,9 @@ impl Pipeline { ) { let line_height = f32::from(line_height.to_absolute(size)); - let font_system = self.font_system.get_mut(); + let mut font_system = font_system().write().expect("Write font system"); + let font_system = font_system.raw(); + let key = cache::Key { bounds: bounds.size(), content, @@ -115,6 +148,33 @@ impl Pipeline { ); } + pub fn draw_raw( + &mut self, + buffer: &cosmic_text::Buffer, + position: Point, + color: Color, + scale_factor: f32, + pixels: &mut tiny_skia::PixmapMut<'_>, + clip_mask: Option<&tiny_skia::Mask>, + ) { + let mut font_system = font_system().write().expect("Write font system"); + + let (width, height) = buffer.size(); + + draw( + font_system.raw(), + &mut self.glyph_cache, + buffer, + Rectangle::new(position, Size::new(width, height)), + color, + alignment::Horizontal::Left, + alignment::Vertical::Top, + scale_factor, + pixels, + clip_mask, + ); + } + pub fn trim_cache(&mut self) { self.cache.get_mut().trim(); self.glyph_cache.trim(); @@ -155,7 +215,7 @@ fn draw( if let Some((buffer, placement)) = glyph_cache.allocate( physical_glyph.cache_key, - color, + glyph.color_opt.map(from_color).unwrap_or(color), font_system, &mut swash, ) { @@ -180,6 +240,12 @@ fn draw( } } +fn from_color(color: cosmic_text::Color) -> Color { + let [r, g, b, a] = color.as_rgba(); + + Color::from_rgba8(r, g, b, a as f32 / 255.0) +} + #[derive(Debug, Clone, Default)] struct GlyphCache { entries: FxHashMap< diff --git a/tiny_skia/src/vector.rs b/tiny_skia/src/vector.rs index a1cd269d..fd1ab3de 100644 --- a/tiny_skia/src/vector.rs +++ b/tiny_skia/src/vector.rs @@ -1,7 +1,8 @@ use crate::core::svg::{Data, Handle}; use crate::core::{Color, Rectangle, Size}; +use crate::graphics::text; -use resvg::usvg; +use resvg::usvg::{self, TreeTextToPath}; use rustc_hash::{FxHashMap, FxHashSet}; use std::cell::RefCell; @@ -77,7 +78,7 @@ impl Cache { let id = handle.id(); if let hash_map::Entry::Vacant(entry) = self.trees.entry(id) { - let svg = match handle.data() { + let mut svg = match handle.data() { Data::Path(path) => { fs::read_to_string(path).ok().and_then(|contents| { usvg::Tree::from_str( @@ -92,6 +93,15 @@ impl Cache { } }; + if let Some(svg) = &mut svg { + if svg.has_text_nodes() { + let mut font_system = + text::font_system().write().expect("Write font system"); + + svg.convert_text(font_system.raw().db_mut()); + } + } + let _ = entry.insert(svg); } diff --git a/tiny_skia/src/window/compositor.rs b/tiny_skia/src/window/compositor.rs index 828e522f..781ed8a5 100644 --- a/tiny_skia/src/window/compositor.rs +++ b/tiny_skia/src/window/compositor.rs @@ -4,19 +4,25 @@ use crate::graphics::damage; use crate::graphics::{Error, Viewport}; use crate::{Backend, Primitive, Renderer, Settings}; -use raw_window_handle::{HasRawDisplayHandle, HasRawWindowHandle}; +use std::collections::VecDeque; use std::marker::PhantomData; +use std::num::NonZeroU32; pub struct Compositor<Theme> { + context: softbuffer::Context<Box<dyn compositor::Window>>, + settings: Settings, _theme: PhantomData<Theme>, } pub struct Surface { - window: softbuffer::GraphicsContext, - buffer: Vec<u32>, + window: softbuffer::Surface< + Box<dyn compositor::Window>, + Box<dyn compositor::Window>, + >, clip_mask: tiny_skia::Mask, - primitives: Option<Vec<Primitive>>, + primitive_stack: VecDeque<Vec<Primitive>>, background_color: Color, + max_age: u8, } impl<Theme> crate::graphics::Compositor for Compositor<Theme> { @@ -24,53 +30,64 @@ impl<Theme> crate::graphics::Compositor for Compositor<Theme> { type Renderer = Renderer<Theme>; type Surface = Surface; - fn new<W: HasRawWindowHandle + HasRawDisplayHandle>( + fn new<W: compositor::Window>( settings: Self::Settings, - _compatible_window: Option<&W>, - ) -> Result<(Self, Self::Renderer), Error> { - let (compositor, backend) = new(); + compatible_window: W, + ) -> Result<Self, Error> { + Ok(new(settings, compatible_window)) + } - Ok(( - compositor, - Renderer::new( - backend, - settings.default_font, - settings.default_text_size, - ), - )) + fn create_renderer(&self) -> Self::Renderer { + Renderer::new( + Backend::new(), + self.settings.default_font, + self.settings.default_text_size, + ) } - fn create_surface<W: HasRawWindowHandle + HasRawDisplayHandle>( + fn create_surface<W: compositor::Window + Clone>( &mut self, - window: &W, + window: W, width: u32, height: u32, - ) -> Surface { - #[allow(unsafe_code)] - let window = - unsafe { softbuffer::GraphicsContext::new(window, window) } - .expect("Create softbuffer for window"); + ) -> Self::Surface { + let window = softbuffer::Surface::new( + &self.context, + Box::new(window.clone()) as _, + ) + .expect("Create softbuffer surface for window"); - Surface { + let mut surface = Surface { window, - buffer: vec![0; width as usize * height as usize], clip_mask: tiny_skia::Mask::new(width, height) .expect("Create clip mask"), - primitives: None, + primitive_stack: VecDeque::new(), background_color: Color::BLACK, - } + max_age: 0, + }; + + self.configure_surface(&mut surface, width, height); + + surface } fn configure_surface( &mut self, - surface: &mut Surface, + surface: &mut Self::Surface, width: u32, height: u32, ) { - surface.buffer.resize((width * height) as usize, 0); + surface + .window + .resize( + NonZeroU32::new(width).expect("Non-zero width"), + NonZeroU32::new(height).expect("Non-zero height"), + ) + .expect("Resize surface"); + surface.clip_mask = tiny_skia::Mask::new(width, height).expect("Create clip mask"); - surface.primitives = None; + surface.primitive_stack.clear(); } fn fetch_information(&self) -> Information { @@ -121,13 +138,19 @@ impl<Theme> crate::graphics::Compositor for Compositor<Theme> { } } -pub fn new<Theme>() -> (Compositor<Theme>, Backend) { - ( - Compositor { - _theme: PhantomData, - }, - Backend::new(), - ) +pub fn new<W: compositor::Window, Theme>( + settings: Settings, + compatible_window: W, +) -> Compositor<Theme> { + #[allow(unsafe_code)] + let context = softbuffer::Context::new(Box::new(compatible_window) as _) + .expect("Create softbuffer context"); + + Compositor { + context, + settings, + _theme: PhantomData, + } } pub fn present<T: AsRef<str>>( @@ -141,16 +164,25 @@ pub fn present<T: AsRef<str>>( let physical_size = viewport.physical_size(); let scale_factor = viewport.scale_factor() as f32; - let mut pixels = tiny_skia::PixmapMut::from_bytes( - bytemuck::cast_slice_mut(&mut surface.buffer), - physical_size.width, - physical_size.height, - ) - .expect("Create pixel map"); + let mut buffer = surface + .window + .buffer_mut() + .map_err(|_| compositor::SurfaceError::Lost)?; + + let last_primitives = { + let age = buffer.age(); - let damage = surface - .primitives - .as_deref() + surface.max_age = surface.max_age.max(age); + surface.primitive_stack.truncate(surface.max_age as usize); + + if age > 0 { + surface.primitive_stack.get(age as usize - 1) + } else { + None + } + }; + + let damage = last_primitives .and_then(|last_primitives| { (surface.background_color == background_color) .then(|| damage::list(last_primitives, primitives)) @@ -161,11 +193,18 @@ pub fn present<T: AsRef<str>>( return Ok(()); } - surface.primitives = Some(primitives.to_vec()); + surface.primitive_stack.push_front(primitives.to_vec()); surface.background_color = background_color; let damage = damage::group(damage, scale_factor, physical_size); + let mut pixels = tiny_skia::PixmapMut::from_bytes( + bytemuck::cast_slice_mut(&mut buffer), + physical_size.width, + physical_size.height, + ) + .expect("Create pixel map"); + backend.draw( &mut pixels, &mut surface.clip_mask, @@ -176,13 +215,7 @@ pub fn present<T: AsRef<str>>( overlay, ); - surface.window.set_buffer( - &surface.buffer, - physical_size.width as u16, - physical_size.height as u16, - ); - - Ok(()) + buffer.present().map_err(|_| compositor::SurfaceError::Lost) } pub fn screenshot<T: AsRef<str>>( |