diff --git a/components/canvas/canvas_data.rs b/components/canvas/canvas_data.rs index b61e4d1e73a..ec8433f44f7 100644 --- a/components/canvas/canvas_data.rs +++ b/components/canvas/canvas_data.rs @@ -34,24 +34,6 @@ use crate::backend::{ // https://github.com/servo/webrender/blob/main/webrender/src/texture_cache.rs#L1475 const MIN_WR_IMAGE_SIZE: Size2D = Size2D::new(1, 1); -/// The canvas data stores a state machine for the current status of -/// the path data and any relevant transformations that are -/// applied to it. The Azure drawing API expects the path to be in -/// userspace. However, when a path is being built but the canvas' -/// transform changes, we choose to transform the path and perform -/// further operations to it in device space. When it's time to -/// draw the path, we convert it back to userspace and draw it -/// with the correct transform applied. -/// TODO: De-abstract now that Azure is removed? -enum PathState { - /// Path in user-space. If a transform has been applied but - /// but no further path operations have occurred, it is stored - /// in the optional field. - UserSpacePath(Path, Option>), - /// Path in device-space. - DeviceSpacePath(Path), -} - /// A wrapper around a stored PathBuilder and an optional transformation that should be /// applied to any points to ensure they are in the matching device space. pub(crate) struct PathBuilderRef<'a> { @@ -305,7 +287,8 @@ pub(crate) enum Filter { pub(crate) struct CanvasData<'a, B: Backend> { backend: B, drawtarget: B::DrawTarget, - path_state: Option, + /// + path_state: Path, state: CanvasPaintState<'a, B>, saved_states: Vec>, compositor_api: CrossProcessCompositorApi, @@ -329,7 +312,7 @@ impl<'a, B: Backend> CanvasData<'a, B> { state: backend.new_paint_state(), backend, drawtarget: draw_target, - path_state: None, + path_state: Path::new(), saved_states: vec![], compositor_api, image_key, @@ -708,64 +691,19 @@ impl<'a, B: Backend> CanvasData<'a, B> { pub(crate) fn begin_path(&mut self) { // Erase any traces of previous paths that existed before this. - self.path_state = None; + self.path_state = Path::new(); } pub(crate) fn close_path(&mut self) { self.path_builder().close(); } - /// Turn the [`Self::path_state`] into a user-space path, returning `None` if the - /// path transformation matrix is uninvertible. - fn ensure_path(&mut self) -> Option<&Path> { - // If there's no record of any path yet, create a new path in user-space. - if self.path_state.is_none() { - self.path_state = Some(PathState::UserSpacePath(Path::new(), None)); - } - - // If a user-space path exists, create a device-space path based on it if - // any transform is present. - let new_state = match *self.path_state.as_ref().unwrap() { - PathState::UserSpacePath(ref path, Some(ref transform)) => { - let mut path = path.clone(); - path.transform(transform.cast()); - Some(path) - }, - PathState::UserSpacePath(..) | PathState::DeviceSpacePath(..) => None, - }; - if let Some(builder) = new_state { - self.path_state = Some(PathState::DeviceSpacePath(builder)); - } - - // If a device-space path is present, create a user-space path from its - // finished path by inverting the initial transformation. - let new_state = match *self.path_state.as_mut().unwrap() { - PathState::DeviceSpacePath(ref mut builder) => { - let inverse = self.drawtarget.get_transform().inverse()?; - let mut path = builder.clone(); - path.transform(inverse.cast()); - Some(path) - }, - PathState::UserSpacePath(..) => None, - }; - if let Some(path) = new_state { - self.path_state = Some(PathState::UserSpacePath(path, None)); - } - - match self.path_state.as_ref() { - Some(PathState::UserSpacePath(path, _)) => Some(path), - _ => unreachable!("Should have been able to successful build path or returned early."), - } - } - pub(crate) fn fill(&mut self) { if self.state.fill_style.is_zero_size_gradient() { return; // Paint nothing if gradient size is zero. } - let Some(path) = self.ensure_path().cloned() else { - return; // Path is uninvertible. - }; + let path = self.path_state.clone(); self.maybe_bound_shape_with_pattern( self.state.fill_style.clone(), @@ -794,9 +732,7 @@ impl<'a, B: Backend> CanvasData<'a, B> { return; // Paint nothing if gradient size is zero. } - let Some(path) = self.ensure_path().cloned() else { - return; // Path is uninvertible. - }; + let path = self.path_state.clone(); self.maybe_bound_shape_with_pattern( self.state.stroke_style.clone(), @@ -832,11 +768,9 @@ impl<'a, B: Backend> CanvasData<'a, B> { } pub(crate) fn clip(&mut self) { - let Some(path) = self.ensure_path().cloned() else { - return; // Path is uninvertible. - }; + let path = &self.path_state; - self.drawtarget.push_clip(&path); + self.drawtarget.push_clip(path); } pub(crate) fn clip_path(&mut self, path: &Path) { @@ -850,18 +784,9 @@ impl<'a, B: Backend> CanvasData<'a, B> { fill_rule: FillRule, chan: IpcSender, ) { - self.ensure_path(); - let result = match self.path_state.as_ref() { - Some(PathState::UserSpacePath(path, transform)) => { - let target_transform = self.drawtarget.get_transform(); - let path_transform = transform.as_ref().unwrap_or(&target_transform); - let mut path = path.clone(); - path.transform(path_transform.cast()); - path.is_point_in_path(x, y, fill_rule) - }, - Some(_) | None => false, - }; - chan.send(result).unwrap(); + let mut path = self.path_state.clone(); + path.transform(self.get_transform().cast()); + chan.send(path.is_point_in_path(x, y, fill_rule)).unwrap(); } pub(crate) fn move_to(&mut self, point: &Point2D) { @@ -873,57 +798,9 @@ impl<'a, B: Backend> CanvasData<'a, B> { } fn path_builder(&mut self) -> PathBuilderRef { - if self.path_state.is_none() { - self.path_state = Some(PathState::UserSpacePath(Path::new(), None)); - } - - // Rust is not pleased by returning a reference to a builder in some branches - // and overwriting path_state in other ones. The following awkward use of duplicate - // matches works around the resulting borrow errors. - let new_state = { - match *self.path_state.as_mut().unwrap() { - PathState::DeviceSpacePath(_) => None, - PathState::UserSpacePath(ref path, Some(ref transform)) => { - let mut path = path.clone(); - path.transform(transform.cast()); - Some(PathState::DeviceSpacePath(path)) - }, - PathState::UserSpacePath(ref path, None) => { - Some(PathState::UserSpacePath(path.clone(), None)) - }, - } - }; - match new_state { - // There's a new builder value that needs to be stored. - Some(state) => self.path_state = Some(state), - // There's an existing builder value that can be returned immediately. - None => match *self.path_state.as_mut().unwrap() { - PathState::UserSpacePath(ref mut builder, None) => { - return PathBuilderRef { - builder, - transform: Transform2D::identity(), - }; - }, - PathState::DeviceSpacePath(ref mut builder) => { - return PathBuilderRef { - builder, - transform: self.drawtarget.get_transform(), - }; - }, - _ => unreachable!(), - }, - } - - match *self.path_state.as_mut().unwrap() { - PathState::UserSpacePath(ref mut builder, None) => PathBuilderRef { - builder, - transform: Transform2D::identity(), - }, - PathState::DeviceSpacePath(ref mut builder) => PathBuilderRef { - builder, - transform: self.drawtarget.get_transform(), - }, - PathState::UserSpacePath(..) => unreachable!(), + PathBuilderRef { + builder: &mut self.path_state, + transform: Transform2D::identity(), } } @@ -932,9 +809,6 @@ impl<'a, B: Backend> CanvasData<'a, B> { } pub(crate) fn quadratic_curve_to(&mut self, cp: &Point2D, endpoint: &Point2D) { - if self.path_state.is_none() { - self.move_to(cp); - } self.path_builder().quadratic_curve_to(cp, endpoint); } @@ -944,9 +818,6 @@ impl<'a, B: Backend> CanvasData<'a, B> { cp2: &Point2D, endpoint: &Point2D, ) { - if self.path_state.is_none() { - self.move_to(cp1); - } self.path_builder().bezier_curve_to(cp1, cp2, endpoint); } @@ -1027,18 +898,12 @@ impl<'a, B: Backend> CanvasData<'a, B> { } pub(crate) fn set_transform(&mut self, transform: &Transform2D) { - // If there is an in-progress path, store the existing transformation required - // to move between device and user space. - match self.path_state.as_mut() { - None | Some(PathState::DeviceSpacePath(..)) => (), - Some(PathState::UserSpacePath(_, transform)) => { - if transform.is_none() { - *transform = Some(self.drawtarget.get_transform()); - } - }, - } + self.path_state.transform(self.get_transform().cast()); self.state.transform = *transform; - self.drawtarget.set_transform(transform) + self.drawtarget.set_transform(transform); + if let Some(inverse) = transform.inverse() { + self.path_state.transform(inverse.cast()); + } } pub(crate) fn set_global_alpha(&mut self, alpha: f32) { @@ -1061,7 +926,7 @@ impl<'a, B: Backend> CanvasData<'a, B> { .create_drawtarget(Size2D::new(size.width, size.height)); // Step 2. Empty the list of subpaths in context's current default path. - self.path_state = None; + self.path_state = Path::new(); // Step 3. Clear the context's drawing state stack. self.saved_states.clear();