mirror of
https://github.com/servo/servo.git
synced 2025-08-06 22:15:33 +01:00
canvas: Don't do operations on paths with uninvertible transforms (#37551)
When the path is created with an uninvertible transform, don't do any path-based operations. Now `ensure_path()` returns a `Path` object and this change adds early returns which prevent trying to use it. Testing: This change adds a WPT crash test. Fixes #36995. Signed-off-by: Martin Robinson <mrobinson@igalia.com>
This commit is contained in:
parent
a27c9ee691
commit
e5c328d660
3 changed files with 39 additions and 42 deletions
|
@ -128,24 +128,6 @@ enum PathState<B: Backend> {
|
||||||
UserSpacePath(B::Path, Option<Transform2D<f32>>),
|
UserSpacePath(B::Path, Option<Transform2D<f32>>),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<B: Backend> PathState<B> {
|
|
||||||
fn is_path(&self) -> bool {
|
|
||||||
match *self {
|
|
||||||
PathState::UserSpacePath(..) => true,
|
|
||||||
PathState::UserSpacePathBuilder(..) | PathState::DeviceSpacePathBuilder(..) => false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn path(&self) -> &B::Path {
|
|
||||||
match *self {
|
|
||||||
PathState::UserSpacePath(ref p, _) => p,
|
|
||||||
PathState::UserSpacePathBuilder(..) | PathState::DeviceSpacePathBuilder(..) => {
|
|
||||||
panic!("should have called ensure_path")
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A wrapper around a stored PathBuilder and an optional transformation that should be
|
/// 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.
|
/// applied to any points to ensure they are in the matching device space.
|
||||||
struct PathBuilderRef<'a, B: Backend> {
|
struct PathBuilderRef<'a, B: Backend> {
|
||||||
|
@ -843,7 +825,9 @@ impl<'a, B: Backend> CanvasData<'a, B> {
|
||||||
self.path_builder().close();
|
self.path_builder().close();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn ensure_path(&mut self) {
|
/// 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<&B::Path> {
|
||||||
// If there's no record of any path yet, create a new builder in user-space.
|
// If there's no record of any path yet, create a new builder in user-space.
|
||||||
if self.path_state.is_none() {
|
if self.path_state.is_none() {
|
||||||
self.path_state = Some(PathState::UserSpacePathBuilder(
|
self.path_state = Some(PathState::UserSpacePathBuilder(
|
||||||
|
@ -881,15 +865,8 @@ impl<'a, B: Backend> CanvasData<'a, B> {
|
||||||
// finished path by inverting the initial transformation.
|
// finished path by inverting the initial transformation.
|
||||||
let new_state = match *self.path_state.as_mut().unwrap() {
|
let new_state = match *self.path_state.as_mut().unwrap() {
|
||||||
PathState::DeviceSpacePathBuilder(ref mut builder) => {
|
PathState::DeviceSpacePathBuilder(ref mut builder) => {
|
||||||
let path = builder.finish();
|
let inverse = self.drawtarget.get_transform().inverse()?;
|
||||||
let inverse = match self.drawtarget.get_transform().inverse() {
|
let mut builder = builder.finish().transformed_copy_to_builder(&inverse);
|
||||||
Some(m) => m,
|
|
||||||
None => {
|
|
||||||
warn!("Couldn't invert canvas transformation.");
|
|
||||||
return;
|
|
||||||
},
|
|
||||||
};
|
|
||||||
let mut builder = path.transformed_copy_to_builder(&inverse);
|
|
||||||
Some(builder.finish())
|
Some(builder.finish())
|
||||||
},
|
},
|
||||||
PathState::UserSpacePathBuilder(..) | PathState::UserSpacePath(..) => None,
|
PathState::UserSpacePathBuilder(..) | PathState::UserSpacePath(..) => None,
|
||||||
|
@ -898,14 +875,10 @@ impl<'a, B: Backend> CanvasData<'a, B> {
|
||||||
self.path_state = Some(PathState::UserSpacePath(path, None));
|
self.path_state = Some(PathState::UserSpacePath(path, None));
|
||||||
}
|
}
|
||||||
|
|
||||||
assert!(self.path_state.as_ref().unwrap().is_path())
|
match self.path_state.as_ref() {
|
||||||
}
|
Some(PathState::UserSpacePath(path, _)) => Some(path),
|
||||||
|
_ => unreachable!("Should have been able to successful build path or returned early."),
|
||||||
fn path(&self) -> &B::Path {
|
}
|
||||||
self.path_state
|
|
||||||
.as_ref()
|
|
||||||
.expect("Should have called ensure_path()")
|
|
||||||
.path()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn fill(&mut self) {
|
pub(crate) fn fill(&mut self) {
|
||||||
|
@ -913,9 +886,12 @@ impl<'a, B: Backend> CanvasData<'a, B> {
|
||||||
return; // Paint nothing if gradient size is zero.
|
return; // Paint nothing if gradient size is zero.
|
||||||
}
|
}
|
||||||
|
|
||||||
self.ensure_path();
|
let Some(path) = self.ensure_path().cloned() else {
|
||||||
|
return; // Path is uninvertible.
|
||||||
|
};
|
||||||
|
|
||||||
self.drawtarget.fill(
|
self.drawtarget.fill(
|
||||||
&self.path().clone(),
|
&path,
|
||||||
self.state.fill_style.clone(),
|
self.state.fill_style.clone(),
|
||||||
&self.state.draw_options.clone(),
|
&self.state.draw_options.clone(),
|
||||||
);
|
);
|
||||||
|
@ -940,9 +916,12 @@ impl<'a, B: Backend> CanvasData<'a, B> {
|
||||||
return; // Paint nothing if gradient size is zero.
|
return; // Paint nothing if gradient size is zero.
|
||||||
}
|
}
|
||||||
|
|
||||||
self.ensure_path();
|
let Some(path) = self.ensure_path().cloned() else {
|
||||||
|
return; // Path is uninvertible.
|
||||||
|
};
|
||||||
|
|
||||||
self.drawtarget.stroke(
|
self.drawtarget.stroke(
|
||||||
&self.path().clone(),
|
&path,
|
||||||
self.state.stroke_style.clone(),
|
self.state.stroke_style.clone(),
|
||||||
&self.state.stroke_opts,
|
&self.state.stroke_opts,
|
||||||
&self.state.draw_options,
|
&self.state.draw_options,
|
||||||
|
@ -965,8 +944,10 @@ impl<'a, B: Backend> CanvasData<'a, B> {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn clip(&mut self) {
|
pub(crate) fn clip(&mut self) {
|
||||||
self.ensure_path();
|
let Some(path) = self.ensure_path().cloned() else {
|
||||||
let path = self.path().clone();
|
return; // Path is uninvertible.
|
||||||
|
};
|
||||||
|
|
||||||
self.drawtarget.push_clip(&path);
|
self.drawtarget.push_clip(&path);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
9
tests/wpt/meta/MANIFEST.json
vendored
9
tests/wpt/meta/MANIFEST.json
vendored
|
@ -7814,6 +7814,15 @@
|
||||||
],
|
],
|
||||||
"element": {
|
"element": {
|
||||||
"manual": {
|
"manual": {
|
||||||
|
"drawing-paths-to-the-canvas": {
|
||||||
|
"fill-path-with-uninvertible-transform-crash.html": [
|
||||||
|
"9d032ddba9637c8bee07c8cb9c4ec618a1c5d701",
|
||||||
|
[
|
||||||
|
null,
|
||||||
|
{}
|
||||||
|
]
|
||||||
|
]
|
||||||
|
},
|
||||||
"filters": {
|
"filters": {
|
||||||
"svg-filter-crash.html": [
|
"svg-filter-crash.html": [
|
||||||
"f64379c79241409ab55535596f4cbad894f45b42",
|
"f64379c79241409ab55535596f4cbad894f45b42",
|
||||||
|
|
|
@ -0,0 +1,7 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<script>
|
||||||
|
let context = document.createElement("canvas").getContext("2d");
|
||||||
|
context.moveTo(0, 0);
|
||||||
|
context.setTransform(4, 2, 6, 3, 0, 0); // non-invertible (4 * 3 - 6 * 2 = 0)
|
||||||
|
context.fill("nonzero");
|
||||||
|
</script>
|
Loading…
Add table
Add a link
Reference in a new issue