From d39e701b467bdb7bad0074fb40188dd8bd76ebec Mon Sep 17 00:00:00 2001 From: sagudev <16504129+sagudev@users.noreply.github.com> Date: Thu, 24 Jul 2025 20:11:29 +0200 Subject: [PATCH] canvas: Fully stateless backend (#38214) I think this simplifies canvas backends greatly. Before there were many (needless) hoops from abstract to concrete (backend) and back, now we can do most stuff on abstract types. Testing: Existing WPT tests Fixes: #38022 try run: https://github.com/sagudev/servo/actions/runs/16450978211 Signed-off-by: sagudev <16504129+sagudev@users.noreply.github.com> --- components/canvas/backend.rs | 102 ++--- components/canvas/canvas_data.rs | 452 +++++++++++------------ components/canvas/canvas_paint_thread.rs | 438 +++++++++------------- components/canvas/raqote_backend.rs | 393 ++++++++------------ components/script/canvas_state.rs | 8 +- components/shared/canvas/canvas.rs | 63 +++- 6 files changed, 650 insertions(+), 806 deletions(-) diff --git a/components/canvas/backend.rs b/components/canvas/backend.rs index 195d7d99799..be31e6ed982 100644 --- a/components/canvas/backend.rs +++ b/components/canvas/backend.rs @@ -3,55 +3,26 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ use canvas_traits::canvas::{ - CompositionOrBlending, FillOrStrokeStyle, LineCapStyle, LineJoinStyle, Path, + CompositionOptions, FillOrStrokeStyle, LineOptions, Path, ShadowOptions, }; use compositing_traits::SerializableImageData; -use euclid::default::{Point2D, Rect, Size2D, Transform2D, Vector2D}; +use euclid::default::{Point2D, Rect, Size2D, Transform2D}; use pixels::Snapshot; -use style::color::AbsoluteColor; use webrender_api::ImageDescriptor; -use crate::canvas_data::{CanvasPaintState, Filter, TextRun}; +use crate::canvas_data::{Filter, TextRun}; pub(crate) trait Backend: Clone + Sized { - type Pattern<'a>: PatternHelpers + Clone; - type StrokeOptions: StrokeOptionsHelpers + Clone; - type Color: Clone; - type DrawOptions: DrawOptionsHelpers + Clone; - type CompositionOp; type DrawTarget: GenericDrawTarget; type SourceSurface; - type GradientStop; - type GradientStops; - fn get_composition_op(&self, opts: &Self::DrawOptions) -> Self::CompositionOp; - fn need_to_draw_shadow(&self, color: &Self::Color) -> bool; - fn set_shadow_color(&mut self, color: AbsoluteColor, state: &mut CanvasPaintState<'_, Self>); - fn set_fill_style( - &mut self, - style: FillOrStrokeStyle, - state: &mut CanvasPaintState<'_, Self>, - drawtarget: &Self::DrawTarget, - ); - fn set_stroke_style( - &mut self, - style: FillOrStrokeStyle, - state: &mut CanvasPaintState<'_, Self>, - drawtarget: &Self::DrawTarget, - ); - fn set_global_composition( - &mut self, - op: CompositionOrBlending, - state: &mut CanvasPaintState<'_, Self>, - ); fn create_drawtarget(&self, size: Size2D) -> Self::DrawTarget; - fn new_paint_state<'a>(&self) -> CanvasPaintState<'a, Self>; } // This defines required methods for a DrawTarget (currently only implemented for raqote). The // prototypes are derived from the now-removed Azure backend's methods. pub(crate) trait GenericDrawTarget { - fn clear_rect(&mut self, rect: &Rect); + fn clear_rect(&mut self, rect: &Rect, transform: Transform2D); fn copy_surface( &mut self, surface: B::SourceSurface, @@ -66,72 +37,59 @@ pub(crate) trait GenericDrawTarget { dest: Rect, source: Rect, filter: Filter, - draw_options: &B::DrawOptions, + composition_options: CompositionOptions, + transform: Transform2D, ); fn draw_surface_with_shadow( &self, surface: B::SourceSurface, dest: &Point2D, - color: &B::Color, - offset: &Vector2D, - sigma: f32, - operator: B::CompositionOp, + shadow_options: ShadowOptions, + composition_options: CompositionOptions, + ); + fn fill( + &mut self, + path: &Path, + style: FillOrStrokeStyle, + composition_options: CompositionOptions, + transform: Transform2D, ); - fn fill(&mut self, path: &Path, pattern: &B::Pattern<'_>, draw_options: &B::DrawOptions); fn fill_text( &mut self, text_runs: Vec, start: Point2D, - pattern: &B::Pattern<'_>, - draw_options: &B::DrawOptions, + style: FillOrStrokeStyle, + composition_options: CompositionOptions, + transform: Transform2D, ); fn fill_rect( &mut self, rect: &Rect, - pattern: &B::Pattern<'_>, - draw_options: &B::DrawOptions, + style: FillOrStrokeStyle, + composition_options: CompositionOptions, + transform: Transform2D, ); fn get_size(&self) -> Size2D; - fn get_transform(&self) -> Transform2D; fn pop_clip(&mut self); - fn push_clip(&mut self, path: &Path); + fn push_clip(&mut self, path: &Path, transform: Transform2D); fn push_clip_rect(&mut self, rect: &Rect); - fn set_transform(&mut self, matrix: &Transform2D); fn stroke( &mut self, path: &Path, - pattern: &B::Pattern<'_>, - stroke_options: &B::StrokeOptions, - draw_options: &B::DrawOptions, + style: FillOrStrokeStyle, + line_options: LineOptions, + composition_options: CompositionOptions, + transform: Transform2D, ); fn stroke_rect( &mut self, rect: &Rect, - pattern: &B::Pattern<'_>, - stroke_options: &B::StrokeOptions, - draw_options: &B::DrawOptions, + style: FillOrStrokeStyle, + line_options: LineOptions, + composition_options: CompositionOptions, + transform: Transform2D, ); fn surface(&self) -> B::SourceSurface; fn image_descriptor_and_serializable_data(&self) -> (ImageDescriptor, SerializableImageData); fn snapshot(&self) -> Snapshot; } - -pub(crate) trait PatternHelpers { - fn is_zero_size_gradient(&self) -> bool; - fn x_bound(&self) -> Option; - fn y_bound(&self) -> Option; -} - -pub(crate) trait StrokeOptionsHelpers { - fn set_line_width(&mut self, _val: f32); - fn set_miter_limit(&mut self, _val: f32); - fn set_line_join(&mut self, val: LineJoinStyle); - fn set_line_cap(&mut self, val: LineCapStyle); - fn set_line_dash(&mut self, items: Vec); - fn set_line_dash_offset(&mut self, offset: f32); -} - -pub(crate) trait DrawOptionsHelpers { - fn set_alpha(&mut self, val: f32); - fn is_clear(&self) -> bool; -} diff --git a/components/canvas/canvas_data.rs b/components/canvas/canvas_data.rs index 0e76a2c5668..594d2777fd2 100644 --- a/components/canvas/canvas_data.rs +++ b/components/canvas/canvas_data.rs @@ -2,7 +2,6 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -use std::marker::PhantomData; use std::mem; use std::sync::Arc; @@ -18,16 +17,10 @@ use fonts::{ use log::warn; use pixels::Snapshot; use range::Range; -use servo_arc::Arc as ServoArc; -use style::color::AbsoluteColor; -use style::properties::style_structs::Font as FontStyleStruct; use unicode_script::Script; use webrender_api::ImageKey; -use crate::backend::{ - Backend, DrawOptionsHelpers as _, GenericDrawTarget as _, PatternHelpers, - StrokeOptionsHelpers as _, -}; +use crate::backend::{Backend, GenericDrawTarget as _}; // Asserts on WR texture cache update for zero sized image with raw data. // https://github.com/servo/webrender/blob/main/webrender/src/texture_cache.rs#L1475 @@ -109,33 +102,29 @@ pub(crate) enum Filter { Nearest, } -pub(crate) struct CanvasData<'a, B: Backend> { +pub(crate) struct CanvasData { backend: B, drawtarget: B::DrawTarget, - state: CanvasPaintState<'a, B>, - saved_states: Vec>, compositor_api: CrossProcessCompositorApi, image_key: ImageKey, font_context: Arc, } -impl<'a, B: Backend> CanvasData<'a, B> { +impl CanvasData { pub(crate) fn new( size: Size2D, compositor_api: CrossProcessCompositorApi, font_context: Arc, backend: B, - ) -> CanvasData<'a, B> { + ) -> CanvasData { let size = size.max(MIN_WR_IMAGE_SIZE); let draw_target = backend.create_drawtarget(size); let image_key = compositor_api.generate_image_key_blocking().unwrap(); let (descriptor, data) = draw_target.image_descriptor_and_serializable_data(); compositor_api.add_image(image_key, descriptor, data); CanvasData { - state: backend.new_paint_state(), backend, drawtarget: draw_target, - saved_states: vec![], compositor_api, image_key, font_context, @@ -146,12 +135,16 @@ impl<'a, B: Backend> CanvasData<'a, B> { self.image_key } + #[allow(clippy::too_many_arguments)] pub(crate) fn draw_image( &mut self, snapshot: Snapshot, dest_rect: Rect, source_rect: Rect, smoothing_enabled: bool, + shadow_options: ShadowOptions, + composition_options: CompositionOptions, + transform: Transform2D, ) { // We round up the floating pixel values to draw the pixels let source_rect = source_rect.ceil(); @@ -162,42 +155,37 @@ impl<'a, B: Backend> CanvasData<'a, B> { snapshot }; - let draw_options = self.state.draw_options.clone(); - let writer = |draw_target: &mut B::DrawTarget| { + let writer = |draw_target: &mut B::DrawTarget, transform| { write_image::( draw_target, snapshot, dest_rect, smoothing_enabled, - &draw_options, + composition_options, + transform, ); }; - if self.need_to_draw_shadow() { + if shadow_options.need_to_draw_shadow() { let rect = Rect::new( Point2D::new(dest_rect.origin.x as f32, dest_rect.origin.y as f32), Size2D::new(dest_rect.size.width as f32, dest_rect.size.height as f32), ); // TODO(pylbrecht) pass another closure for raqote - self.draw_with_shadow(&rect, writer); + self.draw_with_shadow( + &rect, + shadow_options, + composition_options, + transform, + writer, + ); } else { - writer(&mut self.drawtarget); - } - } - - pub(crate) fn save_context_state(&mut self) { - self.saved_states.push(self.state.clone()); - } - - pub(crate) fn restore_context_state(&mut self) { - if let Some(state) = self.saved_states.pop() { - let _ = mem::replace(&mut self.state, state); - self.drawtarget.set_transform(&self.state.transform); - self.drawtarget.pop_clip(); + writer(&mut self.drawtarget, transform); } } + #[allow(clippy::too_many_arguments)] pub(crate) fn fill_text_with_size( &mut self, text: String, @@ -206,13 +194,17 @@ impl<'a, B: Backend> CanvasData<'a, B> { max_width: Option, is_rtl: bool, size: f64, + style: FillOrStrokeStyle, + text_options: &TextOptions, + composition_options: CompositionOptions, + transform: Transform2D, ) { // > Step 2: Replace all ASCII whitespace in text with U+0020 SPACE characters. let text = replace_ascii_whitespace(text); // > Step 3: Let font be the current font of target, as given by that object's font // > attribute. - let Some(ref font_style) = self.state.font_style else { + let Some(ref font_style) = text_options.font else { return; }; @@ -250,7 +242,18 @@ impl<'a, B: Backend> CanvasData<'a, B> { if let Some(max_width) = max_width { let new_size = (max_width / total_advance * size).floor().max(5.); if total_advance > max_width && new_size != size { - self.fill_text_with_size(text, x, y, Some(max_width), is_rtl, new_size); + self.fill_text_with_size( + text, + x, + y, + Some(max_width), + is_rtl, + new_size, + style, + text_options, + composition_options, + transform, + ); return; } } @@ -262,6 +265,7 @@ impl<'a, B: Backend> CanvasData<'a, B> { &first_font.metrics, total_advance as f32, is_rtl, + text_options, ); // > Step 8: Let result be an array constructed by iterating over each glyph in the inline box @@ -269,19 +273,23 @@ impl<'a, B: Backend> CanvasData<'a, B> { // > as it is in the inline box, positioned on a coordinate space using CSS pixels with its // > origin is at the anchor point. self.maybe_bound_shape_with_pattern( - self.state.fill_style.clone(), + style, + composition_options, &Rect::from_size(Size2D::new(total_advance, size)), - |self_| { + transform, + |self_, style| { self_.drawtarget.fill_text( shaped_runs, start, - &self_.state.fill_style, - &self_.state.draw_options, + style, + composition_options, + transform, ); }, ); } + #[allow(clippy::too_many_arguments)] /// pub(crate) fn fill_text( &mut self, @@ -290,21 +298,37 @@ impl<'a, B: Backend> CanvasData<'a, B> { y: f64, max_width: Option, is_rtl: bool, + style: FillOrStrokeStyle, + text_options: TextOptions, + _shadow_options: ShadowOptions, + composition_options: CompositionOptions, + transform: Transform2D, ) { - let Some(ref font_style) = self.state.font_style else { + let Some(ref font_style) = text_options.font else { return; }; let size = font_style.font_size.computed_size(); - self.fill_text_with_size(text, x, y, max_width, is_rtl, size.px() as f64); + self.fill_text_with_size( + text, + x, + y, + max_width, + is_rtl, + size.px() as f64, + style, + &text_options, + composition_options, + transform, + ); } /// /// - pub(crate) fn measure_text(&mut self, text: String) -> TextMetrics { + pub(crate) fn measure_text(&mut self, text: String, text_options: TextOptions) -> TextMetrics { // > Step 2: Replace all ASCII whitespace in text with U+0020 SPACE characters. let text = replace_ascii_whitespace(text); - let Some(ref font_style) = self.state.font_style else { + let Some(ref font_style) = text_options.font else { return TextMetrics::default(); }; @@ -349,13 +373,13 @@ impl<'a, B: Backend> CanvasData<'a, B> { }, }; - let anchor_x = match self.state.text_align { + let anchor_x = match text_options.align { TextAlign::End => total_advance, TextAlign::Center => total_advance / 2., TextAlign::Right => total_advance, _ => 0., }; - let anchor_y = match self.state.text_baseline { + let anchor_y = match text_options.baseline { TextBaseline::Top => ascent, TextBaseline::Hanging => hanging_baseline, TextBaseline::Ideographic => ideographic_baseline, @@ -425,8 +449,9 @@ impl<'a, B: Backend> CanvasData<'a, B> { metrics: &FontMetrics, width: f32, is_rtl: bool, + text_options: &TextOptions, ) -> Point2D { - let text_align = match self.state.text_align { + let text_align = match text_options.align { TextAlign::Start if is_rtl => TextAlign::Right, TextAlign::Start => TextAlign::Left, TextAlign::End if is_rtl => TextAlign::Left, @@ -441,7 +466,7 @@ impl<'a, B: Backend> CanvasData<'a, B> { let ascent = metrics.ascent.to_f32_px(); let descent = metrics.descent.to_f32_px(); - let anchor_y = match self.state.text_baseline { + let anchor_y = match text_options.baseline { TextBaseline::Top => ascent, TextBaseline::Hanging => ascent * HANGING_BASELINE_DEFAULT, TextBaseline::Ideographic => -descent * IDEOGRAPHIC_BASELINE_DEFAULT, @@ -453,152 +478,148 @@ impl<'a, B: Backend> CanvasData<'a, B> { point2(x + anchor_x, y + anchor_y) } - pub(crate) fn fill_rect(&mut self, rect: &Rect) { - if self.state.fill_style.is_zero_size_gradient() { + pub(crate) fn fill_rect( + &mut self, + rect: &Rect, + style: FillOrStrokeStyle, + shadow_options: ShadowOptions, + composition_options: CompositionOptions, + transform: Transform2D, + ) { + if style.is_zero_size_gradient() { return; // Paint nothing if gradient size is zero. } - if self.need_to_draw_shadow() { - self.draw_with_shadow(rect, |new_draw_target: &mut B::DrawTarget| { - new_draw_target.fill_rect(rect, &self.state.fill_style, &self.state.draw_options); - }); + if shadow_options.need_to_draw_shadow() { + self.draw_with_shadow( + rect, + shadow_options, + composition_options, + transform, + |new_draw_target: &mut B::DrawTarget, transform| { + new_draw_target.fill_rect(rect, style, composition_options, transform); + }, + ); } else { self.maybe_bound_shape_with_pattern( - self.state.fill_style.clone(), + style, + composition_options, &rect.cast(), - |self_| { - self_.drawtarget.fill_rect( - rect, - &self_.state.fill_style, - &self_.state.draw_options, - ); + transform, + |self_, style| { + self_ + .drawtarget + .fill_rect(rect, style, composition_options, transform); }, ); } } - pub(crate) fn clear_rect(&mut self, rect: &Rect) { - self.drawtarget.clear_rect(rect); + pub(crate) fn clear_rect(&mut self, rect: &Rect, transform: Transform2D) { + self.drawtarget.clear_rect(rect, transform); } - pub(crate) fn stroke_rect(&mut self, rect: &Rect) { - if self.state.stroke_style.is_zero_size_gradient() { + pub(crate) fn stroke_rect( + &mut self, + rect: &Rect, + style: FillOrStrokeStyle, + line_options: LineOptions, + shadow_options: ShadowOptions, + composition_options: CompositionOptions, + transform: Transform2D, + ) { + if style.is_zero_size_gradient() { return; // Paint nothing if gradient size is zero. } - if self.need_to_draw_shadow() { - self.draw_with_shadow(rect, |new_draw_target: &mut B::DrawTarget| { - new_draw_target.stroke_rect( - rect, - &self.state.stroke_style, - &self.state.stroke_opts, - &self.state.draw_options, - ); - }); + if shadow_options.need_to_draw_shadow() { + self.draw_with_shadow( + rect, + shadow_options, + composition_options, + transform, + |new_draw_target: &mut B::DrawTarget, transform| { + new_draw_target.stroke_rect( + rect, + style, + line_options, + composition_options, + transform, + ); + }, + ); } else { self.maybe_bound_shape_with_pattern( - self.state.stroke_style.clone(), + style, + composition_options, &rect.cast(), - |self_| { + transform, + |self_, style| { self_.drawtarget.stroke_rect( rect, - &self_.state.stroke_style, - &self_.state.stroke_opts, - &self_.state.draw_options, + style, + line_options, + composition_options, + transform, ); }, ) } } - pub(crate) fn fill_path(&mut self, path: &Path) { - if self.state.fill_style.is_zero_size_gradient() { + pub(crate) fn fill_path( + &mut self, + path: &Path, + style: FillOrStrokeStyle, + _shadow_options: ShadowOptions, + composition_options: CompositionOptions, + transform: Transform2D, + ) { + if style.is_zero_size_gradient() { return; // Paint nothing if gradient size is zero. } self.maybe_bound_shape_with_pattern( - self.state.fill_style.clone(), + style, + composition_options, &path.bounding_box(), - |self_| { + transform, + |self_, style| { self_ .drawtarget - .fill(path, &self_.state.fill_style, &self_.state.draw_options) + .fill(path, style, composition_options, transform) }, ) } - pub(crate) fn stroke_path(&mut self, path: &Path) { - if self.state.stroke_style.is_zero_size_gradient() { + pub(crate) fn stroke_path( + &mut self, + path: &Path, + style: FillOrStrokeStyle, + line_options: LineOptions, + _shadow_options: ShadowOptions, + composition_options: CompositionOptions, + transform: Transform2D, + ) { + if style.is_zero_size_gradient() { return; // Paint nothing if gradient size is zero. } self.maybe_bound_shape_with_pattern( - self.state.stroke_style.clone(), + style, + composition_options, &path.bounding_box(), - |self_| { - self_.drawtarget.stroke( - path, - &self_.state.stroke_style, - &self_.state.stroke_opts, - &self_.state.draw_options, - ); + transform, + |self_, style| { + self_ + .drawtarget + .stroke(path, style, line_options, composition_options, transform); }, ) } - pub(crate) fn clip_path(&mut self, path: &Path) { - self.drawtarget.push_clip(path); - } - - pub(crate) fn set_fill_style(&mut self, style: FillOrStrokeStyle) { - self.backend - .set_fill_style(style, &mut self.state, &self.drawtarget); - } - - pub(crate) fn set_stroke_style(&mut self, style: FillOrStrokeStyle) { - self.backend - .set_stroke_style(style, &mut self.state, &self.drawtarget); - } - - pub(crate) fn set_line_width(&mut self, width: f32) { - self.state.stroke_opts.set_line_width(width); - } - - pub(crate) fn set_line_cap(&mut self, cap: LineCapStyle) { - self.state.stroke_opts.set_line_cap(cap); - } - - pub(crate) fn set_line_join(&mut self, join: LineJoinStyle) { - self.state.stroke_opts.set_line_join(join); - } - - pub(crate) fn set_miter_limit(&mut self, limit: f32) { - self.state.stroke_opts.set_miter_limit(limit); - } - - pub(crate) fn set_line_dash(&mut self, items: Vec) { - self.state.stroke_opts.set_line_dash(items); - } - - pub(crate) fn set_line_dash_offset(&mut self, offset: f32) { - self.state.stroke_opts.set_line_dash_offset(offset); - } - - pub(crate) fn get_transform(&self) -> Transform2D { - self.drawtarget.get_transform() - } - - pub(crate) fn set_transform(&mut self, transform: &Transform2D) { - self.state.transform = *transform; - self.drawtarget.set_transform(transform); - } - - pub(crate) fn set_global_alpha(&mut self, alpha: f32) { - self.state.draw_options.set_alpha(alpha); - } - - pub(crate) fn set_global_composition(&mut self, op: CompositionOrBlending) { - self.backend.set_global_composition(op, &mut self.state); + pub(crate) fn clip_path(&mut self, path: &Path, transform: Transform2D) { + self.drawtarget.push_clip(path, transform); } /// @@ -612,13 +633,6 @@ impl<'a, B: Backend> CanvasData<'a, B> { .backend .create_drawtarget(Size2D::new(size.width, size.height)); - // Step 3. Clear the context's drawing state stack. - self.saved_states.clear(); - - // Step 4. Reset everything that drawing state consists of to their - // initial values. - self.state = self.backend.new_paint_state(); - self.update_image_rendering(); } @@ -644,71 +658,35 @@ impl<'a, B: Backend> CanvasData<'a, B> { ); } - pub(crate) fn set_shadow_offset_x(&mut self, value: f64) { - self.state.shadow_offset_x = value; - } - - pub(crate) fn set_shadow_offset_y(&mut self, value: f64) { - self.state.shadow_offset_y = value; - } - - pub(crate) fn set_shadow_blur(&mut self, value: f64) { - self.state.shadow_blur = value; - } - - pub(crate) fn set_shadow_color(&mut self, value: AbsoluteColor) { - self.backend.set_shadow_color(value, &mut self.state); - } - - pub(crate) fn set_font(&mut self, font_style: FontStyleStruct) { - self.state.font_style = Some(ServoArc::new(font_style)) - } - - pub(crate) fn set_text_align(&mut self, text_align: TextAlign) { - self.state.text_align = text_align; - } - - pub(crate) fn set_text_baseline(&mut self, text_baseline: TextBaseline) { - self.state.text_baseline = text_baseline; - } - - // https://html.spec.whatwg.org/multipage/#when-shadows-are-drawn - fn need_to_draw_shadow(&self) -> bool { - self.backend.need_to_draw_shadow(&self.state.shadow_color) && - (self.state.shadow_offset_x != 0.0f64 || - self.state.shadow_offset_y != 0.0f64 || - self.state.shadow_blur != 0.0f64) - } - fn create_draw_target_for_shadow(&self, source_rect: &Rect) -> B::DrawTarget { - let mut draw_target = self.drawtarget.create_similar_draw_target(&Size2D::new( + self.drawtarget.create_similar_draw_target(&Size2D::new( source_rect.size.width as i32, source_rect.size.height as i32, - )); - let matrix = self.state.transform.then( - &Transform2D::identity().pre_translate(-source_rect.origin.to_vector().cast::()), - ); - draw_target.set_transform(&matrix); - draw_target + )) } - fn draw_with_shadow(&self, rect: &Rect, draw_shadow_source: F) - where - F: FnOnce(&mut B::DrawTarget), + fn draw_with_shadow( + &self, + rect: &Rect, + shadow_options: ShadowOptions, + composition_options: CompositionOptions, + transform: Transform2D, + draw_shadow_source: F, + ) where + F: FnOnce(&mut B::DrawTarget, Transform2D), { - let shadow_src_rect = self.state.transform.outer_transformed_rect(rect); + let shadow_src_rect = transform.outer_transformed_rect(rect); let mut new_draw_target = self.create_draw_target_for_shadow(&shadow_src_rect); - draw_shadow_source(&mut new_draw_target); + let shadow_transform = transform.then( + &Transform2D::identity() + .pre_translate(-shadow_src_rect.origin.to_vector().cast::()), + ); + draw_shadow_source(&mut new_draw_target, shadow_transform); self.drawtarget.draw_surface_with_shadow( new_draw_target.surface(), &Point2D::new(shadow_src_rect.origin.x, shadow_src_rect.origin.y), - &self.state.shadow_color, - &Vector2D::new( - self.state.shadow_offset_x as f32, - self.state.shadow_offset_y as f32, - ), - (self.state.shadow_blur / 2.0f64) as f32, - self.backend.get_composition_op(&self.state.draw_options), + shadow_options, + composition_options, ); } @@ -716,17 +694,23 @@ impl<'a, B: Backend> CanvasData<'a, B> { /// of the given pattern. fn maybe_bound_shape_with_pattern( &mut self, - pattern: B::Pattern<'_>, + style: FillOrStrokeStyle, + composition_options: CompositionOptions, path_bound_box: &Rect, + transform: Transform2D, draw_shape: F, ) where - F: FnOnce(&mut Self), + F: FnOnce(&mut Self, FillOrStrokeStyle), { - let x_bound = pattern.x_bound(); - let y_bound = pattern.y_bound(); + let x_bound = style.x_bound(); + let y_bound = style.y_bound(); // Clear operations are also unbounded. - if self.state.draw_options.is_clear() || (x_bound.is_none() && y_bound.is_none()) { - draw_shape(self); + if matches!( + composition_options.composition_operation, + CompositionOrBlending::Composition(CompositionStyle::Clear) + ) || (x_bound.is_none() && y_bound.is_none()) + { + draw_shape(self, style); return; } let rect = Rect::from_size(Size2D::new( @@ -734,9 +718,9 @@ impl<'a, B: Backend> CanvasData<'a, B> { y_bound.unwrap_or(path_bound_box.size.height.ceil() as u32), )) .cast(); - let rect = self.get_transform().outer_transformed_rect(&rect); + let rect = transform.outer_transformed_rect(&rect); self.drawtarget.push_clip_rect(&rect.cast()); - draw_shape(self); + draw_shape(self, style); self.drawtarget.pop_clip(); } @@ -765,9 +749,13 @@ impl<'a, B: Backend> CanvasData<'a, B> { self.drawtarget.snapshot() } } + + pub(crate) fn pop_clip(&mut self) { + self.drawtarget.pop_clip(); + } } -impl Drop for CanvasData<'_, B> { +impl Drop for CanvasData { fn drop(&mut self) { self.compositor_api.delete_image(self.image_key); } @@ -776,24 +764,6 @@ impl Drop for CanvasData<'_, B> { const HANGING_BASELINE_DEFAULT: f32 = 0.8; const IDEOGRAPHIC_BASELINE_DEFAULT: f32 = 0.5; -#[derive(Clone)] -pub(crate) struct CanvasPaintState<'a, B: Backend> { - pub(crate) draw_options: B::DrawOptions, - pub(crate) fill_style: B::Pattern<'a>, - pub(crate) stroke_style: B::Pattern<'a>, - pub(crate) stroke_opts: B::StrokeOptions, - /// The current 2D transform matrix. - pub(crate) transform: Transform2D, - pub(crate) shadow_offset_x: f64, - pub(crate) shadow_offset_y: f64, - pub(crate) shadow_blur: f64, - pub(crate) shadow_color: B::Color, - pub(crate) font_style: Option>, - pub(crate) text_align: TextAlign, - pub(crate) text_baseline: TextBaseline, - pub(crate) _backend: PhantomData, -} - /// It writes an image to the destination target /// draw_target: the destination target where the image_data will be copied /// image_data: Pixel information of the image to be written. It takes RGBA8 @@ -806,7 +776,8 @@ fn write_image( snapshot: Snapshot, dest_rect: Rect, smoothing_enabled: bool, - draw_options: &B::DrawOptions, + composition_options: CompositionOptions, + transform: Transform2D, ) { if snapshot.size().is_empty() { return; @@ -828,7 +799,14 @@ fn write_image( .create_source_surface_from_data(snapshot) .unwrap(); - draw_target.draw_surface(source_surface, dest_rect, image_rect, filter, draw_options); + draw_target.draw_surface( + source_surface, + dest_rect, + image_rect, + filter, + composition_options, + transform, + ); } pub(crate) trait RectToi32 { diff --git a/components/canvas/canvas_paint_thread.rs b/components/canvas/canvas_paint_thread.rs index 633e09b5212..2c933ecc82b 100644 --- a/components/canvas/canvas_paint_thread.rs +++ b/components/canvas/canvas_paint_thread.rs @@ -2,6 +2,8 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ +#![allow(clippy::too_many_arguments)] + use std::borrow::ToOwned; use std::collections::HashMap; use std::sync::Arc; @@ -18,26 +20,24 @@ use ipc_channel::router::ROUTER; use log::warn; use net_traits::ResourceThreads; use pixels::Snapshot; -use style::color::AbsoluteColor; -use style::properties::style_structs::Font as FontStyleStruct; use webrender_api::ImageKey; use crate::canvas_data::*; use crate::raqote_backend::RaqoteBackend; -pub struct CanvasPaintThread<'a> { - canvases: HashMap>, +pub struct CanvasPaintThread { + canvases: HashMap, next_canvas_id: CanvasId, compositor_api: CrossProcessCompositorApi, font_context: Arc, } -impl<'a> CanvasPaintThread<'a> { +impl CanvasPaintThread { fn new( compositor_api: CrossProcessCompositorApi, system_font_service: Arc, resource_threads: ResourceThreads, - ) -> CanvasPaintThread<'a> { + ) -> CanvasPaintThread { CanvasPaintThread { canvases: HashMap::new(), next_canvas_id: CanvasId(0), @@ -146,21 +146,27 @@ impl<'a> CanvasPaintThread<'a> { composition_options, transform, ) => { - let canvas = self.canvas(canvas_id); - canvas.set_transform(&transform); - canvas.set_fill_style(style); - canvas.set_text_options(text_options); - canvas.set_shadow_options(shadow_options); - canvas.set_composition_options(composition_options); - canvas.fill_text(text, x, y, max_width, is_rtl); + self.canvas(canvas_id).fill_text( + text, + x, + y, + max_width, + is_rtl, + style, + text_options, + shadow_options, + composition_options, + transform, + ); }, Canvas2dMsg::FillRect(rect, style, shadow_options, composition_options, transform) => { - let canvas = self.canvas(canvas_id); - canvas.set_transform(&transform); - canvas.set_fill_style(style); - canvas.set_shadow_options(shadow_options); - canvas.set_composition_options(composition_options); - canvas.fill_rect(&rect); + self.canvas(canvas_id).fill_rect( + &rect, + style, + shadow_options, + composition_options, + transform, + ); }, Canvas2dMsg::StrokeRect( rect, @@ -170,25 +176,26 @@ impl<'a> CanvasPaintThread<'a> { composition_options, transform, ) => { - let canvas = self.canvas(canvas_id); - canvas.set_transform(&transform); - canvas.set_stroke_style(style); - canvas.set_line_options(line_options); - canvas.set_shadow_options(shadow_options); - canvas.set_composition_options(composition_options); - canvas.stroke_rect(&rect); + self.canvas(canvas_id).stroke_rect( + &rect, + style, + line_options, + shadow_options, + composition_options, + transform, + ); }, Canvas2dMsg::ClearRect(ref rect, transform) => { - self.canvas(canvas_id).set_transform(&transform); - self.canvas(canvas_id).clear_rect(rect) + self.canvas(canvas_id).clear_rect(rect, transform) }, Canvas2dMsg::FillPath(style, path, shadow_options, composition_options, transform) => { - let canvas = self.canvas(canvas_id); - canvas.set_transform(&transform); - canvas.set_fill_style(style); - canvas.set_shadow_options(shadow_options); - canvas.set_composition_options(composition_options); - canvas.fill_path(&path); + self.canvas(canvas_id).fill_path( + &path, + style, + shadow_options, + composition_options, + transform, + ); }, Canvas2dMsg::StrokePath( path, @@ -198,18 +205,17 @@ impl<'a> CanvasPaintThread<'a> { composition_options, transform, ) => { - let canvas = self.canvas(canvas_id); - canvas.set_transform(&transform); - canvas.set_stroke_style(style); - canvas.set_line_options(line_options); - canvas.set_shadow_options(shadow_options); - canvas.set_composition_options(composition_options); - canvas.stroke_path(&path); + self.canvas(canvas_id).stroke_path( + &path, + style, + line_options, + shadow_options, + composition_options, + transform, + ); }, Canvas2dMsg::ClipPath(path, transform) => { - let canvas = self.canvas(canvas_id); - canvas.set_transform(&transform); - canvas.clip_path(&path); + self.canvas(canvas_id).clip_path(&path, transform); }, Canvas2dMsg::DrawImage( snapshot, @@ -219,18 +225,15 @@ impl<'a> CanvasPaintThread<'a> { shadow_options, composition_options, transform, - ) => { - let canvas = self.canvas(canvas_id); - canvas.set_transform(&transform); - canvas.set_shadow_options(shadow_options); - canvas.set_composition_options(composition_options); - canvas.draw_image( - snapshot.to_owned(), - dest_rect, - source_rect, - smoothing_enabled, - ) - }, + ) => self.canvas(canvas_id).draw_image( + snapshot.to_owned(), + dest_rect, + source_rect, + smoothing_enabled, + shadow_options, + composition_options, + transform, + ), Canvas2dMsg::DrawEmptyImage( image_size, dest_rect, @@ -238,18 +241,15 @@ impl<'a> CanvasPaintThread<'a> { shadow_options, composition_options, transform, - ) => { - let canvas = self.canvas(canvas_id); - canvas.set_transform(&transform); - canvas.set_shadow_options(shadow_options); - canvas.set_composition_options(composition_options); - self.canvas(canvas_id).draw_image( - Snapshot::cleared(image_size), - dest_rect, - source_rect, - false, - ) - }, + ) => self.canvas(canvas_id).draw_image( + Snapshot::cleared(image_size), + dest_rect, + source_rect, + false, + shadow_options, + composition_options, + transform, + ), Canvas2dMsg::DrawImageInOther( other_canvas_id, image_size, @@ -263,20 +263,20 @@ impl<'a> CanvasPaintThread<'a> { let snapshot = self .canvas(canvas_id) .read_pixels(Some(source_rect.to_u32()), Some(image_size)); - let canvas = self.canvas(other_canvas_id); - canvas.set_transform(&transform); - canvas.set_composition_options(composition_options); - canvas.set_shadow_options(shadow_options); - canvas.draw_image(snapshot, dest_rect, source_rect, smoothing); + self.canvas(other_canvas_id).draw_image( + snapshot, + dest_rect, + source_rect, + smoothing, + shadow_options, + composition_options, + transform, + ); }, Canvas2dMsg::MeasureText(text, sender, text_options) => { - let canvas = self.canvas(canvas_id); - canvas.set_text_options(text_options); - let metrics = canvas.measure_text(text); + let metrics = self.canvas(canvas_id).measure_text(text, text_options); sender.send(metrics).unwrap(); }, - Canvas2dMsg::RestoreContext => self.canvas(canvas_id).restore_context_state(), - Canvas2dMsg::SaveContext => self.canvas(canvas_id).save_context_state(), Canvas2dMsg::GetImageData(dest_rect, canvas_size, sender) => { let snapshot = self .canvas(canvas_id) @@ -291,64 +291,130 @@ impl<'a> CanvasPaintThread<'a> { self.canvas(canvas_id).update_image_rendering(); sender.send(()).unwrap(); }, + Canvas2dMsg::PopClip => self.canvas(canvas_id).pop_clip(), } } - fn canvas(&mut self, canvas_id: CanvasId) -> &mut Canvas<'a> { + fn canvas(&mut self, canvas_id: CanvasId) -> &mut Canvas { self.canvases.get_mut(&canvas_id).expect("Bogus canvas id") } } -enum Canvas<'a> { - Raqote(CanvasData<'a, RaqoteBackend>), +enum Canvas { + Raqote(CanvasData), } -impl Canvas<'_> { - fn set_fill_style(&mut self, style: FillOrStrokeStyle) { +impl Canvas { + fn pop_clip(&mut self) { match self { - Canvas::Raqote(canvas_data) => canvas_data.set_fill_style(style), + Canvas::Raqote(canvas_data) => canvas_data.pop_clip(), } } - fn fill_text(&mut self, text: String, x: f64, y: f64, max_width: Option, is_rtl: bool) { + fn fill_text( + &mut self, + text: String, + x: f64, + y: f64, + max_width: Option, + is_rtl: bool, + style: FillOrStrokeStyle, + text_options: TextOptions, + shadow_options: ShadowOptions, + composition_options: CompositionOptions, + transform: Transform2D, + ) { match self { - Canvas::Raqote(canvas_data) => canvas_data.fill_text(text, x, y, max_width, is_rtl), + Canvas::Raqote(canvas_data) => canvas_data.fill_text( + text, + x, + y, + max_width, + is_rtl, + style, + text_options, + shadow_options, + composition_options, + transform, + ), } } - fn fill_rect(&mut self, rect: &Rect) { + fn fill_rect( + &mut self, + rect: &Rect, + style: FillOrStrokeStyle, + shadow_options: ShadowOptions, + composition_options: CompositionOptions, + transform: Transform2D, + ) { match self { - Canvas::Raqote(canvas_data) => canvas_data.fill_rect(rect), + Canvas::Raqote(canvas_data) => { + canvas_data.fill_rect(rect, style, shadow_options, composition_options, transform) + }, } } - fn set_stroke_style(&mut self, style: FillOrStrokeStyle) { + fn stroke_rect( + &mut self, + rect: &Rect, + style: FillOrStrokeStyle, + line_options: LineOptions, + shadow_options: ShadowOptions, + composition_options: CompositionOptions, + transform: Transform2D, + ) { match self { - Canvas::Raqote(canvas_data) => canvas_data.set_stroke_style(style), + Canvas::Raqote(canvas_data) => canvas_data.stroke_rect( + rect, + style, + line_options, + shadow_options, + composition_options, + transform, + ), } } - fn stroke_rect(&mut self, rect: &Rect) { + fn fill_path( + &mut self, + path: &Path, + style: FillOrStrokeStyle, + shadow_options: ShadowOptions, + composition_options: CompositionOptions, + transform: Transform2D, + ) { match self { - Canvas::Raqote(canvas_data) => canvas_data.stroke_rect(rect), + Canvas::Raqote(canvas_data) => { + canvas_data.fill_path(path, style, shadow_options, composition_options, transform) + }, } } - fn fill_path(&mut self, path: &Path) { + fn stroke_path( + &mut self, + path: &Path, + style: FillOrStrokeStyle, + line_options: LineOptions, + shadow_options: ShadowOptions, + composition_options: CompositionOptions, + transform: Transform2D, + ) { match self { - Canvas::Raqote(canvas_data) => canvas_data.fill_path(path), + Canvas::Raqote(canvas_data) => canvas_data.stroke_path( + path, + style, + line_options, + shadow_options, + composition_options, + transform, + ), } } - fn stroke_path(&mut self, path: &Path) { + fn clear_rect(&mut self, rect: &Rect, transform: Transform2D) { match self { - Canvas::Raqote(canvas_data) => canvas_data.stroke_path(path), - } - } - - fn clear_rect(&mut self, rect: &Rect) { - match self { - Canvas::Raqote(canvas_data) => canvas_data.clear_rect(rect), + Canvas::Raqote(canvas_data) => canvas_data.clear_rect(rect, transform), } } @@ -358,11 +424,20 @@ impl Canvas<'_> { dest_rect: Rect, source_rect: Rect, smoothing_enabled: bool, + shadow_options: ShadowOptions, + composition_options: CompositionOptions, + transform: Transform2D, ) { match self { - Canvas::Raqote(canvas_data) => { - canvas_data.draw_image(snapshot, dest_rect, source_rect, smoothing_enabled) - }, + Canvas::Raqote(canvas_data) => canvas_data.draw_image( + snapshot, + dest_rect, + source_rect, + smoothing_enabled, + shadow_options, + composition_options, + transform, + ), } } @@ -376,123 +451,15 @@ impl Canvas<'_> { } } - fn restore_context_state(&mut self) { + fn measure_text(&mut self, text: String, text_options: TextOptions) -> TextMetrics { match self { - Canvas::Raqote(canvas_data) => canvas_data.restore_context_state(), + Canvas::Raqote(canvas_data) => canvas_data.measure_text(text, text_options), } } - fn save_context_state(&mut self) { + fn clip_path(&mut self, path: &Path, transform: Transform2D) { match self { - Canvas::Raqote(canvas_data) => canvas_data.save_context_state(), - } - } - - fn set_line_width(&mut self, width: f32) { - match self { - Canvas::Raqote(canvas_data) => canvas_data.set_line_width(width), - } - } - - fn set_line_cap(&mut self, cap: LineCapStyle) { - match self { - Canvas::Raqote(canvas_data) => canvas_data.set_line_cap(cap), - } - } - - fn set_line_join(&mut self, join: LineJoinStyle) { - match self { - Canvas::Raqote(canvas_data) => canvas_data.set_line_join(join), - } - } - - fn set_miter_limit(&mut self, limit: f32) { - match self { - Canvas::Raqote(canvas_data) => canvas_data.set_miter_limit(limit), - } - } - - fn set_line_dash(&mut self, items: Vec) { - match self { - Canvas::Raqote(canvas_data) => canvas_data.set_line_dash(items), - } - } - - fn set_line_dash_offset(&mut self, offset: f32) { - match self { - Canvas::Raqote(canvas_data) => canvas_data.set_line_dash_offset(offset), - } - } - - fn set_transform(&mut self, matrix: &Transform2D) { - match self { - Canvas::Raqote(canvas_data) => canvas_data.set_transform(matrix), - } - } - - fn set_global_alpha(&mut self, alpha: f32) { - match self { - Canvas::Raqote(canvas_data) => canvas_data.set_global_alpha(alpha), - } - } - - fn set_global_composition(&mut self, op: CompositionOrBlending) { - match self { - Canvas::Raqote(canvas_data) => canvas_data.set_global_composition(op), - } - } - - fn set_shadow_offset_x(&mut self, value: f64) { - match self { - Canvas::Raqote(canvas_data) => canvas_data.set_shadow_offset_x(value), - } - } - - fn set_shadow_offset_y(&mut self, value: f64) { - match self { - Canvas::Raqote(canvas_data) => canvas_data.set_shadow_offset_y(value), - } - } - - fn set_shadow_blur(&mut self, value: f64) { - match self { - Canvas::Raqote(canvas_data) => canvas_data.set_shadow_blur(value), - } - } - - fn set_shadow_color(&mut self, color: AbsoluteColor) { - match self { - Canvas::Raqote(canvas_data) => canvas_data.set_shadow_color(color), - } - } - - fn set_font(&mut self, font_style: FontStyleStruct) { - match self { - Canvas::Raqote(canvas_data) => canvas_data.set_font(font_style), - } - } - - fn set_text_align(&mut self, text_align: TextAlign) { - match self { - Canvas::Raqote(canvas_data) => canvas_data.set_text_align(text_align), - } - } - - fn set_text_baseline(&mut self, text_baseline: TextBaseline) { - match self { - Canvas::Raqote(canvas_data) => canvas_data.set_text_baseline(text_baseline), - } - } - - fn measure_text(&mut self, text: String) -> TextMetrics { - match self { - Canvas::Raqote(canvas_data) => canvas_data.measure_text(text), - } - } - - fn clip_path(&mut self, path: &Path) { - match self { - Canvas::Raqote(canvas_data) => canvas_data.clip_path(path), + Canvas::Raqote(canvas_data) => canvas_data.clip_path(path, transform), } } @@ -513,41 +480,4 @@ impl Canvas<'_> { Canvas::Raqote(canvas_data) => canvas_data.recreate(size), } } - - fn set_text_options(&mut self, text_options: TextOptions) { - if let Some(font) = text_options.font { - self.set_font(font); - } - self.set_text_align(text_options.align); - self.set_text_baseline(text_options.baseline); - } - - fn set_shadow_options(&mut self, shadow_options: ShadowOptions) { - self.set_shadow_color(shadow_options.color); - self.set_shadow_offset_x(shadow_options.offset_x); - self.set_shadow_offset_y(shadow_options.offset_y); - self.set_shadow_blur(shadow_options.blur); - } - - fn set_composition_options(&mut self, composition_options: CompositionOptions) { - self.set_global_alpha(composition_options.alpha as f32); - self.set_global_composition(composition_options.composition_operation); - } - - fn set_line_options(&mut self, line_options: LineOptions) { - let LineOptions { - width, - cap_style, - join_style, - miter_limit, - dash, - dash_offset, - } = line_options; - self.set_line_width(width as f32); - self.set_line_cap(cap_style); - self.set_line_join(join_style); - self.set_miter_limit(miter_limit as f32); - self.set_line_dash(dash); - self.set_line_dash_offset(dash_offset as f32); - } } diff --git a/components/canvas/raqote_backend.rs b/components/canvas/raqote_backend.rs index f6d72183b3d..35e5f702cd1 100644 --- a/components/canvas/raqote_backend.rs +++ b/components/canvas/raqote_backend.rs @@ -8,21 +8,19 @@ use std::collections::HashMap; use canvas_traits::canvas::*; use compositing_traits::SerializableImageData; use cssparser::color::clamp_unit_f32; -use euclid::default::{Point2D, Rect, Size2D, Transform2D, Vector2D}; +use euclid::default::{Point2D, Rect, Size2D, Transform2D}; use font_kit::font::Font; use fonts::{ByteIndex, FontIdentifier, FontTemplateRefMethods}; use ipc_channel::ipc::IpcSharedMemory; use log::warn; use pixels::{Snapshot, SnapshotAlphaMode, SnapshotPixelFormat}; use range::Range; -use raqote::PathBuilder; +use raqote::{DrawOptions, PathBuilder, StrokeStyle}; use style::color::AbsoluteColor; use webrender_api::{ImageDescriptor, ImageDescriptorFlags, ImageFormat}; -use crate::backend::{ - Backend, DrawOptionsHelpers, GenericDrawTarget, PatternHelpers, StrokeOptionsHelpers, -}; -use crate::canvas_data::{CanvasPaintState, Filter, TextRun}; +use crate::backend::{Backend, GenericDrawTarget}; +use crate::canvas_data::{Filter, TextRun}; thread_local! { /// The shared font cache used by all canvases that render on a thread. It would be nicer @@ -36,80 +34,12 @@ thread_local! { pub(crate) struct RaqoteBackend; impl Backend for RaqoteBackend { - type Pattern<'a> = Pattern; - type StrokeOptions = raqote::StrokeStyle; - type Color = raqote::SolidSource; - type DrawOptions = raqote::DrawOptions; - type CompositionOp = raqote::BlendMode; type DrawTarget = raqote::DrawTarget; type SourceSurface = Vec; // TODO: See if we can avoid the alloc (probably?) - type GradientStop = raqote::GradientStop; - type GradientStops = Vec; - - fn get_composition_op(&self, opts: &Self::DrawOptions) -> Self::CompositionOp { - opts.blend_mode - } - - fn need_to_draw_shadow(&self, color: &Self::Color) -> bool { - color.a != 0 - } - - fn set_shadow_color(&mut self, color: AbsoluteColor, state: &mut CanvasPaintState<'_, Self>) { - state.shadow_color = color.to_raqote_style(); - } - - fn set_fill_style( - &mut self, - style: FillOrStrokeStyle, - state: &mut CanvasPaintState<'_, Self>, - _drawtarget: &Self::DrawTarget, - ) { - if let Some(pattern) = style.to_raqote_pattern() { - state.fill_style = pattern; - } - } - - fn set_stroke_style( - &mut self, - style: FillOrStrokeStyle, - state: &mut CanvasPaintState<'_, Self>, - _drawtarget: &Self::DrawTarget, - ) { - if let Some(pattern) = style.to_raqote_pattern() { - state.stroke_style = pattern; - } - } - - fn set_global_composition( - &mut self, - op: CompositionOrBlending, - state: &mut CanvasPaintState<'_, Self>, - ) { - state.draw_options.blend_mode = op.to_raqote_style(); - } fn create_drawtarget(&self, size: Size2D) -> Self::DrawTarget { raqote::DrawTarget::new(size.width as i32, size.height as i32) } - - fn new_paint_state<'a>(&self) -> CanvasPaintState<'a, Self> { - let pattern = Pattern::Color(255, 0, 0, 0); - CanvasPaintState { - draw_options: raqote::DrawOptions::new(), - fill_style: pattern.clone(), - stroke_style: pattern, - stroke_opts: Default::default(), - transform: Transform2D::identity(), - shadow_offset_x: 0.0, - shadow_offset_y: 0.0, - shadow_blur: 0.0, - shadow_color: raqote::SolidSource::from_unpremultiplied_argb(0, 0, 0, 0), - font_style: None, - text_align: TextAlign::default(), - text_baseline: TextBaseline::default(), - _backend: std::marker::PhantomData, - } - } } #[derive(Clone)] @@ -170,7 +100,6 @@ pub struct SurfacePattern { image: Snapshot, filter: raqote::FilterMode, extend: raqote::ExtendMode, - repeat: Repetition, transform: Transform2D, } @@ -191,14 +120,9 @@ impl SurfacePattern { image, filter, extend, - repeat, transform, } } - - pub fn repetition(&self) -> &Repetition { - &self.repeat - } } #[derive(Clone, Copy, Debug)] @@ -265,73 +189,6 @@ pub fn source(pattern: &Pattern) -> raqote::Source { } } -impl PatternHelpers for Pattern { - fn is_zero_size_gradient(&self) -> bool { - match self { - Pattern::RadialGradient(pattern) => { - let centers_equal = pattern.center1 == pattern.center2; - let radii_equal = pattern.radius1 == pattern.radius2; - (centers_equal && radii_equal) || pattern.gradient.stops.is_empty() - }, - Pattern::LinearGradient(pattern) => { - (pattern.start == pattern.end) || pattern.gradient.stops.is_empty() - }, - Pattern::Color(..) | Pattern::Surface(..) => false, - } - } - - fn x_bound(&self) -> Option { - match self { - Pattern::Surface(pattern) => match pattern.repetition() { - Repetition::RepeatX | Repetition::Repeat => None, // x is not bounded - Repetition::RepeatY | Repetition::NoRepeat => Some(pattern.image.size().width), - }, - Pattern::Color(..) | Pattern::LinearGradient(..) | Pattern::RadialGradient(..) => None, - } - } - - fn y_bound(&self) -> Option { - match self { - Pattern::Surface(pattern) => match pattern.repetition() { - Repetition::RepeatY | Repetition::Repeat => None, // y is not bounded - Repetition::RepeatX | Repetition::NoRepeat => Some(pattern.image.size().height), - }, - Pattern::Color(..) | Pattern::LinearGradient(..) | Pattern::RadialGradient(..) => None, - } - } -} - -impl StrokeOptionsHelpers for raqote::StrokeStyle { - fn set_line_width(&mut self, _val: f32) { - self.width = _val; - } - fn set_miter_limit(&mut self, _val: f32) { - self.miter_limit = _val; - } - fn set_line_join(&mut self, val: LineJoinStyle) { - self.join = val.to_raqote_style(); - } - fn set_line_cap(&mut self, val: LineCapStyle) { - self.cap = val.to_raqote_style(); - } - fn set_line_dash(&mut self, items: Vec) { - self.dash_array = items; - } - fn set_line_dash_offset(&mut self, offset: f32) { - self.dash_offset = offset; - } -} - -impl DrawOptionsHelpers for raqote::DrawOptions { - fn set_alpha(&mut self, val: f32) { - self.alpha = val; - } - - fn is_clear(&self) -> bool { - matches!(self.blend_mode, raqote::BlendMode::Clear) - } -} - fn create_gradient_stops(gradient_stops: Vec) -> Vec { let mut stops = gradient_stops .into_iter() @@ -343,19 +200,17 @@ fn create_gradient_stops(gradient_stops: Vec) -> Vec for raqote::DrawTarget { - fn clear_rect(&mut self, rect: &Rect) { - let rect = rect.cast(); - let mut pb = canvas_traits::canvas::Path::new(); - pb.rect( - rect.origin.x, - rect.origin.y, - rect.size.width, - rect.size.height, + fn clear_rect(&mut self, rect: &Rect, transform: Transform2D) { + >::fill_rect( + self, + rect, + FillOrStrokeStyle::Color(AbsoluteColor::TRANSPARENT_BLACK), + CompositionOptions { + alpha: 1.0, + composition_operation: CompositionOrBlending::Composition(CompositionStyle::Clear), + }, + transform, ); - let mut options = raqote::DrawOptions::new(); - options.blend_mode = raqote::BlendMode::Clear; - let pattern = Pattern::Color(0, 0, 0, 0); - >::fill(self, &pb, &pattern, &options); } #[allow(unsafe_code)] fn copy_surface( @@ -396,12 +251,13 @@ impl GenericDrawTarget for raqote::DrawTarget { &mut self, surface: ::SourceSurface, dest: Rect, - source: Rect, + src: Rect, filter: Filter, - draw_options: &::DrawOptions, + composition_options: CompositionOptions, + transform: Transform2D, ) { let image = Snapshot::from_vec( - source.size.cast(), + src.size.cast(), SnapshotPixelFormat::BGRA, SnapshotAlphaMode::Transparent { premultiplied: true, @@ -409,83 +265,70 @@ impl GenericDrawTarget for raqote::DrawTarget { surface, ); - let transform = + let paint_transform = raqote::Transform::translation(-dest.origin.x as f32, -dest.origin.y as f32) .then_scale( - source.size.width as f32 / dest.size.width as f32, - source.size.height as f32 / dest.size.height as f32, + src.size.width as f32 / dest.size.width as f32, + src.size.height as f32 / dest.size.height as f32, ); let pattern = Pattern::Surface(SurfacePattern::new( image, filter.to_raqote(), Repetition::NoRepeat, - transform, + paint_transform, )); - let mut pb = canvas_traits::canvas::Path::new(); + self.set_transform(&transform); + let dest = dest.cast(); + let mut pb = raqote::PathBuilder::new(); pb.rect( dest.origin.x, dest.origin.y, dest.size.width, dest.size.height, ); - - >::fill(self, &pb, &pattern, draw_options); + fill_draw_target( + self, + draw_options(composition_options), + pattern, + pb.finish(), + ); } fn draw_surface_with_shadow( &self, _surface: ::SourceSurface, _dest: &Point2D, - _color: &::Color, - _offset: &Vector2D, - _sigma: f32, - _operator: ::CompositionOp, + _shadow_options: ShadowOptions, + _composition_options: CompositionOptions, ) { warn!("no support for drawing shadows"); } fn fill( &mut self, path: &canvas_traits::canvas::Path, - pattern: &Pattern, - draw_options: &::DrawOptions, + style: FillOrStrokeStyle, + composition_options: CompositionOptions, + transform: Transform2D, ) { + self.set_transform(&transform); + let draw_options = draw_options(composition_options); + let pattern = style.to_raqote_pattern(); let path = to_path(path); - match draw_options.blend_mode { - raqote::BlendMode::Src => { - self.clear(raqote::SolidSource::from_unpremultiplied_argb(0, 0, 0, 0)); - self.fill(&path, &source(pattern), draw_options); - }, - raqote::BlendMode::Clear | - raqote::BlendMode::SrcAtop | - raqote::BlendMode::DstOut | - raqote::BlendMode::Add | - raqote::BlendMode::Xor | - raqote::BlendMode::DstOver | - raqote::BlendMode::SrcOver => { - self.fill(&path, &source(pattern), draw_options); - }, - raqote::BlendMode::SrcIn | - raqote::BlendMode::SrcOut | - raqote::BlendMode::DstIn | - raqote::BlendMode::DstAtop => { - let mut options = *draw_options; - self.push_layer_with_blend(1., options.blend_mode); - options.blend_mode = raqote::BlendMode::SrcOver; - self.fill(&path, &source(pattern), &options); - self.pop_layer(); - }, - _ => warn!("unrecognized blend mode: {:?}", draw_options.blend_mode), - } + fill_draw_target(self, draw_options, pattern, path); } fn fill_text( &mut self, text_runs: Vec, start: Point2D, - pattern: &Pattern, - draw_options: &::DrawOptions, + style: FillOrStrokeStyle, + composition_options: CompositionOptions, + transform: Transform2D, ) { + self.set_transform(&transform); + let draw_options = draw_options(composition_options); + let pattern = style.to_raqote_pattern(); let mut advance = 0.; for run in text_runs.iter() { let mut positions = Vec::new(); @@ -532,8 +375,8 @@ impl GenericDrawTarget for raqote::DrawTarget { run.font.descriptor.pt_size.to_f32_px(), &ids, &positions, - &source(pattern), - draw_options, + &source(&pattern), + &draw_options, ); }) } @@ -542,8 +385,9 @@ impl GenericDrawTarget for raqote::DrawTarget { fn fill_rect( &mut self, rect: &Rect, - pattern: &::Pattern<'_>, - draw_options: &::DrawOptions, + style: FillOrStrokeStyle, + composition_options: CompositionOptions, + transform: Transform2D, ) { let rect = rect.cast(); let mut pb = canvas_traits::canvas::Path::new(); @@ -554,50 +398,59 @@ impl GenericDrawTarget for raqote::DrawTarget { rect.size.height, ); - >::fill(self, &pb, pattern, draw_options); + >::fill( + self, + &pb, + style, + composition_options, + transform, + ); } fn get_size(&self) -> Size2D { Size2D::new(self.width(), self.height()) } - fn get_transform(&self) -> Transform2D { - *self.get_transform() - } fn pop_clip(&mut self) { self.pop_clip(); } - fn push_clip(&mut self, path: &canvas_traits::canvas::Path) { + fn push_clip(&mut self, path: &canvas_traits::canvas::Path, transform: Transform2D) { + self.set_transform(&transform); self.push_clip(&to_path(path)); } fn push_clip_rect(&mut self, rect: &Rect) { self.push_clip_rect(rect.to_box2d()); } - fn set_transform(&mut self, matrix: &Transform2D) { - self.set_transform(matrix); - } fn surface(&self) -> ::SourceSurface { self.get_data_u8().to_vec() } fn stroke( &mut self, path: &canvas_traits::canvas::Path, - pattern: &Pattern, - stroke_options: &::StrokeOptions, - draw_options: &::DrawOptions, + style: FillOrStrokeStyle, + line_options: LineOptions, + composition_options: CompositionOptions, + transform: Transform2D, ) { + let pattern = style.to_raqote_pattern(); + let options = draw_options(composition_options); + self.set_transform(&transform); self.stroke( &to_path(path), - &source(pattern), - stroke_options, - draw_options, + &source(&pattern), + &line_options.to_raqote_style(), + &options, ); } fn stroke_rect( &mut self, rect: &Rect, - pattern: &::Pattern<'_>, - stroke_options: &::StrokeOptions, - draw_options: &::DrawOptions, + style: FillOrStrokeStyle, + line_options: LineOptions, + composition_options: CompositionOptions, + transform: Transform2D, ) { + self.set_transform(&transform); + let pattern = style.to_raqote_pattern(); + let options = draw_options(composition_options); let mut pb = raqote::PathBuilder::new(); pb.rect( rect.origin.x, @@ -606,7 +459,12 @@ impl GenericDrawTarget for raqote::DrawTarget { rect.size.height, ); - self.stroke(&pb.finish(), &source(pattern), stroke_options, draw_options); + self.stroke( + &pb.finish(), + &source(&pattern), + &line_options.to_raqote_style(), + &options, + ); } fn image_descriptor_and_serializable_data( @@ -638,6 +496,40 @@ impl GenericDrawTarget for raqote::DrawTarget { } } +fn fill_draw_target( + draw_target: &mut raqote::DrawTarget, + draw_options: DrawOptions, + pattern: Pattern, + path: raqote::Path, +) { + match draw_options.blend_mode { + raqote::BlendMode::Src => { + draw_target.clear(raqote::SolidSource::from_unpremultiplied_argb(0, 0, 0, 0)); + draw_target.fill(&path, &source(&pattern), &draw_options); + }, + raqote::BlendMode::Clear | + raqote::BlendMode::SrcAtop | + raqote::BlendMode::DstOut | + raqote::BlendMode::Add | + raqote::BlendMode::Xor | + raqote::BlendMode::DstOver | + raqote::BlendMode::SrcOver => { + draw_target.fill(&path, &source(&pattern), &draw_options); + }, + raqote::BlendMode::SrcIn | + raqote::BlendMode::SrcOut | + raqote::BlendMode::DstIn | + raqote::BlendMode::DstAtop => { + let mut options = draw_options; + draw_target.push_layer_with_blend(1., options.blend_mode); + options.blend_mode = raqote::BlendMode::SrcOver; + draw_target.fill(&path, &source(&pattern), &options); + draw_target.pop_layer(); + }, + _ => warn!("unrecognized blend mode: {:?}", draw_options.blend_mode), + } +} + impl Filter { fn to_raqote(self) -> raqote::FilterMode { match self { @@ -676,6 +568,29 @@ pub trait ToRaqoteStyle { fn to_raqote_style(self) -> Self::Target; } +impl ToRaqoteStyle for LineOptions { + type Target = StrokeStyle; + + fn to_raqote_style(self) -> Self::Target { + let LineOptions { + width, + cap_style, + join_style, + miter_limit, + dash, + dash_offset, + } = self; + StrokeStyle { + width: width as f32, + cap: cap_style.to_raqote_style(), + join: join_style.to_raqote_style(), + miter_limit: miter_limit as f32, + dash_array: dash, + dash_offset: dash_offset as f32, + } + } +} + impl ToRaqoteStyle for LineJoinStyle { type Target = raqote::LineJoin; @@ -701,7 +616,7 @@ impl ToRaqoteStyle for LineCapStyle { } pub trait ToRaqotePattern { - fn to_raqote_pattern(self) -> Option; + fn to_raqote_pattern(self) -> Pattern; } pub trait ToRaqoteGradientStop { @@ -724,38 +639,36 @@ impl ToRaqoteGradientStop for CanvasGradientStop { impl ToRaqotePattern for FillOrStrokeStyle { #[allow(unsafe_code)] - fn to_raqote_pattern(self) -> Option { + fn to_raqote_pattern(self) -> Pattern { use canvas_traits::canvas::FillOrStrokeStyle::*; match self { Color(color) => { let srgb = color.into_srgb_legacy(); - Some(Pattern::Color( + Pattern::Color( clamp_unit_f32(srgb.alpha), clamp_unit_f32(srgb.components.0), clamp_unit_f32(srgb.components.1), clamp_unit_f32(srgb.components.2), - )) + ) }, LinearGradient(style) => { let start = Point2D::new(style.x0 as f32, style.y0 as f32); let end = Point2D::new(style.x1 as f32, style.y1 as f32); let stops = create_gradient_stops(style.stops); - Some(Pattern::LinearGradient(LinearGradientPattern::new( - start, end, stops, - ))) + Pattern::LinearGradient(LinearGradientPattern::new(start, end, stops)) }, RadialGradient(style) => { let center1 = Point2D::new(style.x0 as f32, style.y0 as f32); let center2 = Point2D::new(style.x1 as f32, style.y1 as f32); let stops = create_gradient_stops(style.stops); - Some(Pattern::RadialGradient(RadialGradientPattern::new( + Pattern::RadialGradient(RadialGradientPattern::new( center1, style.r0 as f32, center2, style.r1 as f32, stops, - ))) + )) }, Surface(style) => { let repeat = Repetition::from_xy(style.repeat_x, style.repeat_y); @@ -766,12 +679,12 @@ impl ToRaqotePattern for FillOrStrokeStyle { }, SnapshotPixelFormat::BGRA, ); - Some(Pattern::Surface(SurfacePattern::new( + Pattern::Surface(SurfacePattern::new( snapshot, raqote::FilterMode::Nearest, repeat, style.transform, - ))) + )) }, } } @@ -869,3 +782,11 @@ fn to_path(path: &canvas_traits::canvas::Path) -> raqote::Path { } pb.finish() } + +fn draw_options(composition_options: CompositionOptions) -> DrawOptions { + DrawOptions { + blend_mode: composition_options.composition_operation.to_raqote_style(), + alpha: composition_options.alpha as f32, + ..Default::default() + } +} diff --git a/components/script/canvas_state.rs b/components/script/canvas_state.rs index b9f61494fe5..95314dc47a9 100644 --- a/components/script/canvas_state.rs +++ b/components/script/canvas_state.rs @@ -150,7 +150,10 @@ impl CanvasContextState { fn text_options(&self) -> TextOptions { TextOptions { - font: self.font_style.clone(), + font: self + .font_style + .as_ref() + .map(|font| servo_arc::Arc::new(font.clone())), align: self.text_align, baseline: self.text_baseline, } @@ -1344,7 +1347,6 @@ impl CanvasState { self.saved_states .borrow_mut() .push(self.state.borrow().clone()); - self.send_canvas_2d_msg(Canvas2dMsg::SaveContext); } #[cfg_attr(crown, allow(crown::unrooted_must_root))] @@ -1353,7 +1355,7 @@ impl CanvasState { let mut saved_states = self.saved_states.borrow_mut(); if let Some(state) = saved_states.pop() { self.state.borrow_mut().clone_from(&state); - self.send_canvas_2d_msg(Canvas2dMsg::RestoreContext); + self.send_canvas_2d_msg(Canvas2dMsg::PopClip); } } diff --git a/components/shared/canvas/canvas.rs b/components/shared/canvas/canvas.rs index c1c78649a13..56fdabfb8ed 100644 --- a/components/shared/canvas/canvas.rs +++ b/components/shared/canvas/canvas.rs @@ -17,6 +17,7 @@ use serde::{Deserialize, Serialize}; use strum::{Display, EnumString}; use style::color::AbsoluteColor; use style::properties::style_structs::Font as FontStyleStruct; +use style::servo_arc::Arc as ServoArc; #[derive(Clone, Debug, Default, Deserialize, Serialize)] pub struct Path(pub BezPath); @@ -400,7 +401,7 @@ pub enum FillRule { #[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, MallocSizeOf, PartialEq, Serialize)] pub struct CanvasId(pub u64); -#[derive(Debug, Deserialize, MallocSizeOf, Serialize)] +#[derive(Clone, Copy, Debug, Deserialize, MallocSizeOf, Serialize)] pub struct CompositionOptions { pub alpha: f64, pub composition_operation: CompositionOrBlending, @@ -414,6 +415,18 @@ pub struct ShadowOptions { pub color: AbsoluteColor, } +impl ShadowOptions { + /// + pub fn need_to_draw_shadow(&self) -> bool { + // Shadows are only drawn if the opacity component of the alpha component of the shadow color is nonzero + self.color.alpha != 0.0 && + // and either the shadowBlur is nonzero, or the shadowOffsetX is nonzero, or the shadowOffsetY is nonzero. + (self.offset_x != 0.0 || + self.offset_y != 0.0 || + self.blur != 0.0) + } +} + #[derive(Debug, Deserialize, MallocSizeOf, Serialize)] pub struct LineOptions { pub width: f64, @@ -426,7 +439,8 @@ pub struct LineOptions { #[derive(Debug, Deserialize, MallocSizeOf, Serialize)] pub struct TextOptions { - pub font: Option, + #[ignore_malloc_size_of = "Arc"] + pub font: Option>, pub align: TextAlign, pub baseline: TextBaseline, } @@ -471,6 +485,7 @@ pub enum Canvas2dMsg { ), ClearRect(Rect, Transform2D), ClipPath(Path, Transform2D), + PopClip, FillPath( FillOrStrokeStyle, Path, @@ -500,8 +515,6 @@ pub enum Canvas2dMsg { GetImageData(Rect, Size2D, IpcSender), MeasureText(String, IpcSender, TextOptions), PutImageData(Rect, IpcSnapshot), - RestoreContext, - SaveContext, StrokeRect( Rect, FillOrStrokeStyle, @@ -627,6 +640,48 @@ pub enum FillOrStrokeStyle { Surface(SurfaceStyle), } +impl FillOrStrokeStyle { + pub fn is_zero_size_gradient(&self) -> bool { + match self { + Self::RadialGradient(pattern) => { + let centers_equal = (pattern.x0, pattern.y0) == (pattern.x1, pattern.y1); + let radii_equal = pattern.r0 == pattern.r1; + (centers_equal && radii_equal) || pattern.stops.is_empty() + }, + Self::LinearGradient(pattern) => { + (pattern.x0, pattern.y0) == (pattern.x1, pattern.y1) || pattern.stops.is_empty() + }, + Self::Color(..) | Self::Surface(..) => false, + } + } + + pub fn x_bound(&self) -> Option { + match self { + Self::Surface(pattern) => { + if pattern.repeat_x { + None + } else { + Some(pattern.surface_size.width) + } + }, + Self::Color(..) | Self::LinearGradient(..) | Self::RadialGradient(..) => None, + } + } + + pub fn y_bound(&self) -> Option { + match self { + Self::Surface(pattern) => { + if pattern.repeat_y { + None + } else { + Some(pattern.surface_size.height) + } + }, + Self::Color(..) | Self::LinearGradient(..) | Self::RadialGradient(..) => None, + } + } +} + #[derive( Clone, Copy, Debug, Display, Deserialize, EnumString, MallocSizeOf, PartialEq, Serialize, )]