mirror of
https://github.com/servo/servo.git
synced 2025-08-06 22:15:33 +01:00
canvas: Use wrapped kurbo::BezPath
for path everywhere (#37967)
This PR removes existing path(segment) abstractions in favor of `kurbo::BezPath`, well actually wrapped `kurbo::BezPath`, to ensure building of valid paths. This allows us better Path2D building in script and doing all validation and segmentation there and also allows us remove blocking is_point_in_path on Path2D as we can now do this in script. Current path is still done on canvas thread side as it will be harder to move to script (will be done as a follow up), but it now uses this new path abstraction. Using kurbo also allows us to ditch our manual svgpath parser with the one provided by kurbo. Same code is stolen from: https://github.com/servo/servo/pull/36821. Testing: Existing WPT tests Fixes: #37904 wpt run: https://github.com/sagudev/servo/actions/runs/16172191716 --------- Signed-off-by: sagudev <16504129+sagudev@users.noreply.github.com>
This commit is contained in:
parent
d4528e84b9
commit
9b5b26386c
23 changed files with 571 additions and 1502 deletions
|
@ -21,6 +21,7 @@ euclid = { workspace = true }
|
|||
font-kit = "0.14"
|
||||
fonts = { path = "../fonts" }
|
||||
ipc-channel = { workspace = true }
|
||||
kurbo = { workspace = true }
|
||||
log = { workspace = true }
|
||||
lyon_geom = "1.0.4"
|
||||
net_traits = { workspace = true }
|
||||
|
|
|
@ -3,17 +3,15 @@
|
|||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||
|
||||
use canvas_traits::canvas::{
|
||||
CompositionOrBlending, FillOrStrokeStyle, LineCapStyle, LineJoinStyle, PathSegment,
|
||||
CompositionOrBlending, FillOrStrokeStyle, LineCapStyle, LineJoinStyle, Path,
|
||||
};
|
||||
use compositing_traits::SerializableImageData;
|
||||
use euclid::Angle;
|
||||
use euclid::default::{Point2D, Rect, Size2D, Transform2D, Vector2D};
|
||||
use lyon_geom::Arc;
|
||||
use pixels::Snapshot;
|
||||
use style::color::AbsoluteColor;
|
||||
use webrender_api::ImageDescriptor;
|
||||
|
||||
use crate::canvas_data::{CanvasPaintState, Filter, PathBuilderRef, TextRun};
|
||||
use crate::canvas_data::{CanvasPaintState, Filter, TextRun};
|
||||
|
||||
pub(crate) trait Backend: Clone + Sized {
|
||||
type Pattern<'a>: PatternHelpers + Clone;
|
||||
|
@ -22,7 +20,6 @@ pub(crate) trait Backend: Clone + Sized {
|
|||
type DrawOptions: DrawOptionsHelpers + Clone;
|
||||
type CompositionOp;
|
||||
type DrawTarget: GenericDrawTarget<Self>;
|
||||
type Path: GenericPath<Self> + Clone;
|
||||
type SourceSurface;
|
||||
type GradientStop;
|
||||
type GradientStops;
|
||||
|
@ -80,7 +77,7 @@ pub(crate) trait GenericDrawTarget<B: Backend> {
|
|||
sigma: f32,
|
||||
operator: B::CompositionOp,
|
||||
);
|
||||
fn fill(&mut self, path: &B::Path, pattern: &B::Pattern<'_>, draw_options: &B::DrawOptions);
|
||||
fn fill(&mut self, path: &Path, pattern: &B::Pattern<'_>, draw_options: &B::DrawOptions);
|
||||
fn fill_text(
|
||||
&mut self,
|
||||
text_runs: Vec<TextRun>,
|
||||
|
@ -97,12 +94,12 @@ pub(crate) trait GenericDrawTarget<B: Backend> {
|
|||
fn get_size(&self) -> Size2D<i32>;
|
||||
fn get_transform(&self) -> Transform2D<f32>;
|
||||
fn pop_clip(&mut self);
|
||||
fn push_clip(&mut self, path: &B::Path);
|
||||
fn push_clip(&mut self, path: &Path);
|
||||
fn push_clip_rect(&mut self, rect: &Rect<i32>);
|
||||
fn set_transform(&mut self, matrix: &Transform2D<f32>);
|
||||
fn stroke(
|
||||
&mut self,
|
||||
path: &B::Path,
|
||||
path: &Path,
|
||||
pattern: &B::Pattern<'_>,
|
||||
stroke_options: &B::StrokeOptions,
|
||||
draw_options: &B::DrawOptions,
|
||||
|
@ -119,200 +116,6 @@ pub(crate) trait GenericDrawTarget<B: Backend> {
|
|||
fn snapshot(&self) -> Snapshot;
|
||||
}
|
||||
|
||||
/// A generic Path that abstracts the interface for raqote's PathBuilder/Path.
|
||||
pub(crate) trait GenericPath<B: Backend<Path = Self>> {
|
||||
fn new() -> Self;
|
||||
fn transform(&mut self, transform: &Transform2D<f32>);
|
||||
fn arc(
|
||||
&mut self,
|
||||
origin: Point2D<f32>,
|
||||
radius: f32,
|
||||
start_angle: f32,
|
||||
end_angle: f32,
|
||||
anticlockwise: bool,
|
||||
) {
|
||||
Self::ellipse(
|
||||
self,
|
||||
origin,
|
||||
radius,
|
||||
radius,
|
||||
0.,
|
||||
start_angle,
|
||||
end_angle,
|
||||
anticlockwise,
|
||||
);
|
||||
}
|
||||
fn bezier_curve_to(
|
||||
&mut self,
|
||||
control_point1: &Point2D<f32>,
|
||||
control_point2: &Point2D<f32>,
|
||||
control_point3: &Point2D<f32>,
|
||||
);
|
||||
fn close(&mut self);
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn ellipse(
|
||||
&mut self,
|
||||
origin: Point2D<f32>,
|
||||
radius_x: f32,
|
||||
radius_y: f32,
|
||||
rotation_angle: f32,
|
||||
start_angle: f32,
|
||||
end_angle: f32,
|
||||
anticlockwise: bool,
|
||||
) {
|
||||
let mut start = Angle::radians(start_angle);
|
||||
let mut end = Angle::radians(end_angle);
|
||||
|
||||
// Wrap angles mod 2 * PI if necessary
|
||||
if !anticlockwise && start > end + Angle::two_pi() ||
|
||||
anticlockwise && end > start + Angle::two_pi()
|
||||
{
|
||||
start = start.positive();
|
||||
end = end.positive();
|
||||
}
|
||||
|
||||
// Calculate the total arc we're going to sweep.
|
||||
let sweep = match anticlockwise {
|
||||
true => {
|
||||
if end - start == Angle::two_pi() {
|
||||
-Angle::two_pi()
|
||||
} else if end > start {
|
||||
-(Angle::two_pi() - (end - start))
|
||||
} else {
|
||||
-(start - end)
|
||||
}
|
||||
},
|
||||
false => {
|
||||
if start - end == Angle::two_pi() {
|
||||
Angle::two_pi()
|
||||
} else if start > end {
|
||||
Angle::two_pi() - (start - end)
|
||||
} else {
|
||||
end - start
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
let arc: Arc<f32> = Arc {
|
||||
center: origin,
|
||||
radii: Vector2D::new(radius_x, radius_y),
|
||||
start_angle: start,
|
||||
sweep_angle: sweep,
|
||||
x_rotation: Angle::radians(rotation_angle),
|
||||
};
|
||||
|
||||
self.line_to(arc.from());
|
||||
|
||||
if sweep.radians.abs() < 1e-3 {
|
||||
return;
|
||||
}
|
||||
|
||||
arc.for_each_quadratic_bezier(&mut |q| {
|
||||
self.quadratic_curve_to(&q.ctrl, &q.to);
|
||||
});
|
||||
}
|
||||
fn get_current_point(&mut self) -> Option<Point2D<f32>>;
|
||||
fn line_to(&mut self, point: Point2D<f32>);
|
||||
fn move_to(&mut self, point: Point2D<f32>);
|
||||
fn quadratic_curve_to(&mut self, control_point: &Point2D<f32>, end_point: &Point2D<f32>);
|
||||
fn svg_arc(
|
||||
&mut self,
|
||||
radius_x: f32,
|
||||
radius_y: f32,
|
||||
rotation_angle: f32,
|
||||
large_arc: bool,
|
||||
sweep: bool,
|
||||
end_point: Point2D<f32>,
|
||||
) {
|
||||
let Some(start) = self.get_current_point() else {
|
||||
return;
|
||||
};
|
||||
|
||||
let arc = lyon_geom::SvgArc {
|
||||
from: start,
|
||||
to: end_point,
|
||||
radii: lyon_geom::vector(radius_x, radius_y),
|
||||
x_rotation: lyon_geom::Angle::degrees(rotation_angle),
|
||||
flags: lyon_geom::ArcFlags { large_arc, sweep },
|
||||
};
|
||||
|
||||
arc.for_each_quadratic_bezier(&mut |q| {
|
||||
self.quadratic_curve_to(&q.ctrl, &q.to);
|
||||
});
|
||||
}
|
||||
fn contains_point(&self, x: f64, y: f64, path_transform: &Transform2D<f32>) -> bool;
|
||||
fn add_segments(&mut self, path: &[PathSegment]) {
|
||||
let mut build_ref = PathBuilderRef::<B> {
|
||||
builder: self,
|
||||
transform: Transform2D::identity(),
|
||||
};
|
||||
for &seg in path {
|
||||
match seg {
|
||||
PathSegment::ClosePath => build_ref.close(),
|
||||
PathSegment::MoveTo { x, y } => build_ref.move_to(&Point2D::new(x, y)),
|
||||
PathSegment::LineTo { x, y } => build_ref.line_to(&Point2D::new(x, y)),
|
||||
PathSegment::Quadratic { cpx, cpy, x, y } => {
|
||||
build_ref.quadratic_curve_to(&Point2D::new(cpx, cpy), &Point2D::new(x, y))
|
||||
},
|
||||
PathSegment::Bezier {
|
||||
cp1x,
|
||||
cp1y,
|
||||
cp2x,
|
||||
cp2y,
|
||||
x,
|
||||
y,
|
||||
} => build_ref.bezier_curve_to(
|
||||
&Point2D::new(cp1x, cp1y),
|
||||
&Point2D::new(cp2x, cp2y),
|
||||
&Point2D::new(x, y),
|
||||
),
|
||||
PathSegment::ArcTo {
|
||||
cp1x,
|
||||
cp1y,
|
||||
cp2x,
|
||||
cp2y,
|
||||
radius,
|
||||
} => build_ref.arc_to(&Point2D::new(cp1x, cp1y), &Point2D::new(cp2x, cp2y), radius),
|
||||
PathSegment::Ellipse {
|
||||
x,
|
||||
y,
|
||||
radius_x,
|
||||
radius_y,
|
||||
rotation,
|
||||
start_angle,
|
||||
end_angle,
|
||||
anticlockwise,
|
||||
} => build_ref.ellipse(
|
||||
&Point2D::new(x, y),
|
||||
radius_x,
|
||||
radius_y,
|
||||
rotation,
|
||||
start_angle,
|
||||
end_angle,
|
||||
anticlockwise,
|
||||
),
|
||||
PathSegment::SvgArc {
|
||||
radius_x,
|
||||
radius_y,
|
||||
rotation,
|
||||
large_arc,
|
||||
sweep,
|
||||
x,
|
||||
y,
|
||||
} => build_ref.svg_arc(
|
||||
radius_x,
|
||||
radius_y,
|
||||
rotation,
|
||||
large_arc,
|
||||
sweep,
|
||||
&Point2D::new(x, y),
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
fn bounding_box(&self) -> Rect<f64>;
|
||||
}
|
||||
|
||||
pub(crate) trait PatternHelpers {
|
||||
fn is_zero_size_gradient(&self) -> bool;
|
||||
fn x_bound(&self) -> Option<u32>;
|
||||
|
|
|
@ -26,7 +26,7 @@ use unicode_script::Script;
|
|||
use webrender_api::ImageKey;
|
||||
|
||||
use crate::backend::{
|
||||
Backend, DrawOptionsHelpers as _, GenericDrawTarget as _, GenericPath, PatternHelpers,
|
||||
Backend, DrawOptionsHelpers as _, GenericDrawTarget as _, PatternHelpers,
|
||||
StrokeOptionsHelpers as _,
|
||||
};
|
||||
|
||||
|
@ -43,42 +43,32 @@ const MIN_WR_IMAGE_SIZE: Size2D<u64> = Size2D::new(1, 1);
|
|||
/// 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<B: Backend> {
|
||||
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(B::Path, Option<Transform2D<f32>>),
|
||||
UserSpacePath(Path, Option<Transform2D<f32>>),
|
||||
/// Path in device-space.
|
||||
DeviceSpacePath(B::Path),
|
||||
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, B: Backend> {
|
||||
pub(crate) builder: &'a mut B::Path,
|
||||
pub(crate) struct PathBuilderRef<'a> {
|
||||
pub(crate) builder: &'a mut Path,
|
||||
pub(crate) transform: Transform2D<f32>,
|
||||
}
|
||||
|
||||
impl<B: Backend> PathBuilderRef<'_, B> {
|
||||
/// <https://html.spec.whatwg.org/multipage#ensure-there-is-a-subpath>
|
||||
pub(crate) fn ensure_there_is_a_subpath(&mut self, point: &Point2D<f32>) {
|
||||
if self.builder.get_current_point().is_none() {
|
||||
self.builder.move_to(*point);
|
||||
}
|
||||
}
|
||||
|
||||
impl PathBuilderRef<'_> {
|
||||
/// <https://html.spec.whatwg.org/multipage#dom-context-2d-lineto>
|
||||
pub(crate) fn line_to(&mut self, pt: &Point2D<f32>) {
|
||||
// 2. If the object's path has no subpaths, then ensure there is a subpath for (x, y).
|
||||
self.ensure_there_is_a_subpath(pt);
|
||||
|
||||
let pt = self.transform.transform_point(*pt);
|
||||
self.builder.line_to(pt);
|
||||
let pt = self.transform.transform_point(*pt).cast();
|
||||
self.builder.line_to(pt.x, pt.y);
|
||||
}
|
||||
|
||||
pub(crate) fn move_to(&mut self, pt: &Point2D<f32>) {
|
||||
let pt = self.transform.transform_point(*pt);
|
||||
self.builder.move_to(pt);
|
||||
let pt = self.transform.transform_point(*pt).cast();
|
||||
self.builder.move_to(pt.x, pt.y);
|
||||
}
|
||||
|
||||
pub(crate) fn rect(&mut self, rect: &Rect<f32>) {
|
||||
|
@ -101,13 +91,10 @@ impl<B: Backend> PathBuilderRef<'_, B> {
|
|||
|
||||
/// <https://html.spec.whatwg.org/multipage#dom-context-2d-quadraticcurveto>
|
||||
pub(crate) fn quadratic_curve_to(&mut self, cp: &Point2D<f32>, endpoint: &Point2D<f32>) {
|
||||
// 2. Ensure there is a subpath for (cpx, cpy).
|
||||
self.ensure_there_is_a_subpath(cp);
|
||||
|
||||
self.builder.quadratic_curve_to(
|
||||
&self.transform.transform_point(*cp),
|
||||
&self.transform.transform_point(*endpoint),
|
||||
)
|
||||
let cp = self.transform.transform_point(*cp).cast();
|
||||
let endpoint = self.transform.transform_point(*endpoint).cast();
|
||||
self.builder
|
||||
.quadratic_curve_to(cp.x, cp.y, endpoint.x, endpoint.y)
|
||||
}
|
||||
|
||||
/// <https://html.spec.whatwg.org/multipage#dom-context-2d-beziercurveto>
|
||||
|
@ -117,14 +104,11 @@ impl<B: Backend> PathBuilderRef<'_, B> {
|
|||
cp2: &Point2D<f32>,
|
||||
endpoint: &Point2D<f32>,
|
||||
) {
|
||||
// 2. Ensure there is a subpath for (cp1x, cp1y).
|
||||
self.ensure_there_is_a_subpath(cp1);
|
||||
|
||||
self.builder.bezier_curve_to(
|
||||
&self.transform.transform_point(*cp1),
|
||||
&self.transform.transform_point(*cp2),
|
||||
&self.transform.transform_point(*endpoint),
|
||||
)
|
||||
let cp1 = self.transform.transform_point(*cp1).cast();
|
||||
let cp2 = self.transform.transform_point(*cp2).cast();
|
||||
let endpoint = self.transform.transform_point(*endpoint).cast();
|
||||
self.builder
|
||||
.bezier_curve_to(cp1.x, cp1.y, cp2.x, cp2.y, endpoint.x, endpoint.y)
|
||||
}
|
||||
|
||||
pub(crate) fn arc(
|
||||
|
@ -135,17 +119,23 @@ impl<B: Backend> PathBuilderRef<'_, B> {
|
|||
end_angle: f32,
|
||||
ccw: bool,
|
||||
) {
|
||||
let center = self.transform.transform_point(*center);
|
||||
self.builder
|
||||
.arc(center, radius, start_angle, end_angle, ccw);
|
||||
let center = self.transform.transform_point(*center).cast();
|
||||
let _ = self.builder.arc(
|
||||
center.x,
|
||||
center.y,
|
||||
radius as f64,
|
||||
start_angle as f64,
|
||||
end_angle as f64,
|
||||
ccw,
|
||||
);
|
||||
}
|
||||
|
||||
/// <https://html.spec.whatwg.org/multipage#dom-context-2d-arcto>
|
||||
pub(crate) fn arc_to(&mut self, cp1: &Point2D<f32>, cp2: &Point2D<f32>, radius: f32) {
|
||||
let cp0 = if let (Some(inverse), Some(point)) =
|
||||
(self.transform.inverse(), self.builder.get_current_point())
|
||||
(self.transform.inverse(), self.builder.last_point())
|
||||
{
|
||||
inverse.transform_point(Point2D::new(point.x, point.y))
|
||||
inverse.transform_point(Point2D::new(point.x, point.y).cast())
|
||||
} else {
|
||||
*cp1
|
||||
};
|
||||
|
@ -218,40 +208,21 @@ impl<B: Backend> PathBuilderRef<'_, B> {
|
|||
end_angle: f32,
|
||||
ccw: bool,
|
||||
) {
|
||||
let center = self.transform.transform_point(*center);
|
||||
self.builder.ellipse(
|
||||
center,
|
||||
radius_x,
|
||||
radius_y,
|
||||
rotation_angle,
|
||||
start_angle,
|
||||
end_angle,
|
||||
let center = self.transform.transform_point(*center).cast();
|
||||
let _ = self.builder.ellipse(
|
||||
center.x,
|
||||
center.y,
|
||||
radius_x as f64,
|
||||
radius_y as f64,
|
||||
rotation_angle as f64,
|
||||
start_angle as f64,
|
||||
end_angle as f64,
|
||||
ccw,
|
||||
);
|
||||
}
|
||||
|
||||
pub(crate) fn svg_arc(
|
||||
&mut self,
|
||||
radius_x: f32,
|
||||
radius_y: f32,
|
||||
rotation_angle: f32,
|
||||
large_arc: bool,
|
||||
sweep: bool,
|
||||
end_point: &Point2D<f32>,
|
||||
) {
|
||||
let end_point = self.transform.transform_point(*end_point);
|
||||
self.builder.svg_arc(
|
||||
radius_x,
|
||||
radius_y,
|
||||
rotation_angle,
|
||||
large_arc,
|
||||
sweep,
|
||||
end_point,
|
||||
);
|
||||
}
|
||||
|
||||
pub(crate) fn close(&mut self) {
|
||||
self.builder.close();
|
||||
self.builder.close_path();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -334,7 +305,7 @@ pub(crate) enum Filter {
|
|||
pub(crate) struct CanvasData<'a, B: Backend> {
|
||||
backend: B,
|
||||
drawtarget: B::DrawTarget,
|
||||
path_state: Option<PathState<B>>,
|
||||
path_state: Option<PathState>,
|
||||
state: CanvasPaintState<'a, B>,
|
||||
saved_states: Vec<CanvasPaintState<'a, B>>,
|
||||
compositor_api: CrossProcessCompositorApi,
|
||||
|
@ -746,10 +717,10 @@ impl<'a, B: Backend> CanvasData<'a, B> {
|
|||
|
||||
/// 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> {
|
||||
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(B::Path::new(), 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
|
||||
|
@ -757,7 +728,7 @@ impl<'a, B: Backend> CanvasData<'a, B> {
|
|||
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);
|
||||
path.transform(transform.cast());
|
||||
Some(path)
|
||||
},
|
||||
PathState::UserSpacePath(..) | PathState::DeviceSpacePath(..) => None,
|
||||
|
@ -772,7 +743,7 @@ impl<'a, B: Backend> CanvasData<'a, B> {
|
|||
PathState::DeviceSpacePath(ref mut builder) => {
|
||||
let inverse = self.drawtarget.get_transform().inverse()?;
|
||||
let mut path = builder.clone();
|
||||
path.transform(&inverse);
|
||||
path.transform(inverse.cast());
|
||||
Some(path)
|
||||
},
|
||||
PathState::UserSpacePath(..) => None,
|
||||
|
@ -809,16 +780,13 @@ impl<'a, B: Backend> CanvasData<'a, B> {
|
|||
)
|
||||
}
|
||||
|
||||
pub(crate) fn fill_path(&mut self, path_segments: &[PathSegment]) {
|
||||
pub(crate) fn fill_path(&mut self, path: &Path) {
|
||||
if self.state.fill_style.is_zero_size_gradient() {
|
||||
return; // Paint nothing if gradient size is zero.
|
||||
}
|
||||
|
||||
let mut path = B::Path::new();
|
||||
path.add_segments(path_segments);
|
||||
|
||||
self.drawtarget
|
||||
.fill(&path, &self.state.fill_style, &self.state.draw_options);
|
||||
.fill(path, &self.state.fill_style, &self.state.draw_options);
|
||||
}
|
||||
|
||||
pub(crate) fn stroke(&mut self) {
|
||||
|
@ -844,20 +812,17 @@ impl<'a, B: Backend> CanvasData<'a, B> {
|
|||
)
|
||||
}
|
||||
|
||||
pub(crate) fn stroke_path(&mut self, path_segments: &[PathSegment]) {
|
||||
pub(crate) fn stroke_path(&mut self, path: &Path) {
|
||||
if self.state.stroke_style.is_zero_size_gradient() {
|
||||
return; // Paint nothing if gradient size is zero.
|
||||
}
|
||||
|
||||
let mut path = B::Path::new();
|
||||
path.add_segments(path_segments);
|
||||
|
||||
self.maybe_bound_shape_with_pattern(
|
||||
self.state.stroke_style.clone(),
|
||||
&path.bounding_box(),
|
||||
|self_| {
|
||||
self_.drawtarget.stroke(
|
||||
&path,
|
||||
path,
|
||||
&self_.state.stroke_style,
|
||||
&self_.state.stroke_opts,
|
||||
&self_.state.draw_options,
|
||||
|
@ -874,17 +839,15 @@ impl<'a, B: Backend> CanvasData<'a, B> {
|
|||
self.drawtarget.push_clip(&path);
|
||||
}
|
||||
|
||||
pub(crate) fn clip_path(&mut self, path_segments: &[PathSegment]) {
|
||||
let mut path = B::Path::new();
|
||||
path.add_segments(path_segments);
|
||||
self.drawtarget.push_clip(&path);
|
||||
pub(crate) fn clip_path(&mut self, path: &Path) {
|
||||
self.drawtarget.push_clip(path);
|
||||
}
|
||||
|
||||
pub(crate) fn is_point_in_path(
|
||||
&mut self,
|
||||
x: f64,
|
||||
y: f64,
|
||||
_fill_rule: FillRule,
|
||||
fill_rule: FillRule,
|
||||
chan: IpcSender<bool>,
|
||||
) {
|
||||
self.ensure_path();
|
||||
|
@ -892,31 +855,15 @@ impl<'a, B: Backend> CanvasData<'a, B> {
|
|||
Some(PathState::UserSpacePath(path, transform)) => {
|
||||
let target_transform = self.drawtarget.get_transform();
|
||||
let path_transform = transform.as_ref().unwrap_or(&target_transform);
|
||||
path.contains_point(x, y, path_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();
|
||||
}
|
||||
|
||||
pub(crate) fn is_point_in_path_(
|
||||
&mut self,
|
||||
path_segments: &[PathSegment],
|
||||
x: f64,
|
||||
y: f64,
|
||||
_fill_rule: FillRule,
|
||||
chan: IpcSender<bool>,
|
||||
) {
|
||||
let path_transform = match self.path_state.as_ref() {
|
||||
Some(PathState::UserSpacePath(_, Some(transform))) => transform,
|
||||
Some(_) | None => &self.drawtarget.get_transform(),
|
||||
};
|
||||
let mut path = B::Path::new();
|
||||
path.add_segments(path_segments);
|
||||
let result = path.contains_point(x, y, path_transform);
|
||||
chan.send(result).unwrap();
|
||||
}
|
||||
|
||||
pub(crate) fn move_to(&mut self, point: &Point2D<f32>) {
|
||||
self.path_builder().move_to(point);
|
||||
}
|
||||
|
@ -925,9 +872,9 @@ impl<'a, B: Backend> CanvasData<'a, B> {
|
|||
self.path_builder().line_to(point);
|
||||
}
|
||||
|
||||
fn path_builder(&mut self) -> PathBuilderRef<B> {
|
||||
fn path_builder(&mut self) -> PathBuilderRef {
|
||||
if self.path_state.is_none() {
|
||||
self.path_state = Some(PathState::UserSpacePath(B::Path::new(), None));
|
||||
self.path_state = Some(PathState::UserSpacePath(Path::new(), None));
|
||||
}
|
||||
|
||||
// Rust is not pleased by returning a reference to a builder in some branches
|
||||
|
@ -938,7 +885,7 @@ impl<'a, B: Backend> CanvasData<'a, B> {
|
|||
PathState::DeviceSpacePath(_) => None,
|
||||
PathState::UserSpacePath(ref path, Some(ref transform)) => {
|
||||
let mut path = path.clone();
|
||||
path.transform(transform);
|
||||
path.transform(transform.cast());
|
||||
Some(PathState::DeviceSpacePath(path))
|
||||
},
|
||||
PathState::UserSpacePath(ref path, None) => {
|
||||
|
|
|
@ -156,7 +156,7 @@ impl<'a> CanvasPaintThread<'a> {
|
|||
},
|
||||
Canvas2dMsg::FillPath(style, path) => {
|
||||
self.canvas(canvas_id).set_fill_style(style);
|
||||
self.canvas(canvas_id).fill_path(&path[..]);
|
||||
self.canvas(canvas_id).fill_path(&path);
|
||||
},
|
||||
Canvas2dMsg::Stroke(style) => {
|
||||
self.canvas(canvas_id).set_stroke_style(style);
|
||||
|
@ -164,16 +164,13 @@ impl<'a> CanvasPaintThread<'a> {
|
|||
},
|
||||
Canvas2dMsg::StrokePath(style, path) => {
|
||||
self.canvas(canvas_id).set_stroke_style(style);
|
||||
self.canvas(canvas_id).stroke_path(&path[..]);
|
||||
self.canvas(canvas_id).stroke_path(&path);
|
||||
},
|
||||
Canvas2dMsg::Clip => self.canvas(canvas_id).clip(),
|
||||
Canvas2dMsg::ClipPath(path) => self.canvas(canvas_id).clip_path(&path[..]),
|
||||
Canvas2dMsg::ClipPath(path) => self.canvas(canvas_id).clip_path(&path),
|
||||
Canvas2dMsg::IsPointInCurrentPath(x, y, fill_rule, chan) => self
|
||||
.canvas(canvas_id)
|
||||
.is_point_in_path(x, y, fill_rule, chan),
|
||||
Canvas2dMsg::IsPointInPath(path, x, y, fill_rule, chan) => self
|
||||
.canvas(canvas_id)
|
||||
.is_point_in_path_(&path[..], x, y, fill_rule, chan),
|
||||
Canvas2dMsg::DrawImage(snapshot, dest_rect, source_rect, smoothing_enabled) => {
|
||||
self.canvas(canvas_id).draw_image(
|
||||
snapshot.to_owned(),
|
||||
|
@ -333,7 +330,7 @@ impl Canvas<'_> {
|
|||
}
|
||||
}
|
||||
|
||||
fn fill_path(&mut self, path: &[PathSegment]) {
|
||||
fn fill_path(&mut self, path: &Path) {
|
||||
match self {
|
||||
Canvas::Raqote(canvas_data) => canvas_data.fill_path(path),
|
||||
}
|
||||
|
@ -345,7 +342,7 @@ impl Canvas<'_> {
|
|||
}
|
||||
}
|
||||
|
||||
fn stroke_path(&mut self, path: &[PathSegment]) {
|
||||
fn stroke_path(&mut self, path: &Path) {
|
||||
match self {
|
||||
Canvas::Raqote(canvas_data) => canvas_data.stroke_path(path),
|
||||
}
|
||||
|
@ -363,21 +360,6 @@ impl Canvas<'_> {
|
|||
}
|
||||
}
|
||||
|
||||
fn is_point_in_path_(
|
||||
&mut self,
|
||||
path: &[PathSegment],
|
||||
x: f64,
|
||||
y: f64,
|
||||
fill_rule: FillRule,
|
||||
chan: IpcSender<bool>,
|
||||
) {
|
||||
match self {
|
||||
Canvas::Raqote(canvas_data) => {
|
||||
canvas_data.is_point_in_path_(path, x, y, fill_rule, chan)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn clear_rect(&mut self, rect: &Rect<f32>) {
|
||||
match self {
|
||||
Canvas::Raqote(canvas_data) => canvas_data.clear_rect(rect),
|
||||
|
@ -582,7 +564,7 @@ impl Canvas<'_> {
|
|||
}
|
||||
}
|
||||
|
||||
fn clip_path(&mut self, path: &[PathSegment]) {
|
||||
fn clip_path(&mut self, path: &Path) {
|
||||
match self {
|
||||
Canvas::Raqote(canvas_data) => canvas_data.clip_path(path),
|
||||
}
|
||||
|
|
|
@ -8,20 +8,19 @@ use std::collections::HashMap;
|
|||
use canvas_traits::canvas::*;
|
||||
use compositing_traits::SerializableImageData;
|
||||
use cssparser::color::clamp_unit_f32;
|
||||
use euclid::default::{Box2D, Point2D, Rect, Size2D, Transform2D, Vector2D};
|
||||
use euclid::default::{Point2D, Rect, Size2D, Transform2D, Vector2D};
|
||||
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::PathOp;
|
||||
use raqote::PathBuilder;
|
||||
use style::color::AbsoluteColor;
|
||||
use webrender_api::{ImageDescriptor, ImageDescriptorFlags, ImageFormat};
|
||||
|
||||
use crate::backend::{
|
||||
Backend, DrawOptionsHelpers, GenericDrawTarget, GenericPath, PatternHelpers,
|
||||
StrokeOptionsHelpers,
|
||||
Backend, DrawOptionsHelpers, GenericDrawTarget, PatternHelpers, StrokeOptionsHelpers,
|
||||
};
|
||||
use crate::canvas_data::{CanvasPaintState, Filter, TextRun};
|
||||
|
||||
|
@ -44,7 +43,6 @@ impl Backend for RaqoteBackend {
|
|||
type CompositionOp = raqote::BlendMode;
|
||||
type DrawTarget = raqote::DrawTarget;
|
||||
type SourceSurface = Vec<u8>; // TODO: See if we can avoid the alloc (probably?)
|
||||
type Path = Path;
|
||||
type GradientStop = raqote::GradientStop;
|
||||
type GradientStops = Vec<raqote::GradientStop>;
|
||||
|
||||
|
@ -346,7 +344,8 @@ fn create_gradient_stops(gradient_stops: Vec<CanvasGradientStop>) -> Vec<raqote:
|
|||
|
||||
impl GenericDrawTarget<RaqoteBackend> for raqote::DrawTarget {
|
||||
fn clear_rect(&mut self, rect: &Rect<f32>) {
|
||||
let mut pb = raqote::PathBuilder::new();
|
||||
let rect = rect.cast();
|
||||
let mut pb = canvas_traits::canvas::Path::new();
|
||||
pb.rect(
|
||||
rect.origin.x,
|
||||
rect.origin.y,
|
||||
|
@ -356,7 +355,7 @@ impl GenericDrawTarget<RaqoteBackend> for raqote::DrawTarget {
|
|||
let mut options = raqote::DrawOptions::new();
|
||||
options.blend_mode = raqote::BlendMode::Clear;
|
||||
let pattern = Pattern::Color(0, 0, 0, 0);
|
||||
<Self as GenericDrawTarget<RaqoteBackend>>::fill(self, &pb.into(), &pattern, &options);
|
||||
<Self as GenericDrawTarget<RaqoteBackend>>::fill(self, &pb, &pattern, &options);
|
||||
}
|
||||
#[allow(unsafe_code)]
|
||||
fn copy_surface(
|
||||
|
@ -424,15 +423,15 @@ impl GenericDrawTarget<RaqoteBackend> for raqote::DrawTarget {
|
|||
transform,
|
||||
));
|
||||
|
||||
let mut pb = raqote::PathBuilder::new();
|
||||
let mut pb = canvas_traits::canvas::Path::new();
|
||||
pb.rect(
|
||||
dest.origin.x as f32,
|
||||
dest.origin.y as f32,
|
||||
dest.size.width as f32,
|
||||
dest.size.height as f32,
|
||||
dest.origin.x,
|
||||
dest.origin.y,
|
||||
dest.size.width,
|
||||
dest.size.height,
|
||||
);
|
||||
|
||||
<Self as GenericDrawTarget<RaqoteBackend>>::fill(self, &pb.into(), &pattern, draw_options);
|
||||
<Self as GenericDrawTarget<RaqoteBackend>>::fill(self, &pb, &pattern, draw_options);
|
||||
}
|
||||
fn draw_surface_with_shadow(
|
||||
&self,
|
||||
|
@ -447,11 +446,11 @@ impl GenericDrawTarget<RaqoteBackend> for raqote::DrawTarget {
|
|||
}
|
||||
fn fill(
|
||||
&mut self,
|
||||
path: &<RaqoteBackend as Backend>::Path,
|
||||
path: &canvas_traits::canvas::Path,
|
||||
pattern: &Pattern,
|
||||
draw_options: &<RaqoteBackend as Backend>::DrawOptions,
|
||||
) {
|
||||
let path = path.into();
|
||||
let path = to_path(path);
|
||||
match draw_options.blend_mode {
|
||||
raqote::BlendMode::Src => {
|
||||
self.clear(raqote::SolidSource::from_unpremultiplied_argb(0, 0, 0, 0));
|
||||
|
@ -546,7 +545,8 @@ impl GenericDrawTarget<RaqoteBackend> for raqote::DrawTarget {
|
|||
pattern: &<RaqoteBackend as Backend>::Pattern<'_>,
|
||||
draw_options: &<RaqoteBackend as Backend>::DrawOptions,
|
||||
) {
|
||||
let mut pb = raqote::PathBuilder::new();
|
||||
let rect = rect.cast();
|
||||
let mut pb = canvas_traits::canvas::Path::new();
|
||||
pb.rect(
|
||||
rect.origin.x,
|
||||
rect.origin.y,
|
||||
|
@ -554,7 +554,7 @@ impl GenericDrawTarget<RaqoteBackend> for raqote::DrawTarget {
|
|||
rect.size.height,
|
||||
);
|
||||
|
||||
<Self as GenericDrawTarget<RaqoteBackend>>::fill(self, &pb.into(), pattern, draw_options);
|
||||
<Self as GenericDrawTarget<RaqoteBackend>>::fill(self, &pb, pattern, draw_options);
|
||||
}
|
||||
fn get_size(&self) -> Size2D<i32> {
|
||||
Size2D::new(self.width(), self.height())
|
||||
|
@ -565,8 +565,8 @@ impl GenericDrawTarget<RaqoteBackend> for raqote::DrawTarget {
|
|||
fn pop_clip(&mut self) {
|
||||
self.pop_clip();
|
||||
}
|
||||
fn push_clip(&mut self, path: &<RaqoteBackend as Backend>::Path) {
|
||||
self.push_clip(&path.into());
|
||||
fn push_clip(&mut self, path: &canvas_traits::canvas::Path) {
|
||||
self.push_clip(&to_path(path));
|
||||
}
|
||||
fn push_clip_rect(&mut self, rect: &Rect<i32>) {
|
||||
self.push_clip_rect(rect.to_box2d());
|
||||
|
@ -579,12 +579,17 @@ impl GenericDrawTarget<RaqoteBackend> for raqote::DrawTarget {
|
|||
}
|
||||
fn stroke(
|
||||
&mut self,
|
||||
path: &<RaqoteBackend as Backend>::Path,
|
||||
path: &canvas_traits::canvas::Path,
|
||||
pattern: &Pattern,
|
||||
stroke_options: &<RaqoteBackend as Backend>::StrokeOptions,
|
||||
draw_options: &<RaqoteBackend as Backend>::DrawOptions,
|
||||
) {
|
||||
self.stroke(&path.into(), &source(pattern), stroke_options, draw_options);
|
||||
self.stroke(
|
||||
&to_path(path),
|
||||
&source(pattern),
|
||||
stroke_options,
|
||||
draw_options,
|
||||
);
|
||||
}
|
||||
fn stroke_rect(
|
||||
&mut self,
|
||||
|
@ -665,99 +670,6 @@ impl Clone for Path {
|
|||
}
|
||||
}
|
||||
|
||||
impl GenericPath<RaqoteBackend> for Path {
|
||||
fn contains_point(&self, x: f64, y: f64, path_transform: &Transform2D<f32>) -> bool {
|
||||
let path: raqote::Path = self.into();
|
||||
path.transform(path_transform)
|
||||
.contains_point(0.01, x as f32, y as f32)
|
||||
}
|
||||
|
||||
fn new() -> Self {
|
||||
Self(Cell::new(raqote::PathBuilder::new()))
|
||||
}
|
||||
|
||||
fn bezier_curve_to(
|
||||
&mut self,
|
||||
control_point1: &Point2D<f32>,
|
||||
control_point2: &Point2D<f32>,
|
||||
control_point3: &Point2D<f32>,
|
||||
) {
|
||||
self.0.get_mut().cubic_to(
|
||||
control_point1.x,
|
||||
control_point1.y,
|
||||
control_point2.x,
|
||||
control_point2.y,
|
||||
control_point3.x,
|
||||
control_point3.y,
|
||||
);
|
||||
}
|
||||
|
||||
fn close(&mut self) {
|
||||
self.0.get_mut().close();
|
||||
}
|
||||
|
||||
fn get_current_point(&mut self) -> Option<Point2D<f32>> {
|
||||
let path: raqote::Path = (&*self).into();
|
||||
|
||||
path.ops.iter().last().and_then(|op| match op {
|
||||
PathOp::MoveTo(point) | PathOp::LineTo(point) => Some(Point2D::new(point.x, point.y)),
|
||||
PathOp::CubicTo(_, _, point) => Some(Point2D::new(point.x, point.y)),
|
||||
PathOp::QuadTo(_, point) => Some(Point2D::new(point.x, point.y)),
|
||||
PathOp::Close => path.ops.first().and_then(get_first_point),
|
||||
})
|
||||
}
|
||||
|
||||
fn line_to(&mut self, point: Point2D<f32>) {
|
||||
self.0.get_mut().line_to(point.x, point.y);
|
||||
}
|
||||
|
||||
fn move_to(&mut self, point: Point2D<f32>) {
|
||||
self.0.get_mut().move_to(point.x, point.y);
|
||||
}
|
||||
|
||||
fn quadratic_curve_to(&mut self, control_point: &Point2D<f32>, end_point: &Point2D<f32>) {
|
||||
self.0
|
||||
.get_mut()
|
||||
.quad_to(control_point.x, control_point.y, end_point.x, end_point.y);
|
||||
}
|
||||
|
||||
fn transform(&mut self, transform: &Transform2D<f32>) {
|
||||
let path: raqote::Path = (&*self).into();
|
||||
self.0.set(path.transform(transform).into());
|
||||
}
|
||||
|
||||
fn bounding_box(&self) -> Rect<f64> {
|
||||
let path: raqote::Path = self.into();
|
||||
let mut points = vec![];
|
||||
for op in path.ops {
|
||||
match op {
|
||||
PathOp::MoveTo(p) => points.push(p),
|
||||
PathOp::LineTo(p) => points.push(p),
|
||||
PathOp::QuadTo(p1, p2) => {
|
||||
points.push(p1);
|
||||
points.push(p2);
|
||||
},
|
||||
PathOp::CubicTo(p1, p2, p3) => {
|
||||
points.push(p1);
|
||||
points.push(p2);
|
||||
points.push(p3);
|
||||
},
|
||||
PathOp::Close => {},
|
||||
}
|
||||
}
|
||||
Box2D::from_points(points).to_rect().cast()
|
||||
}
|
||||
}
|
||||
|
||||
fn get_first_point(op: &PathOp) -> Option<euclid::Point2D<f32, euclid::UnknownUnit>> {
|
||||
match op {
|
||||
PathOp::MoveTo(point) | PathOp::LineTo(point) => Some(Point2D::new(point.x, point.y)),
|
||||
PathOp::CubicTo(point, _, _) => Some(Point2D::new(point.x, point.y)),
|
||||
PathOp::QuadTo(point, _) => Some(Point2D::new(point.x, point.y)),
|
||||
PathOp::Close => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub trait ToRaqoteStyle {
|
||||
type Target;
|
||||
|
||||
|
@ -934,3 +846,26 @@ impl ToRaqoteStyle for CompositionStyle {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn to_path(path: &canvas_traits::canvas::Path) -> raqote::Path {
|
||||
let mut pb = PathBuilder::new();
|
||||
for cmd in &path.0 {
|
||||
match cmd {
|
||||
kurbo::PathEl::MoveTo(kurbo::Point { x, y }) => pb.move_to(x as f32, y as f32),
|
||||
kurbo::PathEl::LineTo(kurbo::Point { x, y }) => pb.line_to(x as f32, y as f32),
|
||||
kurbo::PathEl::QuadTo(cp, p) => {
|
||||
pb.quad_to(cp.x as f32, cp.y as f32, p.x as f32, p.y as f32)
|
||||
},
|
||||
kurbo::PathEl::CurveTo(cp1, cp2, p) => pb.cubic_to(
|
||||
cp1.x as f32,
|
||||
cp1.y as f32,
|
||||
cp2.x as f32,
|
||||
cp2.y as f32,
|
||||
p.x as f32,
|
||||
p.y as f32,
|
||||
),
|
||||
kurbo::PathEl::ClosePath => pb.close(),
|
||||
}
|
||||
}
|
||||
pb.finish()
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue