mirror of
https://github.com/servo/servo.git
synced 2025-07-22 23:03:42 +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
4
Cargo.lock
generated
4
Cargo.lock
generated
|
@ -1087,6 +1087,7 @@ dependencies = [
|
||||||
"font-kit",
|
"font-kit",
|
||||||
"fonts",
|
"fonts",
|
||||||
"ipc-channel",
|
"ipc-channel",
|
||||||
|
"kurbo",
|
||||||
"log",
|
"log",
|
||||||
"lyon_geom",
|
"lyon_geom",
|
||||||
"net_traits",
|
"net_traits",
|
||||||
|
@ -1108,6 +1109,7 @@ dependencies = [
|
||||||
"euclid",
|
"euclid",
|
||||||
"glow",
|
"glow",
|
||||||
"ipc-channel",
|
"ipc-channel",
|
||||||
|
"kurbo",
|
||||||
"malloc_size_of_derive",
|
"malloc_size_of_derive",
|
||||||
"pixels",
|
"pixels",
|
||||||
"serde",
|
"serde",
|
||||||
|
@ -4681,6 +4683,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "1077d333efea6170d9ccb96d3c3026f300ca0773da4938cc4c811daa6df68b0c"
|
checksum = "1077d333efea6170d9ccb96d3c3026f300ca0773da4938cc4c811daa6df68b0c"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"arrayvec",
|
"arrayvec",
|
||||||
|
"serde",
|
||||||
"smallvec",
|
"smallvec",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -7089,6 +7092,7 @@ dependencies = [
|
||||||
"itertools 0.14.0",
|
"itertools 0.14.0",
|
||||||
"jstraceable_derive",
|
"jstraceable_derive",
|
||||||
"keyboard-types",
|
"keyboard-types",
|
||||||
|
"kurbo",
|
||||||
"layout_api",
|
"layout_api",
|
||||||
"libc",
|
"libc",
|
||||||
"log",
|
"log",
|
||||||
|
|
|
@ -87,6 +87,7 @@ ipc-channel = "0.20"
|
||||||
itertools = "0.14"
|
itertools = "0.14"
|
||||||
js = { package = "mozjs", git = "https://github.com/servo/mozjs" }
|
js = { package = "mozjs", git = "https://github.com/servo/mozjs" }
|
||||||
keyboard-types = "0.7"
|
keyboard-types = "0.7"
|
||||||
|
kurbo = "0.11"
|
||||||
libc = "0.2"
|
libc = "0.2"
|
||||||
log = "0.4"
|
log = "0.4"
|
||||||
mach2 = "0.4"
|
mach2 = "0.4"
|
||||||
|
|
|
@ -21,6 +21,7 @@ euclid = { workspace = true }
|
||||||
font-kit = "0.14"
|
font-kit = "0.14"
|
||||||
fonts = { path = "../fonts" }
|
fonts = { path = "../fonts" }
|
||||||
ipc-channel = { workspace = true }
|
ipc-channel = { workspace = true }
|
||||||
|
kurbo = { workspace = true }
|
||||||
log = { workspace = true }
|
log = { workspace = true }
|
||||||
lyon_geom = "1.0.4"
|
lyon_geom = "1.0.4"
|
||||||
net_traits = { workspace = true }
|
net_traits = { workspace = true }
|
||||||
|
|
|
@ -3,17 +3,15 @@
|
||||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
use canvas_traits::canvas::{
|
use canvas_traits::canvas::{
|
||||||
CompositionOrBlending, FillOrStrokeStyle, LineCapStyle, LineJoinStyle, PathSegment,
|
CompositionOrBlending, FillOrStrokeStyle, LineCapStyle, LineJoinStyle, Path,
|
||||||
};
|
};
|
||||||
use compositing_traits::SerializableImageData;
|
use compositing_traits::SerializableImageData;
|
||||||
use euclid::Angle;
|
|
||||||
use euclid::default::{Point2D, Rect, Size2D, Transform2D, Vector2D};
|
use euclid::default::{Point2D, Rect, Size2D, Transform2D, Vector2D};
|
||||||
use lyon_geom::Arc;
|
|
||||||
use pixels::Snapshot;
|
use pixels::Snapshot;
|
||||||
use style::color::AbsoluteColor;
|
use style::color::AbsoluteColor;
|
||||||
use webrender_api::ImageDescriptor;
|
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 {
|
pub(crate) trait Backend: Clone + Sized {
|
||||||
type Pattern<'a>: PatternHelpers + Clone;
|
type Pattern<'a>: PatternHelpers + Clone;
|
||||||
|
@ -22,7 +20,6 @@ pub(crate) trait Backend: Clone + Sized {
|
||||||
type DrawOptions: DrawOptionsHelpers + Clone;
|
type DrawOptions: DrawOptionsHelpers + Clone;
|
||||||
type CompositionOp;
|
type CompositionOp;
|
||||||
type DrawTarget: GenericDrawTarget<Self>;
|
type DrawTarget: GenericDrawTarget<Self>;
|
||||||
type Path: GenericPath<Self> + Clone;
|
|
||||||
type SourceSurface;
|
type SourceSurface;
|
||||||
type GradientStop;
|
type GradientStop;
|
||||||
type GradientStops;
|
type GradientStops;
|
||||||
|
@ -80,7 +77,7 @@ pub(crate) trait GenericDrawTarget<B: Backend> {
|
||||||
sigma: f32,
|
sigma: f32,
|
||||||
operator: B::CompositionOp,
|
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(
|
fn fill_text(
|
||||||
&mut self,
|
&mut self,
|
||||||
text_runs: Vec<TextRun>,
|
text_runs: Vec<TextRun>,
|
||||||
|
@ -97,12 +94,12 @@ pub(crate) trait GenericDrawTarget<B: Backend> {
|
||||||
fn get_size(&self) -> Size2D<i32>;
|
fn get_size(&self) -> Size2D<i32>;
|
||||||
fn get_transform(&self) -> Transform2D<f32>;
|
fn get_transform(&self) -> Transform2D<f32>;
|
||||||
fn pop_clip(&mut self);
|
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 push_clip_rect(&mut self, rect: &Rect<i32>);
|
||||||
fn set_transform(&mut self, matrix: &Transform2D<f32>);
|
fn set_transform(&mut self, matrix: &Transform2D<f32>);
|
||||||
fn stroke(
|
fn stroke(
|
||||||
&mut self,
|
&mut self,
|
||||||
path: &B::Path,
|
path: &Path,
|
||||||
pattern: &B::Pattern<'_>,
|
pattern: &B::Pattern<'_>,
|
||||||
stroke_options: &B::StrokeOptions,
|
stroke_options: &B::StrokeOptions,
|
||||||
draw_options: &B::DrawOptions,
|
draw_options: &B::DrawOptions,
|
||||||
|
@ -119,200 +116,6 @@ pub(crate) trait GenericDrawTarget<B: Backend> {
|
||||||
fn snapshot(&self) -> Snapshot;
|
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 {
|
pub(crate) trait PatternHelpers {
|
||||||
fn is_zero_size_gradient(&self) -> bool;
|
fn is_zero_size_gradient(&self) -> bool;
|
||||||
fn x_bound(&self) -> Option<u32>;
|
fn x_bound(&self) -> Option<u32>;
|
||||||
|
|
|
@ -26,7 +26,7 @@ use unicode_script::Script;
|
||||||
use webrender_api::ImageKey;
|
use webrender_api::ImageKey;
|
||||||
|
|
||||||
use crate::backend::{
|
use crate::backend::{
|
||||||
Backend, DrawOptionsHelpers as _, GenericDrawTarget as _, GenericPath, PatternHelpers,
|
Backend, DrawOptionsHelpers as _, GenericDrawTarget as _, PatternHelpers,
|
||||||
StrokeOptionsHelpers as _,
|
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
|
/// draw the path, we convert it back to userspace and draw it
|
||||||
/// with the correct transform applied.
|
/// with the correct transform applied.
|
||||||
/// TODO: De-abstract now that Azure is removed?
|
/// 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
|
/// Path in user-space. If a transform has been applied but
|
||||||
/// but no further path operations have occurred, it is stored
|
/// but no further path operations have occurred, it is stored
|
||||||
/// in the optional field.
|
/// in the optional field.
|
||||||
UserSpacePath(B::Path, Option<Transform2D<f32>>),
|
UserSpacePath(Path, Option<Transform2D<f32>>),
|
||||||
/// Path in device-space.
|
/// Path in device-space.
|
||||||
DeviceSpacePath(B::Path),
|
DeviceSpacePath(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.
|
||||||
pub(crate) struct PathBuilderRef<'a, B: Backend> {
|
pub(crate) struct PathBuilderRef<'a> {
|
||||||
pub(crate) builder: &'a mut B::Path,
|
pub(crate) builder: &'a mut Path,
|
||||||
pub(crate) transform: Transform2D<f32>,
|
pub(crate) transform: Transform2D<f32>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<B: Backend> PathBuilderRef<'_, B> {
|
impl PathBuilderRef<'_> {
|
||||||
/// <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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <https://html.spec.whatwg.org/multipage#dom-context-2d-lineto>
|
/// <https://html.spec.whatwg.org/multipage#dom-context-2d-lineto>
|
||||||
pub(crate) fn line_to(&mut self, pt: &Point2D<f32>) {
|
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).
|
let pt = self.transform.transform_point(*pt).cast();
|
||||||
self.ensure_there_is_a_subpath(pt);
|
self.builder.line_to(pt.x, pt.y);
|
||||||
|
|
||||||
let pt = self.transform.transform_point(*pt);
|
|
||||||
self.builder.line_to(pt);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn move_to(&mut self, pt: &Point2D<f32>) {
|
pub(crate) fn move_to(&mut self, pt: &Point2D<f32>) {
|
||||||
let pt = self.transform.transform_point(*pt);
|
let pt = self.transform.transform_point(*pt).cast();
|
||||||
self.builder.move_to(pt);
|
self.builder.move_to(pt.x, pt.y);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn rect(&mut self, rect: &Rect<f32>) {
|
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>
|
/// <https://html.spec.whatwg.org/multipage#dom-context-2d-quadraticcurveto>
|
||||||
pub(crate) fn quadratic_curve_to(&mut self, cp: &Point2D<f32>, endpoint: &Point2D<f32>) {
|
pub(crate) fn quadratic_curve_to(&mut self, cp: &Point2D<f32>, endpoint: &Point2D<f32>) {
|
||||||
// 2. Ensure there is a subpath for (cpx, cpy).
|
let cp = self.transform.transform_point(*cp).cast();
|
||||||
self.ensure_there_is_a_subpath(cp);
|
let endpoint = self.transform.transform_point(*endpoint).cast();
|
||||||
|
self.builder
|
||||||
self.builder.quadratic_curve_to(
|
.quadratic_curve_to(cp.x, cp.y, endpoint.x, endpoint.y)
|
||||||
&self.transform.transform_point(*cp),
|
|
||||||
&self.transform.transform_point(*endpoint),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <https://html.spec.whatwg.org/multipage#dom-context-2d-beziercurveto>
|
/// <https://html.spec.whatwg.org/multipage#dom-context-2d-beziercurveto>
|
||||||
|
@ -117,14 +104,11 @@ impl<B: Backend> PathBuilderRef<'_, B> {
|
||||||
cp2: &Point2D<f32>,
|
cp2: &Point2D<f32>,
|
||||||
endpoint: &Point2D<f32>,
|
endpoint: &Point2D<f32>,
|
||||||
) {
|
) {
|
||||||
// 2. Ensure there is a subpath for (cp1x, cp1y).
|
let cp1 = self.transform.transform_point(*cp1).cast();
|
||||||
self.ensure_there_is_a_subpath(cp1);
|
let cp2 = self.transform.transform_point(*cp2).cast();
|
||||||
|
let endpoint = self.transform.transform_point(*endpoint).cast();
|
||||||
self.builder.bezier_curve_to(
|
self.builder
|
||||||
&self.transform.transform_point(*cp1),
|
.bezier_curve_to(cp1.x, cp1.y, cp2.x, cp2.y, endpoint.x, endpoint.y)
|
||||||
&self.transform.transform_point(*cp2),
|
|
||||||
&self.transform.transform_point(*endpoint),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn arc(
|
pub(crate) fn arc(
|
||||||
|
@ -135,17 +119,23 @@ impl<B: Backend> PathBuilderRef<'_, B> {
|
||||||
end_angle: f32,
|
end_angle: f32,
|
||||||
ccw: bool,
|
ccw: bool,
|
||||||
) {
|
) {
|
||||||
let center = self.transform.transform_point(*center);
|
let center = self.transform.transform_point(*center).cast();
|
||||||
self.builder
|
let _ = self.builder.arc(
|
||||||
.arc(center, radius, start_angle, end_angle, ccw);
|
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>
|
/// <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) {
|
pub(crate) fn arc_to(&mut self, cp1: &Point2D<f32>, cp2: &Point2D<f32>, radius: f32) {
|
||||||
let cp0 = if let (Some(inverse), Some(point)) =
|
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 {
|
} else {
|
||||||
*cp1
|
*cp1
|
||||||
};
|
};
|
||||||
|
@ -218,40 +208,21 @@ impl<B: Backend> PathBuilderRef<'_, B> {
|
||||||
end_angle: f32,
|
end_angle: f32,
|
||||||
ccw: bool,
|
ccw: bool,
|
||||||
) {
|
) {
|
||||||
let center = self.transform.transform_point(*center);
|
let center = self.transform.transform_point(*center).cast();
|
||||||
self.builder.ellipse(
|
let _ = self.builder.ellipse(
|
||||||
center,
|
center.x,
|
||||||
radius_x,
|
center.y,
|
||||||
radius_y,
|
radius_x as f64,
|
||||||
rotation_angle,
|
radius_y as f64,
|
||||||
start_angle,
|
rotation_angle as f64,
|
||||||
end_angle,
|
start_angle as f64,
|
||||||
|
end_angle as f64,
|
||||||
ccw,
|
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) {
|
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> {
|
pub(crate) struct CanvasData<'a, B: Backend> {
|
||||||
backend: B,
|
backend: B,
|
||||||
drawtarget: B::DrawTarget,
|
drawtarget: B::DrawTarget,
|
||||||
path_state: Option<PathState<B>>,
|
path_state: Option<PathState>,
|
||||||
state: CanvasPaintState<'a, B>,
|
state: CanvasPaintState<'a, B>,
|
||||||
saved_states: Vec<CanvasPaintState<'a, B>>,
|
saved_states: Vec<CanvasPaintState<'a, B>>,
|
||||||
compositor_api: CrossProcessCompositorApi,
|
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
|
/// Turn the [`Self::path_state`] into a user-space path, returning `None` if the
|
||||||
/// path transformation matrix is uninvertible.
|
/// 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 there's no record of any path yet, create a new path in user-space.
|
||||||
if self.path_state.is_none() {
|
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
|
// 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() {
|
let new_state = match *self.path_state.as_ref().unwrap() {
|
||||||
PathState::UserSpacePath(ref path, Some(ref transform)) => {
|
PathState::UserSpacePath(ref path, Some(ref transform)) => {
|
||||||
let mut path = path.clone();
|
let mut path = path.clone();
|
||||||
path.transform(transform);
|
path.transform(transform.cast());
|
||||||
Some(path)
|
Some(path)
|
||||||
},
|
},
|
||||||
PathState::UserSpacePath(..) | PathState::DeviceSpacePath(..) => None,
|
PathState::UserSpacePath(..) | PathState::DeviceSpacePath(..) => None,
|
||||||
|
@ -772,7 +743,7 @@ impl<'a, B: Backend> CanvasData<'a, B> {
|
||||||
PathState::DeviceSpacePath(ref mut builder) => {
|
PathState::DeviceSpacePath(ref mut builder) => {
|
||||||
let inverse = self.drawtarget.get_transform().inverse()?;
|
let inverse = self.drawtarget.get_transform().inverse()?;
|
||||||
let mut path = builder.clone();
|
let mut path = builder.clone();
|
||||||
path.transform(&inverse);
|
path.transform(inverse.cast());
|
||||||
Some(path)
|
Some(path)
|
||||||
},
|
},
|
||||||
PathState::UserSpacePath(..) => None,
|
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() {
|
if self.state.fill_style.is_zero_size_gradient() {
|
||||||
return; // Paint nothing if gradient size is zero.
|
return; // Paint nothing if gradient size is zero.
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut path = B::Path::new();
|
|
||||||
path.add_segments(path_segments);
|
|
||||||
|
|
||||||
self.drawtarget
|
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) {
|
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() {
|
if self.state.stroke_style.is_zero_size_gradient() {
|
||||||
return; // Paint nothing if gradient size is zero.
|
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.maybe_bound_shape_with_pattern(
|
||||||
self.state.stroke_style.clone(),
|
self.state.stroke_style.clone(),
|
||||||
&path.bounding_box(),
|
&path.bounding_box(),
|
||||||
|self_| {
|
|self_| {
|
||||||
self_.drawtarget.stroke(
|
self_.drawtarget.stroke(
|
||||||
&path,
|
path,
|
||||||
&self_.state.stroke_style,
|
&self_.state.stroke_style,
|
||||||
&self_.state.stroke_opts,
|
&self_.state.stroke_opts,
|
||||||
&self_.state.draw_options,
|
&self_.state.draw_options,
|
||||||
|
@ -874,17 +839,15 @@ impl<'a, B: Backend> CanvasData<'a, B> {
|
||||||
self.drawtarget.push_clip(&path);
|
self.drawtarget.push_clip(&path);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn clip_path(&mut self, path_segments: &[PathSegment]) {
|
pub(crate) fn clip_path(&mut self, path: &Path) {
|
||||||
let mut path = B::Path::new();
|
self.drawtarget.push_clip(path);
|
||||||
path.add_segments(path_segments);
|
|
||||||
self.drawtarget.push_clip(&path);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn is_point_in_path(
|
pub(crate) fn is_point_in_path(
|
||||||
&mut self,
|
&mut self,
|
||||||
x: f64,
|
x: f64,
|
||||||
y: f64,
|
y: f64,
|
||||||
_fill_rule: FillRule,
|
fill_rule: FillRule,
|
||||||
chan: IpcSender<bool>,
|
chan: IpcSender<bool>,
|
||||||
) {
|
) {
|
||||||
self.ensure_path();
|
self.ensure_path();
|
||||||
|
@ -892,31 +855,15 @@ impl<'a, B: Backend> CanvasData<'a, B> {
|
||||||
Some(PathState::UserSpacePath(path, transform)) => {
|
Some(PathState::UserSpacePath(path, transform)) => {
|
||||||
let target_transform = self.drawtarget.get_transform();
|
let target_transform = self.drawtarget.get_transform();
|
||||||
let path_transform = transform.as_ref().unwrap_or(&target_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,
|
Some(_) | None => false,
|
||||||
};
|
};
|
||||||
chan.send(result).unwrap();
|
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>) {
|
pub(crate) fn move_to(&mut self, point: &Point2D<f32>) {
|
||||||
self.path_builder().move_to(point);
|
self.path_builder().move_to(point);
|
||||||
}
|
}
|
||||||
|
@ -925,9 +872,9 @@ impl<'a, B: Backend> CanvasData<'a, B> {
|
||||||
self.path_builder().line_to(point);
|
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() {
|
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
|
// 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::DeviceSpacePath(_) => None,
|
||||||
PathState::UserSpacePath(ref path, Some(ref transform)) => {
|
PathState::UserSpacePath(ref path, Some(ref transform)) => {
|
||||||
let mut path = path.clone();
|
let mut path = path.clone();
|
||||||
path.transform(transform);
|
path.transform(transform.cast());
|
||||||
Some(PathState::DeviceSpacePath(path))
|
Some(PathState::DeviceSpacePath(path))
|
||||||
},
|
},
|
||||||
PathState::UserSpacePath(ref path, None) => {
|
PathState::UserSpacePath(ref path, None) => {
|
||||||
|
|
|
@ -156,7 +156,7 @@ impl<'a> CanvasPaintThread<'a> {
|
||||||
},
|
},
|
||||||
Canvas2dMsg::FillPath(style, path) => {
|
Canvas2dMsg::FillPath(style, path) => {
|
||||||
self.canvas(canvas_id).set_fill_style(style);
|
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) => {
|
Canvas2dMsg::Stroke(style) => {
|
||||||
self.canvas(canvas_id).set_stroke_style(style);
|
self.canvas(canvas_id).set_stroke_style(style);
|
||||||
|
@ -164,16 +164,13 @@ impl<'a> CanvasPaintThread<'a> {
|
||||||
},
|
},
|
||||||
Canvas2dMsg::StrokePath(style, path) => {
|
Canvas2dMsg::StrokePath(style, path) => {
|
||||||
self.canvas(canvas_id).set_stroke_style(style);
|
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::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
|
Canvas2dMsg::IsPointInCurrentPath(x, y, fill_rule, chan) => self
|
||||||
.canvas(canvas_id)
|
.canvas(canvas_id)
|
||||||
.is_point_in_path(x, y, fill_rule, chan),
|
.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) => {
|
Canvas2dMsg::DrawImage(snapshot, dest_rect, source_rect, smoothing_enabled) => {
|
||||||
self.canvas(canvas_id).draw_image(
|
self.canvas(canvas_id).draw_image(
|
||||||
snapshot.to_owned(),
|
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 {
|
match self {
|
||||||
Canvas::Raqote(canvas_data) => canvas_data.fill_path(path),
|
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 {
|
match self {
|
||||||
Canvas::Raqote(canvas_data) => canvas_data.stroke_path(path),
|
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>) {
|
fn clear_rect(&mut self, rect: &Rect<f32>) {
|
||||||
match self {
|
match self {
|
||||||
Canvas::Raqote(canvas_data) => canvas_data.clear_rect(rect),
|
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 {
|
match self {
|
||||||
Canvas::Raqote(canvas_data) => canvas_data.clip_path(path),
|
Canvas::Raqote(canvas_data) => canvas_data.clip_path(path),
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,20 +8,19 @@ use std::collections::HashMap;
|
||||||
use canvas_traits::canvas::*;
|
use canvas_traits::canvas::*;
|
||||||
use compositing_traits::SerializableImageData;
|
use compositing_traits::SerializableImageData;
|
||||||
use cssparser::color::clamp_unit_f32;
|
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 font_kit::font::Font;
|
||||||
use fonts::{ByteIndex, FontIdentifier, FontTemplateRefMethods};
|
use fonts::{ByteIndex, FontIdentifier, FontTemplateRefMethods};
|
||||||
use ipc_channel::ipc::IpcSharedMemory;
|
use ipc_channel::ipc::IpcSharedMemory;
|
||||||
use log::warn;
|
use log::warn;
|
||||||
use pixels::{Snapshot, SnapshotAlphaMode, SnapshotPixelFormat};
|
use pixels::{Snapshot, SnapshotAlphaMode, SnapshotPixelFormat};
|
||||||
use range::Range;
|
use range::Range;
|
||||||
use raqote::PathOp;
|
use raqote::PathBuilder;
|
||||||
use style::color::AbsoluteColor;
|
use style::color::AbsoluteColor;
|
||||||
use webrender_api::{ImageDescriptor, ImageDescriptorFlags, ImageFormat};
|
use webrender_api::{ImageDescriptor, ImageDescriptorFlags, ImageFormat};
|
||||||
|
|
||||||
use crate::backend::{
|
use crate::backend::{
|
||||||
Backend, DrawOptionsHelpers, GenericDrawTarget, GenericPath, PatternHelpers,
|
Backend, DrawOptionsHelpers, GenericDrawTarget, PatternHelpers, StrokeOptionsHelpers,
|
||||||
StrokeOptionsHelpers,
|
|
||||||
};
|
};
|
||||||
use crate::canvas_data::{CanvasPaintState, Filter, TextRun};
|
use crate::canvas_data::{CanvasPaintState, Filter, TextRun};
|
||||||
|
|
||||||
|
@ -44,7 +43,6 @@ impl Backend for RaqoteBackend {
|
||||||
type CompositionOp = raqote::BlendMode;
|
type CompositionOp = raqote::BlendMode;
|
||||||
type DrawTarget = raqote::DrawTarget;
|
type DrawTarget = raqote::DrawTarget;
|
||||||
type SourceSurface = Vec<u8>; // TODO: See if we can avoid the alloc (probably?)
|
type SourceSurface = Vec<u8>; // TODO: See if we can avoid the alloc (probably?)
|
||||||
type Path = Path;
|
|
||||||
type GradientStop = raqote::GradientStop;
|
type GradientStop = raqote::GradientStop;
|
||||||
type GradientStops = Vec<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 {
|
impl GenericDrawTarget<RaqoteBackend> for raqote::DrawTarget {
|
||||||
fn clear_rect(&mut self, rect: &Rect<f32>) {
|
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(
|
pb.rect(
|
||||||
rect.origin.x,
|
rect.origin.x,
|
||||||
rect.origin.y,
|
rect.origin.y,
|
||||||
|
@ -356,7 +355,7 @@ impl GenericDrawTarget<RaqoteBackend> for raqote::DrawTarget {
|
||||||
let mut options = raqote::DrawOptions::new();
|
let mut options = raqote::DrawOptions::new();
|
||||||
options.blend_mode = raqote::BlendMode::Clear;
|
options.blend_mode = raqote::BlendMode::Clear;
|
||||||
let pattern = Pattern::Color(0, 0, 0, 0);
|
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)]
|
#[allow(unsafe_code)]
|
||||||
fn copy_surface(
|
fn copy_surface(
|
||||||
|
@ -424,15 +423,15 @@ impl GenericDrawTarget<RaqoteBackend> for raqote::DrawTarget {
|
||||||
transform,
|
transform,
|
||||||
));
|
));
|
||||||
|
|
||||||
let mut pb = raqote::PathBuilder::new();
|
let mut pb = canvas_traits::canvas::Path::new();
|
||||||
pb.rect(
|
pb.rect(
|
||||||
dest.origin.x as f32,
|
dest.origin.x,
|
||||||
dest.origin.y as f32,
|
dest.origin.y,
|
||||||
dest.size.width as f32,
|
dest.size.width,
|
||||||
dest.size.height as f32,
|
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(
|
fn draw_surface_with_shadow(
|
||||||
&self,
|
&self,
|
||||||
|
@ -447,11 +446,11 @@ impl GenericDrawTarget<RaqoteBackend> for raqote::DrawTarget {
|
||||||
}
|
}
|
||||||
fn fill(
|
fn fill(
|
||||||
&mut self,
|
&mut self,
|
||||||
path: &<RaqoteBackend as Backend>::Path,
|
path: &canvas_traits::canvas::Path,
|
||||||
pattern: &Pattern,
|
pattern: &Pattern,
|
||||||
draw_options: &<RaqoteBackend as Backend>::DrawOptions,
|
draw_options: &<RaqoteBackend as Backend>::DrawOptions,
|
||||||
) {
|
) {
|
||||||
let path = path.into();
|
let path = to_path(path);
|
||||||
match draw_options.blend_mode {
|
match draw_options.blend_mode {
|
||||||
raqote::BlendMode::Src => {
|
raqote::BlendMode::Src => {
|
||||||
self.clear(raqote::SolidSource::from_unpremultiplied_argb(0, 0, 0, 0));
|
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<'_>,
|
pattern: &<RaqoteBackend as Backend>::Pattern<'_>,
|
||||||
draw_options: &<RaqoteBackend as Backend>::DrawOptions,
|
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(
|
pb.rect(
|
||||||
rect.origin.x,
|
rect.origin.x,
|
||||||
rect.origin.y,
|
rect.origin.y,
|
||||||
|
@ -554,7 +554,7 @@ impl GenericDrawTarget<RaqoteBackend> for raqote::DrawTarget {
|
||||||
rect.size.height,
|
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> {
|
fn get_size(&self) -> Size2D<i32> {
|
||||||
Size2D::new(self.width(), self.height())
|
Size2D::new(self.width(), self.height())
|
||||||
|
@ -565,8 +565,8 @@ impl GenericDrawTarget<RaqoteBackend> for raqote::DrawTarget {
|
||||||
fn pop_clip(&mut self) {
|
fn pop_clip(&mut self) {
|
||||||
self.pop_clip();
|
self.pop_clip();
|
||||||
}
|
}
|
||||||
fn push_clip(&mut self, path: &<RaqoteBackend as Backend>::Path) {
|
fn push_clip(&mut self, path: &canvas_traits::canvas::Path) {
|
||||||
self.push_clip(&path.into());
|
self.push_clip(&to_path(path));
|
||||||
}
|
}
|
||||||
fn push_clip_rect(&mut self, rect: &Rect<i32>) {
|
fn push_clip_rect(&mut self, rect: &Rect<i32>) {
|
||||||
self.push_clip_rect(rect.to_box2d());
|
self.push_clip_rect(rect.to_box2d());
|
||||||
|
@ -579,12 +579,17 @@ impl GenericDrawTarget<RaqoteBackend> for raqote::DrawTarget {
|
||||||
}
|
}
|
||||||
fn stroke(
|
fn stroke(
|
||||||
&mut self,
|
&mut self,
|
||||||
path: &<RaqoteBackend as Backend>::Path,
|
path: &canvas_traits::canvas::Path,
|
||||||
pattern: &Pattern,
|
pattern: &Pattern,
|
||||||
stroke_options: &<RaqoteBackend as Backend>::StrokeOptions,
|
stroke_options: &<RaqoteBackend as Backend>::StrokeOptions,
|
||||||
draw_options: &<RaqoteBackend as Backend>::DrawOptions,
|
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(
|
fn stroke_rect(
|
||||||
&mut self,
|
&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 {
|
pub trait ToRaqoteStyle {
|
||||||
type Target;
|
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()
|
||||||
|
}
|
||||||
|
|
|
@ -81,6 +81,7 @@ itertools = { workspace = true }
|
||||||
js = { workspace = true }
|
js = { workspace = true }
|
||||||
jstraceable_derive = { path = "../jstraceable_derive" }
|
jstraceable_derive = { path = "../jstraceable_derive" }
|
||||||
keyboard-types = { workspace = true }
|
keyboard-types = { workspace = true }
|
||||||
|
kurbo = { workspace = true }
|
||||||
layout_api = { workspace = true }
|
layout_api = { workspace = true }
|
||||||
libc = { workspace = true }
|
libc = { workspace = true }
|
||||||
log = { workspace = true }
|
log = { workspace = true }
|
||||||
|
|
|
@ -9,7 +9,7 @@ use std::sync::Arc;
|
||||||
|
|
||||||
use canvas_traits::canvas::{
|
use canvas_traits::canvas::{
|
||||||
Canvas2dMsg, CanvasId, CanvasMsg, CompositionOrBlending, Direction, FillOrStrokeStyle,
|
Canvas2dMsg, CanvasId, CanvasMsg, CompositionOrBlending, Direction, FillOrStrokeStyle,
|
||||||
FillRule, LineCapStyle, LineJoinStyle, LinearGradientStyle, PathSegment, RadialGradientStyle,
|
FillRule, LineCapStyle, LineJoinStyle, LinearGradientStyle, Path, RadialGradientStyle,
|
||||||
RepetitionStyle, TextAlign, TextBaseline, TextMetrics as CanvasTextMetrics,
|
RepetitionStyle, TextAlign, TextBaseline, TextMetrics as CanvasTextMetrics,
|
||||||
};
|
};
|
||||||
use constellation_traits::ScriptToConstellationMessage;
|
use constellation_traits::ScriptToConstellationMessage;
|
||||||
|
@ -1799,7 +1799,7 @@ impl CanvasState {
|
||||||
}
|
}
|
||||||
|
|
||||||
// https://html.spec.whatwg.org/multipage/#dom-context-2d-fill
|
// https://html.spec.whatwg.org/multipage/#dom-context-2d-fill
|
||||||
pub(crate) fn fill_(&self, path: Vec<PathSegment>, _fill_rule: CanvasFillRule) {
|
pub(crate) fn fill_(&self, path: Path, _fill_rule: CanvasFillRule) {
|
||||||
// TODO: Process fill rule
|
// TODO: Process fill rule
|
||||||
let style = self.state.borrow().fill_style.to_fill_or_stroke_style();
|
let style = self.state.borrow().fill_style.to_fill_or_stroke_style();
|
||||||
self.send_canvas_2d_msg(Canvas2dMsg::FillPath(style, path));
|
self.send_canvas_2d_msg(Canvas2dMsg::FillPath(style, path));
|
||||||
|
@ -1811,7 +1811,7 @@ impl CanvasState {
|
||||||
self.send_canvas_2d_msg(Canvas2dMsg::Stroke(style));
|
self.send_canvas_2d_msg(Canvas2dMsg::Stroke(style));
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn stroke_(&self, path: Vec<PathSegment>) {
|
pub(crate) fn stroke_(&self, path: Path) {
|
||||||
let style = self.state.borrow().stroke_style.to_fill_or_stroke_style();
|
let style = self.state.borrow().stroke_style.to_fill_or_stroke_style();
|
||||||
self.send_canvas_2d_msg(Canvas2dMsg::StrokePath(style, path));
|
self.send_canvas_2d_msg(Canvas2dMsg::StrokePath(style, path));
|
||||||
}
|
}
|
||||||
|
@ -1823,7 +1823,7 @@ impl CanvasState {
|
||||||
}
|
}
|
||||||
|
|
||||||
// https://html.spec.whatwg.org/multipage/#dom-context-2d-clip
|
// https://html.spec.whatwg.org/multipage/#dom-context-2d-clip
|
||||||
pub(crate) fn clip_(&self, path: Vec<PathSegment>, _fill_rule: CanvasFillRule) {
|
pub(crate) fn clip_(&self, path: Path, _fill_rule: CanvasFillRule) {
|
||||||
// TODO: Process fill rule
|
// TODO: Process fill rule
|
||||||
self.send_canvas_2d_msg(Canvas2dMsg::ClipPath(path));
|
self.send_canvas_2d_msg(Canvas2dMsg::ClipPath(path));
|
||||||
}
|
}
|
||||||
|
@ -1853,24 +1853,17 @@ impl CanvasState {
|
||||||
// https://html.spec.whatwg.org/multipage/#dom-context-2d-ispointinpath
|
// https://html.spec.whatwg.org/multipage/#dom-context-2d-ispointinpath
|
||||||
pub(crate) fn is_point_in_path_(
|
pub(crate) fn is_point_in_path_(
|
||||||
&self,
|
&self,
|
||||||
global: &GlobalScope,
|
_global: &GlobalScope,
|
||||||
path: Vec<PathSegment>,
|
path: Path,
|
||||||
x: f64,
|
x: f64,
|
||||||
y: f64,
|
y: f64,
|
||||||
fill_rule: CanvasFillRule,
|
fill_rule: CanvasFillRule,
|
||||||
) -> bool {
|
) -> bool {
|
||||||
if !(x.is_finite() && y.is_finite()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
let fill_rule = match fill_rule {
|
let fill_rule = match fill_rule {
|
||||||
CanvasFillRule::Nonzero => FillRule::Nonzero,
|
CanvasFillRule::Nonzero => FillRule::Nonzero,
|
||||||
CanvasFillRule::Evenodd => FillRule::Evenodd,
|
CanvasFillRule::Evenodd => FillRule::Evenodd,
|
||||||
};
|
};
|
||||||
let (sender, receiver) =
|
path.is_point_in_path(x, y, fill_rule)
|
||||||
profiled_ipc::channel::<bool>(global.time_profiler_chan().clone()).unwrap();
|
|
||||||
self.send_canvas_2d_msg(Canvas2dMsg::IsPointInPath(path, x, y, fill_rule, sender));
|
|
||||||
receiver.recv().unwrap()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// https://html.spec.whatwg.org/multipage/#dom-context-2d-scale
|
// https://html.spec.whatwg.org/multipage/#dom-context-2d-scale
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
|
|
||||||
use std::cell::RefCell;
|
use std::cell::RefCell;
|
||||||
|
|
||||||
use canvas_traits::canvas::PathSegment;
|
use canvas_traits::canvas::Path;
|
||||||
use dom_struct::dom_struct;
|
use dom_struct::dom_struct;
|
||||||
use js::rust::HandleObject;
|
use js::rust::HandleObject;
|
||||||
use script_bindings::str::DOMString;
|
use script_bindings::str::DOMString;
|
||||||
|
@ -15,20 +15,20 @@ use crate::dom::bindings::reflector::{Reflector, reflect_dom_object_with_proto};
|
||||||
use crate::dom::bindings::root::DomRoot;
|
use crate::dom::bindings::root::DomRoot;
|
||||||
use crate::dom::globalscope::GlobalScope;
|
use crate::dom::globalscope::GlobalScope;
|
||||||
use crate::script_runtime::CanGc;
|
use crate::script_runtime::CanGc;
|
||||||
use crate::svgpath::PathParser;
|
|
||||||
|
|
||||||
#[dom_struct]
|
#[dom_struct]
|
||||||
pub(crate) struct Path2D {
|
pub(crate) struct Path2D {
|
||||||
reflector_: Reflector,
|
reflector_: Reflector,
|
||||||
|
#[ignore_malloc_size_of = "Defined in kurbo."]
|
||||||
#[no_trace]
|
#[no_trace]
|
||||||
path: RefCell<Vec<PathSegment>>,
|
path: RefCell<Path>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Path2D {
|
impl Path2D {
|
||||||
pub(crate) fn new() -> Path2D {
|
pub(crate) fn new() -> Path2D {
|
||||||
Self {
|
Self {
|
||||||
reflector_: Reflector::new(),
|
reflector_: Reflector::new(),
|
||||||
path: RefCell::new(vec![]),
|
path: RefCell::new(Path::new()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub(crate) fn new_with_path(other: &Path2D) -> Path2D {
|
pub(crate) fn new_with_path(other: &Path2D) -> Path2D {
|
||||||
|
@ -37,26 +37,15 @@ impl Path2D {
|
||||||
path: other.path.clone(),
|
path: other.path.clone(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn new_with_str(path: &str) -> Path2D {
|
pub(crate) fn new_with_str(path: &str) -> Path2D {
|
||||||
let mut path_segments = Vec::new();
|
|
||||||
|
|
||||||
for segment in PathParser::new(path) {
|
|
||||||
if let Ok(segment) = segment {
|
|
||||||
path_segments.push(segment);
|
|
||||||
} else {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
reflector_: Reflector::new(),
|
reflector_: Reflector::new(),
|
||||||
path: RefCell::new(path_segments),
|
path: RefCell::new(Path::from_svg(path)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub(crate) fn push(&self, seg: PathSegment) {
|
|
||||||
self.path.borrow_mut().push(seg);
|
pub(crate) fn segments(&self) -> Path {
|
||||||
}
|
|
||||||
pub(crate) fn segments(&self) -> Vec<PathSegment> {
|
|
||||||
self.path.borrow().clone()
|
self.path.borrow().clone()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -64,143 +53,49 @@ impl Path2D {
|
||||||
impl Path2DMethods<crate::DomTypeHolder> for Path2D {
|
impl Path2DMethods<crate::DomTypeHolder> for Path2D {
|
||||||
/// <https://html.spec.whatwg.org/multipage/#dom-path2d-addpath>
|
/// <https://html.spec.whatwg.org/multipage/#dom-path2d-addpath>
|
||||||
fn AddPath(&self, other: &Path2D) {
|
fn AddPath(&self, other: &Path2D) {
|
||||||
|
let other = other.segments();
|
||||||
// Step 7. Add all the subpaths in c to a.
|
// Step 7. Add all the subpaths in c to a.
|
||||||
if std::ptr::eq(&self.path, &other.path) {
|
self.path.borrow_mut().0.extend(other.0);
|
||||||
// Note: this is not part of the spec, but it is a workaround to
|
|
||||||
// avoids borrow conflict when path is same as other.path
|
|
||||||
self.path.borrow_mut().extend_from_within(..);
|
|
||||||
} else {
|
|
||||||
let mut dest = self.path.borrow_mut();
|
|
||||||
dest.extend(other.path.borrow().iter().copied());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <https://html.spec.whatwg.org/multipage/#dom-context-2d-closepath>
|
/// <https://html.spec.whatwg.org/multipage/#dom-context-2d-closepath>
|
||||||
fn ClosePath(&self) {
|
fn ClosePath(&self) {
|
||||||
self.push(PathSegment::ClosePath);
|
self.path.borrow_mut().close_path();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <https://html.spec.whatwg.org/multipage/#dom-context-2d-moveto>
|
/// <https://html.spec.whatwg.org/multipage/#dom-context-2d-moveto>
|
||||||
fn MoveTo(&self, x: f64, y: f64) {
|
fn MoveTo(&self, x: f64, y: f64) {
|
||||||
// Step 1. If either of the arguments are infinite or NaN, then return.
|
self.path.borrow_mut().move_to(x, y);
|
||||||
if !(x.is_finite() && y.is_finite()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Step 2. Create a new subpath with the specified point as its first (and only) point.
|
|
||||||
self.push(PathSegment::MoveTo {
|
|
||||||
x: x as f32,
|
|
||||||
y: y as f32,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <https://html.spec.whatwg.org/multipage/#dom-context-2d-lineto>
|
/// <https://html.spec.whatwg.org/multipage/#dom-context-2d-lineto>
|
||||||
fn LineTo(&self, x: f64, y: f64) {
|
fn LineTo(&self, x: f64, y: f64) {
|
||||||
// Step 1. If either of the arguments are infinite or NaN, then return.
|
self.path.borrow_mut().line_to(x, y);
|
||||||
if !(x.is_finite() && y.is_finite()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
self.push(PathSegment::LineTo {
|
|
||||||
x: x as f32,
|
|
||||||
y: y as f32,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <https://html.spec.whatwg.org/multipage/#dom-context-2d-quadraticcurveto>
|
/// <https://html.spec.whatwg.org/multipage/#dom-context-2d-quadraticcurveto>
|
||||||
fn QuadraticCurveTo(&self, cpx: f64, cpy: f64, x: f64, y: f64) {
|
fn QuadraticCurveTo(&self, cpx: f64, cpy: f64, x: f64, y: f64) {
|
||||||
// Step 1. If any of the arguments are infinite or NaN, then return.
|
self.path.borrow_mut().quadratic_curve_to(cpx, cpy, x, y);
|
||||||
if !(cpx.is_finite() && cpy.is_finite() && x.is_finite() && y.is_finite()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
self.push(PathSegment::Quadratic {
|
|
||||||
cpx: cpx as f32,
|
|
||||||
cpy: cpy as f32,
|
|
||||||
x: x as f32,
|
|
||||||
y: y as f32,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <https://html.spec.whatwg.org/multipage/#dom-context-2d-beziercurveto>
|
/// <https://html.spec.whatwg.org/multipage/#dom-context-2d-beziercurveto>
|
||||||
fn BezierCurveTo(&self, cp1x: f64, cp1y: f64, cp2x: f64, cp2y: f64, x: f64, y: f64) {
|
fn BezierCurveTo(&self, cp1x: f64, cp1y: f64, cp2x: f64, cp2y: f64, x: f64, y: f64) {
|
||||||
// Step 1. If any of the arguments are infinite or NaN, then return.
|
self.path
|
||||||
if !(cp1x.is_finite() &&
|
.borrow_mut()
|
||||||
cp1y.is_finite() &&
|
.bezier_curve_to(cp1x, cp1y, cp2x, cp2y, x, y);
|
||||||
cp2x.is_finite() &&
|
|
||||||
cp2y.is_finite() &&
|
|
||||||
x.is_finite() &&
|
|
||||||
y.is_finite())
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
self.push(PathSegment::Bezier {
|
|
||||||
cp1x: cp1x as f32,
|
|
||||||
cp1y: cp1y as f32,
|
|
||||||
cp2x: cp2x as f32,
|
|
||||||
cp2y: cp2y as f32,
|
|
||||||
x: x as f32,
|
|
||||||
y: y as f32,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <https://html.spec.whatwg.org/multipage/#dom-context-2d-arcto>
|
/// <https://html.spec.whatwg.org/multipage/#dom-context-2d-arcto>
|
||||||
fn ArcTo(&self, x1: f64, y1: f64, x2: f64, y2: f64, r: f64) -> Fallible<()> {
|
fn ArcTo(&self, x1: f64, y1: f64, x2: f64, y2: f64, radius: f64) -> Fallible<()> {
|
||||||
// Step 1. If any of the arguments are infinite or NaN, then return.
|
self.path
|
||||||
if !(x1.is_finite() && y1.is_finite() && x2.is_finite() && y2.is_finite() && r.is_finite())
|
.borrow_mut()
|
||||||
{
|
.arc_to(x1, y1, x2, y2, radius)
|
||||||
return Ok(());
|
.map_err(|_| Error::IndexSize)
|
||||||
}
|
|
||||||
|
|
||||||
// Step 3. If radius is negative, then throw an "IndexSizeError" DOMException.
|
|
||||||
if r < 0.0 {
|
|
||||||
return Err(Error::IndexSize);
|
|
||||||
}
|
|
||||||
|
|
||||||
self.push(PathSegment::ArcTo {
|
|
||||||
cp1x: x1 as f32,
|
|
||||||
cp1y: y1 as f32,
|
|
||||||
cp2x: x2 as f32,
|
|
||||||
cp2y: y2 as f32,
|
|
||||||
radius: r as f32,
|
|
||||||
});
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <https://html.spec.whatwg.org/multipage/#dom-context-2d-rect>
|
/// <https://html.spec.whatwg.org/multipage/#dom-context-2d-rect>
|
||||||
fn Rect(&self, x: f64, y: f64, w: f64, h: f64) {
|
fn Rect(&self, x: f64, y: f64, w: f64, h: f64) {
|
||||||
// Step 1. If any of the arguments are infinite or NaN, then return.
|
self.path.borrow_mut().rect(x, y, w, h);
|
||||||
if !(x.is_finite() && y.is_finite() && w.is_finite() && h.is_finite()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// Step 2. Create a new subpath containing just the four points
|
|
||||||
// (x, y), (x+w, y), (x+w, y+h), (x, y+h), in that order,
|
|
||||||
// with those four points connected by straight lines.
|
|
||||||
self.push(PathSegment::MoveTo {
|
|
||||||
x: x as f32,
|
|
||||||
y: y as f32,
|
|
||||||
});
|
|
||||||
self.push(PathSegment::LineTo {
|
|
||||||
x: (x + w) as f32,
|
|
||||||
y: y as f32,
|
|
||||||
});
|
|
||||||
self.push(PathSegment::LineTo {
|
|
||||||
x: (x + w) as f32,
|
|
||||||
y: (y + h) as f32,
|
|
||||||
});
|
|
||||||
self.push(PathSegment::LineTo {
|
|
||||||
x: x as f32,
|
|
||||||
y: (y + h) as f32,
|
|
||||||
});
|
|
||||||
// Step 3. Mark the subpath as closed.
|
|
||||||
self.push(PathSegment::ClosePath);
|
|
||||||
|
|
||||||
// Step 4. Create a new subpath with the point (x, y) as the only point in the subpath.
|
|
||||||
self.push(PathSegment::MoveTo {
|
|
||||||
x: x as f32,
|
|
||||||
y: y as f32,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <https://html.spec.whatwg.org/multipage/#dom-context-2d-arc>
|
/// <https://html.spec.whatwg.org/multipage/#dom-context-2d-arc>
|
||||||
|
@ -208,37 +103,15 @@ impl Path2DMethods<crate::DomTypeHolder> for Path2D {
|
||||||
&self,
|
&self,
|
||||||
x: f64,
|
x: f64,
|
||||||
y: f64,
|
y: f64,
|
||||||
r: f64,
|
radius: f64,
|
||||||
start: f64,
|
start_angle: f64,
|
||||||
end: f64,
|
end_angle: f64,
|
||||||
anticlockwise: bool,
|
counterclockwise: bool,
|
||||||
) -> Fallible<()> {
|
) -> Fallible<()> {
|
||||||
// Step 1. If any of the arguments are infinite or NaN, then return.
|
self.path
|
||||||
if !(x.is_finite() &&
|
.borrow_mut()
|
||||||
y.is_finite() &&
|
.arc(x, y, radius, start_angle, end_angle, counterclockwise)
|
||||||
r.is_finite() &&
|
.map_err(|_| Error::IndexSize)
|
||||||
start.is_finite() &&
|
|
||||||
end.is_finite())
|
|
||||||
{
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
|
|
||||||
// Step 2. If either radiusX or radiusY are negative, then throw an "IndexSizeError" DOMException.
|
|
||||||
if r < 0.0 {
|
|
||||||
return Err(Error::IndexSize);
|
|
||||||
}
|
|
||||||
|
|
||||||
self.push(PathSegment::Ellipse {
|
|
||||||
x: x as f32,
|
|
||||||
y: y as f32,
|
|
||||||
radius_x: r as f32,
|
|
||||||
radius_y: r as f32,
|
|
||||||
rotation: 0.,
|
|
||||||
start_angle: start as f32,
|
|
||||||
end_angle: end as f32,
|
|
||||||
anticlockwise,
|
|
||||||
});
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <https://html.spec.whatwg.org/multipage/#dom-context-2d-ellipse>
|
/// <https://html.spec.whatwg.org/multipage/#dom-context-2d-ellipse>
|
||||||
|
@ -246,41 +119,26 @@ impl Path2DMethods<crate::DomTypeHolder> for Path2D {
|
||||||
&self,
|
&self,
|
||||||
x: f64,
|
x: f64,
|
||||||
y: f64,
|
y: f64,
|
||||||
rx: f64,
|
radius_x: f64,
|
||||||
ry: f64,
|
radius_y: f64,
|
||||||
rotation: f64,
|
rotation_angle: f64,
|
||||||
start: f64,
|
start_angle: f64,
|
||||||
end: f64,
|
end_angle: f64,
|
||||||
anticlockwise: bool,
|
counterclockwise: bool,
|
||||||
) -> Fallible<()> {
|
) -> Fallible<()> {
|
||||||
// Step 1. If any of the arguments are infinite or NaN, then return.
|
self.path
|
||||||
if !(x.is_finite() &&
|
.borrow_mut()
|
||||||
y.is_finite() &&
|
.ellipse(
|
||||||
rx.is_finite() &&
|
x,
|
||||||
ry.is_finite() &&
|
y,
|
||||||
rotation.is_finite() &&
|
radius_x,
|
||||||
start.is_finite() &&
|
radius_y,
|
||||||
end.is_finite())
|
rotation_angle,
|
||||||
{
|
start_angle,
|
||||||
return Ok(());
|
end_angle,
|
||||||
}
|
counterclockwise,
|
||||||
|
)
|
||||||
// Step 2. If either radiusX or radiusY are negative, then throw an "IndexSizeError" DOMException.
|
.map_err(|_| Error::IndexSize)
|
||||||
if rx < 0.0 || ry < 0.0 {
|
|
||||||
return Err(Error::IndexSize);
|
|
||||||
}
|
|
||||||
|
|
||||||
self.push(PathSegment::Ellipse {
|
|
||||||
x: x as f32,
|
|
||||||
y: y as f32,
|
|
||||||
radius_x: rx as f32,
|
|
||||||
radius_y: ry as f32,
|
|
||||||
rotation: rotation as f32,
|
|
||||||
start_angle: start as f32,
|
|
||||||
end_angle: end as f32,
|
|
||||||
anticlockwise,
|
|
||||||
});
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <https://html.spec.whatwg.org/multipage/#dom-path2d-dev>
|
/// <https://html.spec.whatwg.org/multipage/#dom-path2d-dev>
|
||||||
|
|
|
@ -74,8 +74,6 @@ mod drag_data_store;
|
||||||
mod links;
|
mod links;
|
||||||
mod xpath;
|
mod xpath;
|
||||||
|
|
||||||
mod svgpath;
|
|
||||||
|
|
||||||
pub use init::init;
|
pub use init::init;
|
||||||
pub(crate) use script_bindings::DomTypes;
|
pub(crate) use script_bindings::DomTypes;
|
||||||
pub use script_runtime::JSEngineSetup;
|
pub use script_runtime::JSEngineSetup;
|
||||||
|
|
|
@ -1,13 +0,0 @@
|
||||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
||||||
* 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/. */
|
|
||||||
|
|
||||||
mod number;
|
|
||||||
mod path;
|
|
||||||
mod stream;
|
|
||||||
|
|
||||||
#[derive(Debug, Default, Eq, PartialEq)]
|
|
||||||
pub struct Error;
|
|
||||||
|
|
||||||
pub(crate) use path::PathParser;
|
|
||||||
pub(crate) use stream::Stream;
|
|
|
@ -1,198 +0,0 @@
|
||||||
// Copyright 2018 the SVG Types Authors
|
|
||||||
// Copyright 2025 the Servo Authors
|
|
||||||
// SPDX-License-Identifier: Apache-2.0 OR MIT
|
|
||||||
|
|
||||||
use std::str::FromStr;
|
|
||||||
|
|
||||||
use crate::svgpath::{Error, Stream};
|
|
||||||
|
|
||||||
/// An [SVG number](https://www.w3.org/TR/SVG2/types.html#InterfaceSVGNumber).
|
|
||||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
|
||||||
pub struct Number(pub f32);
|
|
||||||
|
|
||||||
impl std::str::FromStr for Number {
|
|
||||||
type Err = Error;
|
|
||||||
|
|
||||||
fn from_str(text: &str) -> Result<Self, Self::Err> {
|
|
||||||
let mut s = Stream::from(text);
|
|
||||||
let n = s.parse_number()?;
|
|
||||||
s.skip_spaces();
|
|
||||||
if !s.at_end() {
|
|
||||||
return Err(Error);
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(Self(n))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Stream<'_> {
|
|
||||||
/// Parses number from the stream.
|
|
||||||
///
|
|
||||||
/// This method will detect a number length and then
|
|
||||||
/// will pass a substring to the `f64::from_str` method.
|
|
||||||
///
|
|
||||||
/// <https://www.w3.org/TR/SVG2/types.html#InterfaceSVGNumber>
|
|
||||||
pub fn parse_number(&mut self) -> Result<f32, Error> {
|
|
||||||
// Strip off leading whitespaces.
|
|
||||||
self.skip_spaces();
|
|
||||||
|
|
||||||
if self.at_end() {
|
|
||||||
return Err(Error);
|
|
||||||
}
|
|
||||||
|
|
||||||
self.parse_number_impl().map_err(|_| Error)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn parse_number_impl(&mut self) -> Result<f32, Error> {
|
|
||||||
let start = self.pos();
|
|
||||||
|
|
||||||
let mut c = self.curr_byte()?;
|
|
||||||
|
|
||||||
// Consume sign.
|
|
||||||
if matches!(c, b'+' | b'-') {
|
|
||||||
self.advance(1);
|
|
||||||
c = self.curr_byte()?;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Consume integer.
|
|
||||||
match c {
|
|
||||||
b'0'..=b'9' => self.skip_digits(),
|
|
||||||
b'.' => {},
|
|
||||||
_ => return Err(Error),
|
|
||||||
}
|
|
||||||
|
|
||||||
// Consume fraction.
|
|
||||||
if let Ok(b'.') = self.curr_byte() {
|
|
||||||
self.advance(1);
|
|
||||||
self.skip_digits();
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Ok(c) = self.curr_byte() {
|
|
||||||
if matches!(c, b'e' | b'E') {
|
|
||||||
let c2 = self.next_byte()?;
|
|
||||||
// Check for `em`/`ex`.
|
|
||||||
if c2 != b'm' && c2 != b'x' {
|
|
||||||
self.advance(1);
|
|
||||||
|
|
||||||
match self.curr_byte()? {
|
|
||||||
b'+' | b'-' => {
|
|
||||||
self.advance(1);
|
|
||||||
self.skip_digits();
|
|
||||||
},
|
|
||||||
b'0'..=b'9' => self.skip_digits(),
|
|
||||||
_ => {
|
|
||||||
return Err(Error);
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let s = self.slice_back(start);
|
|
||||||
|
|
||||||
// Use the default f32 parser now.
|
|
||||||
if let Ok(n) = f32::from_str(s) {
|
|
||||||
// inf, nan, etc. are an error.
|
|
||||||
if n.is_finite() {
|
|
||||||
return Ok(n);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Err(Error)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Parses number from a list of numbers.
|
|
||||||
pub fn parse_list_number(&mut self) -> Result<f32, Error> {
|
|
||||||
if self.at_end() {
|
|
||||||
return Err(Error);
|
|
||||||
}
|
|
||||||
|
|
||||||
let n = self.parse_number()?;
|
|
||||||
self.skip_spaces();
|
|
||||||
self.parse_list_separator();
|
|
||||||
Ok(n)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A pull-based [`<list-of-numbers>`] parser.
|
|
||||||
///
|
|
||||||
/// # Examples
|
|
||||||
///
|
|
||||||
/// ```
|
|
||||||
/// use svgtypes::NumberListParser;
|
|
||||||
///
|
|
||||||
/// let mut p = NumberListParser::from("10, 20 -50");
|
|
||||||
/// assert_eq!(p.next().unwrap().unwrap(), 10.0);
|
|
||||||
/// assert_eq!(p.next().unwrap().unwrap(), 20.0);
|
|
||||||
/// assert_eq!(p.next().unwrap().unwrap(), -50.0);
|
|
||||||
/// assert_eq!(p.next().is_none(), true);
|
|
||||||
/// ```
|
|
||||||
///
|
|
||||||
/// [`<list-of-numbers>`]: https://www.w3.org/TR/SVG2/types.html#InterfaceSVGNumberList
|
|
||||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
|
||||||
pub struct NumberListParser<'a>(Stream<'a>);
|
|
||||||
|
|
||||||
impl<'a> From<&'a str> for NumberListParser<'a> {
|
|
||||||
#[inline]
|
|
||||||
fn from(v: &'a str) -> Self {
|
|
||||||
NumberListParser(Stream::from(v))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Iterator for NumberListParser<'_> {
|
|
||||||
type Item = Result<f32, Error>;
|
|
||||||
|
|
||||||
fn next(&mut self) -> Option<Self::Item> {
|
|
||||||
if self.0.at_end() {
|
|
||||||
None
|
|
||||||
} else {
|
|
||||||
let v = self.0.parse_list_number();
|
|
||||||
if v.is_err() {
|
|
||||||
self.0.jump_to_end();
|
|
||||||
}
|
|
||||||
|
|
||||||
Some(v)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[rustfmt::skip]
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use crate::svgpath::Stream;
|
|
||||||
|
|
||||||
macro_rules! test_p {
|
|
||||||
($name:ident, $text:expr, $result:expr) => (
|
|
||||||
#[test]
|
|
||||||
fn $name() {
|
|
||||||
let mut s = Stream::from($text);
|
|
||||||
assert_eq!(s.parse_number().unwrap(), $result);
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
test_p!(parse_1, "0", 0.0);
|
|
||||||
test_p!(parse_2, "1", 1.0);
|
|
||||||
test_p!(parse_3, "-1", -1.0);
|
|
||||||
test_p!(parse_4, " -1 ", -1.0);
|
|
||||||
test_p!(parse_5, " 1 ", 1.0);
|
|
||||||
test_p!(parse_6, ".4", 0.4);
|
|
||||||
test_p!(parse_7, "-.4", -0.4);
|
|
||||||
test_p!(parse_8, "-.4text", -0.4);
|
|
||||||
test_p!(parse_9, "-.01 text", -0.01);
|
|
||||||
test_p!(parse_10, "-.01 4", -0.01);
|
|
||||||
test_p!(parse_11, ".0000000000008", 0.0000000000008);
|
|
||||||
test_p!(parse_12, "1000000000000", 1000000000000.0);
|
|
||||||
test_p!(parse_13, "123456.123456", 123456.123456);
|
|
||||||
test_p!(parse_14, "+10", 10.0);
|
|
||||||
test_p!(parse_15, "1e2", 100.0);
|
|
||||||
test_p!(parse_16, "1e+2", 100.0);
|
|
||||||
test_p!(parse_17, "1E2", 100.0);
|
|
||||||
test_p!(parse_18, "1e-2", 0.01);
|
|
||||||
test_p!(parse_19, "1ex", 1.0);
|
|
||||||
test_p!(parse_20, "1em", 1.0);
|
|
||||||
test_p!(parse_21, "12345678901234567890", 12345678901234567000.0);
|
|
||||||
test_p!(parse_22, "0.", 0.0);
|
|
||||||
test_p!(parse_23, "1.3e-2", 0.013);
|
|
||||||
// test_number!(parse_24, "1e", 1.0); // TODO: this
|
|
||||||
}
|
|
|
@ -1,393 +0,0 @@
|
||||||
// Copyright 2021 the SVG Types Authors
|
|
||||||
// Copyright 2025 the Servo Authors
|
|
||||||
// SPDX-License-Identifier: Apache-2.0 OR MIT
|
|
||||||
|
|
||||||
use canvas_traits::canvas::PathSegment;
|
|
||||||
|
|
||||||
use crate::svgpath::{Error, Stream};
|
|
||||||
|
|
||||||
pub struct PathParser<'a> {
|
|
||||||
stream: Stream<'a>,
|
|
||||||
state: State,
|
|
||||||
last_cmd: u8,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> PathParser<'a> {
|
|
||||||
pub fn new(string: &'a str) -> Self {
|
|
||||||
Self {
|
|
||||||
stream: Stream::from(string),
|
|
||||||
state: State::default(),
|
|
||||||
last_cmd: b' ',
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Iterator for PathParser<'_> {
|
|
||||||
type Item = Result<PathSegment, Error>;
|
|
||||||
|
|
||||||
fn next(&mut self) -> Option<Self::Item> {
|
|
||||||
self.stream.skip_spaces();
|
|
||||||
|
|
||||||
let Ok(curr_byte) = self.stream.curr_byte() else {
|
|
||||||
return None;
|
|
||||||
};
|
|
||||||
|
|
||||||
let cmd = if self.last_cmd == b' ' {
|
|
||||||
if let move_to @ (b'm' | b'M') = curr_byte {
|
|
||||||
self.stream.advance(1);
|
|
||||||
move_to
|
|
||||||
} else {
|
|
||||||
return Some(Err(Error));
|
|
||||||
}
|
|
||||||
} else if curr_byte.is_ascii_alphabetic() {
|
|
||||||
self.stream.advance(1);
|
|
||||||
curr_byte
|
|
||||||
} else {
|
|
||||||
match self.last_cmd {
|
|
||||||
b'm' => b'l',
|
|
||||||
b'M' => b'L',
|
|
||||||
b'z' | b'Z' => return Some(Err(Error)),
|
|
||||||
cmd => cmd,
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
self.last_cmd = cmd;
|
|
||||||
Some(to_point(&mut self.stream, cmd, &mut self.state))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Default)]
|
|
||||||
pub struct State {
|
|
||||||
start: (f32, f32),
|
|
||||||
pos: (f32, f32),
|
|
||||||
quad: (f32, f32),
|
|
||||||
cubic: (f32, f32),
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn to_point(s: &mut Stream<'_>, cmd: u8, state: &mut State) -> Result<PathSegment, Error> {
|
|
||||||
let abs = cmd.is_ascii_uppercase();
|
|
||||||
let cmd = cmd.to_ascii_lowercase();
|
|
||||||
let (dx, dy) = if abs { (0., 0.) } else { state.pos };
|
|
||||||
let seg = match cmd {
|
|
||||||
b'm' => PathSegment::MoveTo {
|
|
||||||
x: s.parse_list_number()? + dx,
|
|
||||||
y: s.parse_list_number()? + dy,
|
|
||||||
},
|
|
||||||
b'l' => PathSegment::LineTo {
|
|
||||||
x: s.parse_list_number()? + dx,
|
|
||||||
y: s.parse_list_number()? + dy,
|
|
||||||
},
|
|
||||||
b'h' => PathSegment::LineTo {
|
|
||||||
x: s.parse_list_number()? + dx,
|
|
||||||
y: state.pos.1,
|
|
||||||
},
|
|
||||||
b'v' => PathSegment::LineTo {
|
|
||||||
x: state.pos.0,
|
|
||||||
y: s.parse_list_number()? + dy,
|
|
||||||
},
|
|
||||||
b'c' => PathSegment::Bezier {
|
|
||||||
cp1x: s.parse_list_number()? + dx,
|
|
||||||
cp1y: s.parse_list_number()? + dy,
|
|
||||||
cp2x: s.parse_list_number()? + dx,
|
|
||||||
cp2y: s.parse_list_number()? + dy,
|
|
||||||
x: s.parse_list_number()? + dx,
|
|
||||||
y: s.parse_list_number()? + dy,
|
|
||||||
},
|
|
||||||
b's' => PathSegment::Bezier {
|
|
||||||
cp1x: state.cubic.0,
|
|
||||||
cp1y: state.cubic.1,
|
|
||||||
cp2x: s.parse_list_number()? + dx,
|
|
||||||
cp2y: s.parse_list_number()? + dy,
|
|
||||||
x: s.parse_list_number()? + dx,
|
|
||||||
y: s.parse_list_number()? + dy,
|
|
||||||
},
|
|
||||||
b'q' => PathSegment::Quadratic {
|
|
||||||
cpx: s.parse_list_number()? + dx,
|
|
||||||
cpy: s.parse_list_number()? + dy,
|
|
||||||
x: s.parse_list_number()? + dx,
|
|
||||||
y: s.parse_list_number()? + dy,
|
|
||||||
},
|
|
||||||
b't' => PathSegment::Quadratic {
|
|
||||||
cpx: state.quad.0,
|
|
||||||
cpy: state.quad.1,
|
|
||||||
x: s.parse_list_number()? + dx,
|
|
||||||
y: s.parse_list_number()? + dy,
|
|
||||||
},
|
|
||||||
b'a' => PathSegment::SvgArc {
|
|
||||||
radius_x: s.parse_list_number()?,
|
|
||||||
radius_y: s.parse_list_number()?,
|
|
||||||
rotation: s.parse_list_number()?,
|
|
||||||
large_arc: s.parse_flag()?,
|
|
||||||
sweep: s.parse_flag()?,
|
|
||||||
x: s.parse_list_number()? + dx,
|
|
||||||
y: s.parse_list_number()? + dy,
|
|
||||||
},
|
|
||||||
b'z' => PathSegment::ClosePath,
|
|
||||||
_ => return Err(crate::svgpath::Error),
|
|
||||||
};
|
|
||||||
|
|
||||||
match seg {
|
|
||||||
PathSegment::MoveTo { x, y } => {
|
|
||||||
state.start = (x, y);
|
|
||||||
state.pos = (x, y);
|
|
||||||
state.quad = (x, y);
|
|
||||||
state.cubic = (x, y);
|
|
||||||
},
|
|
||||||
PathSegment::LineTo { x, y } | PathSegment::SvgArc { x, y, .. } => {
|
|
||||||
state.pos = (x, y);
|
|
||||||
state.quad = (x, y);
|
|
||||||
state.cubic = (x, y);
|
|
||||||
},
|
|
||||||
PathSegment::Bezier {
|
|
||||||
cp2x, cp2y, x, y, ..
|
|
||||||
} => {
|
|
||||||
state.pos = (x, y);
|
|
||||||
state.quad = (x, y);
|
|
||||||
state.cubic = (x * 2.0 - cp2x, y * 2.0 - cp2y);
|
|
||||||
},
|
|
||||||
PathSegment::Quadratic { cpx, cpy, x, y, .. } => {
|
|
||||||
state.pos = (x, y);
|
|
||||||
state.quad = (x * 2.0 - cpx, y * 2.0 - cpy);
|
|
||||||
state.cubic = (x, y);
|
|
||||||
},
|
|
||||||
PathSegment::ClosePath => {
|
|
||||||
state.pos = state.start;
|
|
||||||
state.quad = state.start;
|
|
||||||
state.cubic = state.start;
|
|
||||||
},
|
|
||||||
_ => {},
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(seg)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[rustfmt::skip]
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use super::*;
|
|
||||||
|
|
||||||
macro_rules! test {
|
|
||||||
($name:ident, $text:expr, $( $seg:expr ),*) => (
|
|
||||||
#[test]
|
|
||||||
fn $name() {
|
|
||||||
let mut s = PathParser::new($text);
|
|
||||||
$(
|
|
||||||
assert_eq!(s.next().unwrap().unwrap(), $seg);
|
|
||||||
)*
|
|
||||||
|
|
||||||
if let Some(res) = s.next() {
|
|
||||||
assert!(res.is_err());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
test!(null, "", );
|
|
||||||
test!(not_a_path, "q", );
|
|
||||||
test!(not_a_move_to, "L 20 30", );
|
|
||||||
test!(stop_on_err_1, "M 10 20 L 30 40 L 50",
|
|
||||||
PathSegment::MoveTo { x: 10.0, y: 20.0 },
|
|
||||||
PathSegment::LineTo { x: 30.0, y: 40.0 }
|
|
||||||
);
|
|
||||||
|
|
||||||
test!(move_to_1, "M 10 20", PathSegment::MoveTo { x: 10.0, y: 20.0 });
|
|
||||||
test!(move_to_2, "m 10 20", PathSegment::MoveTo { x: 10.0, y: 20.0 });
|
|
||||||
test!(move_to_3, "M 10 20 30 40 50 60",
|
|
||||||
PathSegment::MoveTo { x: 10.0, y: 20.0 },
|
|
||||||
PathSegment::LineTo { x: 30.0, y: 40.0 },
|
|
||||||
PathSegment::LineTo { x: 50.0, y: 60.0 }
|
|
||||||
);
|
|
||||||
test!(move_to_4, "M 10 20 30 40 50 60 M 70 80 90 100 110 120",
|
|
||||||
PathSegment::MoveTo { x: 10.0, y: 20.0 },
|
|
||||||
PathSegment::LineTo { x: 30.0, y: 40.0 },
|
|
||||||
PathSegment::LineTo { x: 50.0, y: 60.0 },
|
|
||||||
PathSegment::MoveTo { x: 70.0, y: 80.0 },
|
|
||||||
PathSegment::LineTo { x: 90.0, y: 100.0 },
|
|
||||||
PathSegment::LineTo { x: 110.0, y: 120.0 }
|
|
||||||
);
|
|
||||||
|
|
||||||
test!(arc_to_1, "M 10 20 A 5 5 30 1 1 20 20",
|
|
||||||
PathSegment::MoveTo { x: 10.0, y: 20.0 },
|
|
||||||
PathSegment::SvgArc {
|
|
||||||
radius_x: 5.0, radius_y: 5.0,
|
|
||||||
rotation: 30.0,
|
|
||||||
large_arc: true, sweep: true,
|
|
||||||
x: 20.0, y: 20.0
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
test!(arc_to_2, "M 10 20 a 5 5 30 0 0 20 20",
|
|
||||||
PathSegment::MoveTo { x: 10.0, y: 20.0 },
|
|
||||||
PathSegment::SvgArc {
|
|
||||||
radius_x: 5.0, radius_y: 5.0,
|
|
||||||
rotation: 30.0,
|
|
||||||
large_arc: false, sweep: false,
|
|
||||||
x: 30.0, y: 40.0
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
test!(arc_to_10, "M10-20A5.5.3-4 010-.1",
|
|
||||||
PathSegment::MoveTo { x: 10.0, y: -20.0 },
|
|
||||||
PathSegment::SvgArc {
|
|
||||||
radius_x: 5.5, radius_y: 0.3,
|
|
||||||
rotation: -4.0,
|
|
||||||
large_arc: false, sweep: true,
|
|
||||||
x: 0.0, y: -0.1
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
test!(separator_1, "M 10 20 L 5 15 C 10 20 30 40 50 60",
|
|
||||||
PathSegment::MoveTo { x: 10.0, y: 20.0 },
|
|
||||||
PathSegment::LineTo { x: 5.0, y: 15.0 },
|
|
||||||
PathSegment::Bezier {
|
|
||||||
cp1x: 10.0, cp1y: 20.0,
|
|
||||||
cp2x: 30.0, cp2y: 40.0,
|
|
||||||
x: 50.0, y: 60.0,
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
test!(separator_2, "M 10, 20 L 5, 15 C 10, 20 30, 40 50, 60",
|
|
||||||
PathSegment::MoveTo { x: 10.0, y: 20.0 },
|
|
||||||
PathSegment::LineTo { x: 5.0, y: 15.0 },
|
|
||||||
PathSegment::Bezier {
|
|
||||||
cp1x: 10.0, cp1y: 20.0,
|
|
||||||
cp2x: 30.0, cp2y: 40.0,
|
|
||||||
x: 50.0, y: 60.0,
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
test!(separator_3, "M 10,20 L 5,15 C 10,20 30,40 50,60",
|
|
||||||
PathSegment::MoveTo { x: 10.0, y: 20.0 },
|
|
||||||
PathSegment::LineTo { x: 5.0, y: 15.0 },
|
|
||||||
PathSegment::Bezier {
|
|
||||||
cp1x: 10.0, cp1y: 20.0,
|
|
||||||
cp2x: 30.0, cp2y: 40.0,
|
|
||||||
x: 50.0, y: 60.0,
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
test!(separator_4, "M10, 20 L5, 15 C10, 20 30 40 50 60",
|
|
||||||
PathSegment::MoveTo { x: 10.0, y: 20.0 },
|
|
||||||
PathSegment::LineTo { x: 5.0, y: 15.0 },
|
|
||||||
PathSegment::Bezier {
|
|
||||||
cp1x: 10.0, cp1y: 20.0,
|
|
||||||
cp2x: 30.0, cp2y: 40.0,
|
|
||||||
x: 50.0, y: 60.0,
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
test!(separator_5, "M10 20V30H40V50H60Z",
|
|
||||||
PathSegment::MoveTo { x: 10.0, y: 20.0 },
|
|
||||||
PathSegment::LineTo { x: 10.0, y: 30.0 },
|
|
||||||
PathSegment::LineTo { x: 40.0, y: 30.0 },
|
|
||||||
PathSegment::LineTo { x: 40.0, y: 50.0 },
|
|
||||||
PathSegment::LineTo { x: 60.0, y: 50.0 },
|
|
||||||
PathSegment::ClosePath
|
|
||||||
);
|
|
||||||
|
|
||||||
test!(all_segments_1, "M 10 20 L 30 40 H 50 V 60 C 70 80 90 100 110 120 S 130 140 150 160
|
|
||||||
Q 170 180 190 200 T 210 220 A 50 50 30 1 1 230 240 Z",
|
|
||||||
PathSegment::MoveTo { x: 10.0, y: 20.0 },
|
|
||||||
PathSegment::LineTo { x: 30.0, y: 40.0 },
|
|
||||||
PathSegment::LineTo { x: 50.0, y: 40.0 },
|
|
||||||
PathSegment::LineTo { x: 50.0, y: 60.0 },
|
|
||||||
PathSegment::Bezier {
|
|
||||||
cp1x: 70.0, cp1y: 80.0,
|
|
||||||
cp2x: 90.0, cp2y: 100.0,
|
|
||||||
x: 110.0, y: 120.0,
|
|
||||||
},
|
|
||||||
PathSegment::Bezier {
|
|
||||||
cp1x: 130.0, cp1y: 140.0,
|
|
||||||
cp2x: 130.0, cp2y: 140.0,
|
|
||||||
x: 150.0, y: 160.0,
|
|
||||||
},
|
|
||||||
PathSegment::Quadratic {
|
|
||||||
cpx: 170.0, cpy: 180.0,
|
|
||||||
x: 190.0, y: 200.0,
|
|
||||||
},
|
|
||||||
PathSegment::Quadratic {
|
|
||||||
cpx: 210.0, cpy: 220.0,
|
|
||||||
x: 210.0, y: 220.0,
|
|
||||||
},
|
|
||||||
PathSegment::SvgArc {
|
|
||||||
radius_x: 50.0, radius_y: 50.0,
|
|
||||||
rotation: 30.0,
|
|
||||||
large_arc: true, sweep: true,
|
|
||||||
x: 230.0, y: 240.0
|
|
||||||
},
|
|
||||||
PathSegment::ClosePath
|
|
||||||
);
|
|
||||||
|
|
||||||
test!(all_segments_2, "m 10 20 l 30 40 h 50 v 60 c 70 80 90 100 110 120 s 130 140 150 160
|
|
||||||
q 170 180 190 200 t 210 220 a 50 50 30 1 1 230 240 z",
|
|
||||||
PathSegment::MoveTo { x: 10.0, y: 20.0 },
|
|
||||||
PathSegment::LineTo { x: 40.0, y: 60.0 },
|
|
||||||
PathSegment::LineTo { x: 90.0, y: 60.0 },
|
|
||||||
PathSegment::LineTo { x: 90.0, y: 120.0 },
|
|
||||||
PathSegment::Bezier {
|
|
||||||
cp1x: 160.0, cp1y: 200.0,
|
|
||||||
cp2x: 180.0, cp2y: 220.0,
|
|
||||||
x: 200.0, y: 240.0,
|
|
||||||
},
|
|
||||||
PathSegment::Bezier {
|
|
||||||
cp1x: 220.0, cp1y: 260.0, //?
|
|
||||||
cp2x: 330.0, cp2y: 380.0,
|
|
||||||
x: 350.0, y: 400.0,
|
|
||||||
},
|
|
||||||
PathSegment::Quadratic {
|
|
||||||
cpx: 520.0, cpy: 580.0,
|
|
||||||
x: 540.0, y: 600.0,
|
|
||||||
},
|
|
||||||
PathSegment::Quadratic {
|
|
||||||
cpx: 560.0, cpy: 620.0, //?
|
|
||||||
x: 750.0, y: 820.0
|
|
||||||
},
|
|
||||||
PathSegment::SvgArc {
|
|
||||||
radius_x: 50.0, radius_y: 50.0,
|
|
||||||
rotation: 30.0,
|
|
||||||
large_arc: true, sweep: true,
|
|
||||||
x: 980.0, y: 1060.0
|
|
||||||
},
|
|
||||||
PathSegment::ClosePath
|
|
||||||
);
|
|
||||||
|
|
||||||
test!(close_path_1, "M10 20 L 30 40 ZM 100 200 L 300 400",
|
|
||||||
PathSegment::MoveTo { x: 10.0, y: 20.0 },
|
|
||||||
PathSegment::LineTo { x: 30.0, y: 40.0 },
|
|
||||||
PathSegment::ClosePath,
|
|
||||||
PathSegment::MoveTo { x: 100.0, y: 200.0 },
|
|
||||||
PathSegment::LineTo { x: 300.0, y: 400.0 }
|
|
||||||
);
|
|
||||||
|
|
||||||
test!(close_path_2, "M10 20 L 30 40 zM 100 200 L 300 400",
|
|
||||||
PathSegment::MoveTo { x: 10.0, y: 20.0 },
|
|
||||||
PathSegment::LineTo { x: 30.0, y: 40.0 },
|
|
||||||
PathSegment::ClosePath,
|
|
||||||
PathSegment::MoveTo { x: 100.0, y: 200.0 },
|
|
||||||
PathSegment::LineTo { x: 300.0, y: 400.0 }
|
|
||||||
);
|
|
||||||
|
|
||||||
test!(close_path_3, "M10 20 L 30 40 Z Z Z",
|
|
||||||
PathSegment::MoveTo { x: 10.0, y: 20.0 },
|
|
||||||
PathSegment::LineTo { x: 30.0, y: 40.0 },
|
|
||||||
PathSegment::ClosePath,
|
|
||||||
PathSegment::ClosePath,
|
|
||||||
PathSegment::ClosePath
|
|
||||||
);
|
|
||||||
|
|
||||||
// first token should be EndOfStream
|
|
||||||
test!(invalid_1, "M\t.", );
|
|
||||||
|
|
||||||
// ClosePath can't be followed by a number
|
|
||||||
test!(invalid_2, "M 0 0 Z 2",
|
|
||||||
PathSegment::MoveTo { x: 0.0, y: 0.0 },
|
|
||||||
PathSegment::ClosePath
|
|
||||||
);
|
|
||||||
|
|
||||||
// ClosePath can be followed by any command
|
|
||||||
test!(invalid_3, "M 0 0 Z H 10",
|
|
||||||
PathSegment::MoveTo { x: 0.0, y: 0.0 },
|
|
||||||
PathSegment::ClosePath,
|
|
||||||
PathSegment::LineTo { x: 10.0, y: 0.0 }
|
|
||||||
);
|
|
||||||
}
|
|
|
@ -1,162 +0,0 @@
|
||||||
// Copyright 2018 the SVG Types Authors
|
|
||||||
// Copyright 2025 the Servo Authors
|
|
||||||
// SPDX-License-Identifier: Apache-2.0 OR MIT
|
|
||||||
|
|
||||||
use crate::svgpath::Error;
|
|
||||||
|
|
||||||
/// A streaming text parsing interface.
|
|
||||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
|
||||||
pub struct Stream<'a> {
|
|
||||||
text: &'a str,
|
|
||||||
pos: usize,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> From<&'a str> for Stream<'a> {
|
|
||||||
#[inline]
|
|
||||||
fn from(text: &'a str) -> Self {
|
|
||||||
Stream { text, pos: 0 }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> Stream<'a> {
|
|
||||||
/// Returns the current position in bytes.
|
|
||||||
#[inline]
|
|
||||||
pub fn pos(&self) -> usize {
|
|
||||||
self.pos
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Sets current position equal to the end.
|
|
||||||
///
|
|
||||||
/// Used to indicate end of parsing on error.
|
|
||||||
#[inline]
|
|
||||||
pub fn jump_to_end(&mut self) {
|
|
||||||
self.pos = self.text.len();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Checks if the stream is reached the end.
|
|
||||||
///
|
|
||||||
/// Any [`pos()`] value larger than original text length indicates stream end.
|
|
||||||
///
|
|
||||||
/// Accessing stream after reaching end via safe methods will produce
|
|
||||||
/// an error.
|
|
||||||
///
|
|
||||||
/// Accessing stream after reaching end via *_unchecked methods will produce
|
|
||||||
/// a Rust's bound checking error.
|
|
||||||
///
|
|
||||||
/// [`pos()`]: #method.pos
|
|
||||||
#[inline]
|
|
||||||
pub fn at_end(&self) -> bool {
|
|
||||||
self.pos >= self.text.len()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns a byte from a current stream position.
|
|
||||||
#[inline]
|
|
||||||
pub fn curr_byte(&self) -> Result<u8, Error> {
|
|
||||||
if self.at_end() {
|
|
||||||
return Err(Error);
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(self.curr_byte_unchecked())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns a byte from a current stream position.
|
|
||||||
///
|
|
||||||
/// # Panics
|
|
||||||
///
|
|
||||||
/// - if the current position is after the end of the data
|
|
||||||
#[inline]
|
|
||||||
pub fn curr_byte_unchecked(&self) -> u8 {
|
|
||||||
self.text.as_bytes()[self.pos]
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Checks that current byte is equal to provided.
|
|
||||||
///
|
|
||||||
/// Returns `false` if no bytes left.
|
|
||||||
#[inline]
|
|
||||||
pub fn is_curr_byte_eq(&self, c: u8) -> bool {
|
|
||||||
if !self.at_end() {
|
|
||||||
self.curr_byte_unchecked() == c
|
|
||||||
} else {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns a next byte from a current stream position.
|
|
||||||
#[inline]
|
|
||||||
pub fn next_byte(&self) -> Result<u8, Error> {
|
|
||||||
if self.pos + 1 >= self.text.len() {
|
|
||||||
return Err(Error);
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(self.text.as_bytes()[self.pos + 1])
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Advances by `n` bytes.
|
|
||||||
#[inline]
|
|
||||||
pub fn advance(&mut self, n: usize) {
|
|
||||||
debug_assert!(self.pos + n <= self.text.len());
|
|
||||||
self.pos += n;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Skips whitespaces.
|
|
||||||
///
|
|
||||||
/// Accepted values: `' ' \n \r \t`.
|
|
||||||
pub fn skip_spaces(&mut self) {
|
|
||||||
while !self.at_end() && matches!(self.curr_byte_unchecked(), b' ' | b'\t' | b'\n' | b'\r') {
|
|
||||||
self.advance(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Consumes bytes by the predicate.
|
|
||||||
pub fn skip_bytes<F>(&mut self, f: F)
|
|
||||||
where
|
|
||||||
F: Fn(&Stream<'_>, u8) -> bool,
|
|
||||||
{
|
|
||||||
while !self.at_end() {
|
|
||||||
let c = self.curr_byte_unchecked();
|
|
||||||
if f(self, c) {
|
|
||||||
self.advance(1);
|
|
||||||
} else {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Slices data from `pos` to the current position.
|
|
||||||
#[inline]
|
|
||||||
pub fn slice_back(&self, pos: usize) -> &'a str {
|
|
||||||
&self.text[pos..self.pos]
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Skips digits.
|
|
||||||
pub fn skip_digits(&mut self) {
|
|
||||||
self.skip_bytes(|_, c| c.is_ascii_digit());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub(crate) fn parse_list_separator(&mut self) {
|
|
||||||
if self.is_curr_byte_eq(b',') {
|
|
||||||
self.advance(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// By the SVG spec 'large-arc' and 'sweep' must contain only one char
|
|
||||||
// and can be written without any separators, e.g.: 10 20 30 01 10 20.
|
|
||||||
pub(crate) fn parse_flag(&mut self) -> Result<bool, Error> {
|
|
||||||
self.skip_spaces();
|
|
||||||
|
|
||||||
let c = self.curr_byte()?;
|
|
||||||
match c {
|
|
||||||
b'0' | b'1' => {
|
|
||||||
self.advance(1);
|
|
||||||
if self.is_curr_byte_eq(b',') {
|
|
||||||
self.advance(1);
|
|
||||||
}
|
|
||||||
self.skip_spaces();
|
|
||||||
|
|
||||||
Ok(c == b'1')
|
|
||||||
},
|
|
||||||
_ => Err(Error),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -20,6 +20,7 @@ crossbeam-channel = { workspace = true }
|
||||||
euclid = { workspace = true }
|
euclid = { workspace = true }
|
||||||
glow = { workspace = true }
|
glow = { workspace = true }
|
||||||
ipc-channel = { workspace = true }
|
ipc-channel = { workspace = true }
|
||||||
|
kurbo = { workspace = true, features = ["serde"] }
|
||||||
malloc_size_of = { workspace = true }
|
malloc_size_of = { workspace = true }
|
||||||
malloc_size_of_derive = { workspace = true }
|
malloc_size_of_derive = { workspace = true }
|
||||||
pixels = { path = "../../pixels" }
|
pixels = { path = "../../pixels" }
|
||||||
|
|
|
@ -5,8 +5,11 @@
|
||||||
use std::default::Default;
|
use std::default::Default;
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
|
||||||
|
use euclid::Angle;
|
||||||
|
use euclid::approxeq::ApproxEq;
|
||||||
use euclid::default::{Point2D, Rect, Size2D, Transform2D};
|
use euclid::default::{Point2D, Rect, Size2D, Transform2D};
|
||||||
use ipc_channel::ipc::IpcSender;
|
use ipc_channel::ipc::IpcSender;
|
||||||
|
use kurbo::{Affine, BezPath, ParamCurveNearest as _, PathEl, Point, Shape, Triangle};
|
||||||
use malloc_size_of_derive::MallocSizeOf;
|
use malloc_size_of_derive::MallocSizeOf;
|
||||||
use pixels::IpcSnapshot;
|
use pixels::IpcSnapshot;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
@ -14,57 +17,387 @@ use strum::{Display, EnumString};
|
||||||
use style::color::AbsoluteColor;
|
use style::color::AbsoluteColor;
|
||||||
use style::properties::style_structs::Font as FontStyleStruct;
|
use style::properties::style_structs::Font as FontStyleStruct;
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, Deserialize, MallocSizeOf, PartialEq, Serialize)]
|
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
|
||||||
pub enum PathSegment {
|
pub struct Path(pub BezPath);
|
||||||
ClosePath,
|
|
||||||
MoveTo {
|
pub struct IndexSizeError;
|
||||||
x: f32,
|
|
||||||
y: f32,
|
impl Path {
|
||||||
},
|
pub fn new() -> Self {
|
||||||
LineTo {
|
Self(BezPath::new())
|
||||||
x: f32,
|
}
|
||||||
y: f32,
|
|
||||||
},
|
pub fn from_svg(s: &str) -> Self {
|
||||||
Quadratic {
|
Self(BezPath::from_svg(s).unwrap_or_default())
|
||||||
cpx: f32,
|
}
|
||||||
cpy: f32,
|
|
||||||
x: f32,
|
pub fn transform(&mut self, transform: Transform2D<f64>) {
|
||||||
y: f32,
|
self.0.apply_affine(Affine::new(transform.to_array()));
|
||||||
},
|
}
|
||||||
Bezier {
|
|
||||||
cp1x: f32,
|
/// <https://html.spec.whatwg.org/multipage/#ensure-there-is-a-subpath>
|
||||||
cp1y: f32,
|
pub fn ensure_there_is_a_subpath(&mut self, x: f64, y: f64) {
|
||||||
cp2x: f32,
|
// The user agent must check to see if the path has its need new subpath flag set.
|
||||||
cp2y: f32,
|
if self.0.elements().is_empty() {
|
||||||
x: f32,
|
// If it does, then the user agent must create a new subpath with the point (x, y)
|
||||||
y: f32,
|
// as its first (and only) point,
|
||||||
},
|
// as if the moveTo() method had been called,
|
||||||
ArcTo {
|
// and must then unset the path's need new subpath flag.
|
||||||
cp1x: f32,
|
self.0.move_to((x, y));
|
||||||
cp1y: f32,
|
}
|
||||||
cp2x: f32,
|
}
|
||||||
cp2y: f32,
|
|
||||||
radius: f32,
|
/// <https://html.spec.whatwg.org/multipage/#dom-context-2d-closepath>
|
||||||
},
|
pub fn close_path(&mut self) {
|
||||||
Ellipse {
|
// must do nothing if the object's path has no subpaths
|
||||||
x: f32,
|
if matches!(self.0.elements().last(), None | Some(PathEl::ClosePath)) {
|
||||||
y: f32,
|
return;
|
||||||
radius_x: f32,
|
}
|
||||||
radius_y: f32,
|
// Otherwise, it must mark the last subpath as closed,
|
||||||
rotation: f32,
|
// create a new subpath whose first point is the same as the previous subpath's first point,
|
||||||
start_angle: f32,
|
// and finally add this new subpath to the path.
|
||||||
end_angle: f32,
|
self.0.close_path();
|
||||||
anticlockwise: bool,
|
}
|
||||||
},
|
|
||||||
SvgArc {
|
/// <https://html.spec.whatwg.org/multipage/#dom-context-2d-moveto>
|
||||||
radius_x: f32,
|
pub fn move_to(&mut self, x: f64, y: f64) {
|
||||||
radius_y: f32,
|
// Step 1. If either of the arguments are infinite or NaN, then return.
|
||||||
rotation: f32,
|
if !(x.is_finite() && y.is_finite()) {
|
||||||
large_arc: bool,
|
return;
|
||||||
sweep: bool,
|
}
|
||||||
x: f32,
|
|
||||||
y: f32,
|
// Step 2. Create a new subpath with the specified point as its first (and only) point.
|
||||||
},
|
self.0.move_to((x, y));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <https://html.spec.whatwg.org/multipage/#dom-context-2d-lineto>
|
||||||
|
pub fn line_to(&mut self, x: f64, y: f64) {
|
||||||
|
// Step 1. If either of the arguments are infinite or NaN, then return.
|
||||||
|
if !(x.is_finite() && y.is_finite()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Step 2. If the object's path has no subpaths, then ensure there is a subpath for (x, y).
|
||||||
|
self.ensure_there_is_a_subpath(x, y);
|
||||||
|
|
||||||
|
// Step 3. Otherwise, connect the last point in the subpath to the given point (x, y) using a straight line,
|
||||||
|
// and then add the given point (x, y) to the subpath.
|
||||||
|
self.0.line_to((x, y));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <https://html.spec.whatwg.org/multipage/#dom-context-2d-quadraticcurveto>
|
||||||
|
pub fn quadratic_curve_to(&mut self, cpx: f64, cpy: f64, x: f64, y: f64) {
|
||||||
|
// Step 1. If any of the arguments are infinite or NaN, then return.
|
||||||
|
if !(cpx.is_finite() && cpy.is_finite() && x.is_finite() && y.is_finite()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Step 2. Ensure there is a subpath for (cpx, cpy).
|
||||||
|
self.ensure_there_is_a_subpath(cpx, cpy);
|
||||||
|
|
||||||
|
// 3. Connect the last point in the subpath to the given point (x, y)
|
||||||
|
// using a quadratic Bézier curve with control point (cpx, cpy). [BEZIER]
|
||||||
|
// 4. Add the given point (x, y) to the subpath.
|
||||||
|
self.0.quad_to((cpx, cpy), (x, y));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <https://html.spec.whatwg.org/multipage/#dom-context-2d-beziercurveto>
|
||||||
|
pub fn bezier_curve_to(&mut self, cp1x: f64, cp1y: f64, cp2x: f64, cp2y: f64, x: f64, y: f64) {
|
||||||
|
// Step 1. If any of the arguments are infinite or NaN, then return.
|
||||||
|
if !(cp1x.is_finite() &&
|
||||||
|
cp1y.is_finite() &&
|
||||||
|
cp2x.is_finite() &&
|
||||||
|
cp2y.is_finite() &&
|
||||||
|
x.is_finite() &&
|
||||||
|
y.is_finite())
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Step 2. Ensure there is a subpath for (cp1x, cp1y).
|
||||||
|
self.ensure_there_is_a_subpath(cp1x, cp1y);
|
||||||
|
|
||||||
|
// Step 3. Connect the last point in the subpath to the given point (x, y)
|
||||||
|
// using a cubic Bézier curve with control points (cp1x, cp1y) and (cp2x, cp2y). [BEZIER]
|
||||||
|
// Step 4. Add the point (x, y) to the subpath.
|
||||||
|
self.0.curve_to((cp1x, cp1y), (cp2x, cp2y), (x, y));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <https://html.spec.whatwg.org/multipage/#dom-context-2d-arcto>
|
||||||
|
pub fn arc_to(
|
||||||
|
&mut self,
|
||||||
|
x1: f64,
|
||||||
|
y1: f64,
|
||||||
|
x2: f64,
|
||||||
|
y2: f64,
|
||||||
|
radius: f64,
|
||||||
|
) -> Result<(), IndexSizeError> {
|
||||||
|
// Step 1. If any of the arguments are infinite or NaN, then return.
|
||||||
|
if !(x1.is_finite() &&
|
||||||
|
y1.is_finite() &&
|
||||||
|
x2.is_finite() &&
|
||||||
|
y2.is_finite() &&
|
||||||
|
radius.is_finite())
|
||||||
|
{
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Step 2. Ensure there is a subpath for (x1, y1).
|
||||||
|
self.ensure_there_is_a_subpath(x1, y1);
|
||||||
|
|
||||||
|
// Step 3. If either radius is negative, then throw an "IndexSizeError" DOMException.
|
||||||
|
if radius.is_sign_negative() {
|
||||||
|
return Err(IndexSizeError);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Step 4. Let the point (x0, y0) be the last point in the subpath.
|
||||||
|
let Point { x: x0, y: y0 } = self.last_point().unwrap();
|
||||||
|
|
||||||
|
// Step 5. If the point (x0, y0) is equal to the point (x1, y1),
|
||||||
|
// or if the point (x1, y1) is equal to the point (x2, y2),
|
||||||
|
// or if radius is zero, then add the point (x1, y1) to the subpath,
|
||||||
|
// and connect that point to the previous point (x0, y0) by a straight line.
|
||||||
|
if ((x0, y0) == (x1, y1)) || ((x1, y1) == (x2, y2)) || radius.approx_eq(&0.0) {
|
||||||
|
self.0.line_to((x1, y1));
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Step 6. Otherwise, if the points (x0, y0), (x1, y1), and (x2, y2)
|
||||||
|
// all lie on a single straight line, then add the point (x1, y1) to the subpath,
|
||||||
|
// and connect that point to the previous point (x0, y0) by a straight line.
|
||||||
|
let direction = Triangle::from_coords((x0, y0), (x1, y1), (x2, y2)).area();
|
||||||
|
if direction == 0.0 {
|
||||||
|
self.0.line_to((x1, y1));
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Step 7. Otherwise, let The Arc be the shortest arc given by circumference of the circle
|
||||||
|
// that has radius radius, and that has one point tangent to the half-infinite line
|
||||||
|
// that crosses the point (x0, y0) and ends at the point (x1, y1),
|
||||||
|
// and that has a different point tangent to the half-infinite line that ends at the point (x1, y1)
|
||||||
|
// and crosses the point (x2, y2).
|
||||||
|
// The points at which this circle touches these two lines are called the start
|
||||||
|
// and end tangent points respectively.
|
||||||
|
// Connect the point (x0, y0) to the start tangent point by a straight line,
|
||||||
|
// adding the start tangent point to the subpath,
|
||||||
|
// and then connect the start tangent point to the end tangent point by The Arc,
|
||||||
|
// adding the end tangent point to the subpath.
|
||||||
|
|
||||||
|
let a2 = (x0 - x1).powi(2) + (y0 - y1).powi(2);
|
||||||
|
let b2 = (x1 - x2).powi(2) + (y1 - y2).powi(2);
|
||||||
|
let d = {
|
||||||
|
let c2 = (x0 - x2).powi(2) + (y0 - y2).powi(2);
|
||||||
|
let cosx = (a2 + b2 - c2) / (2.0 * (a2 * b2).sqrt());
|
||||||
|
let sinx = (1.0 - cosx.powi(2)).sqrt();
|
||||||
|
radius / ((1.0 - cosx) / sinx)
|
||||||
|
};
|
||||||
|
|
||||||
|
// first tangent point
|
||||||
|
let anx = (x1 - x0) / a2.sqrt();
|
||||||
|
let any = (y1 - y0) / a2.sqrt();
|
||||||
|
let tp1 = Point2D::new(x1 - anx * d, y1 - any * d);
|
||||||
|
|
||||||
|
// second tangent point
|
||||||
|
let bnx = (x1 - x2) / b2.sqrt();
|
||||||
|
let bny = (y1 - y2) / b2.sqrt();
|
||||||
|
let tp2 = Point2D::new(x1 - bnx * d, y1 - bny * d);
|
||||||
|
|
||||||
|
// arc center and angles
|
||||||
|
let anticlockwise = direction < 0.0;
|
||||||
|
let cx = tp1.x + any * radius * if anticlockwise { 1.0 } else { -1.0 };
|
||||||
|
let cy = tp1.y - anx * radius * if anticlockwise { 1.0 } else { -1.0 };
|
||||||
|
let angle_start = (tp1.y - cy).atan2(tp1.x - cx);
|
||||||
|
let angle_end = (tp2.y - cy).atan2(tp2.x - cx);
|
||||||
|
|
||||||
|
self.0.line_to((tp1.x, tp2.x));
|
||||||
|
|
||||||
|
self.arc(cx, cy, radius, angle_start, angle_end, anticlockwise)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn last_point(&mut self) -> Option<Point> {
|
||||||
|
// https://github.com/linebender/kurbo/pull/462
|
||||||
|
match self.0.elements().last()? {
|
||||||
|
PathEl::ClosePath => self
|
||||||
|
.0
|
||||||
|
.elements()
|
||||||
|
.iter()
|
||||||
|
.rev()
|
||||||
|
.skip(1)
|
||||||
|
.take_while(|el| !matches!(el, PathEl::ClosePath))
|
||||||
|
.last()
|
||||||
|
.and_then(|el| el.end_point()),
|
||||||
|
other => other.end_point(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::too_many_arguments)]
|
||||||
|
/// <https://html.spec.whatwg.org/multipage/#dom-context-2d-arc>
|
||||||
|
pub fn arc(
|
||||||
|
&mut self,
|
||||||
|
x: f64,
|
||||||
|
y: f64,
|
||||||
|
radius: f64,
|
||||||
|
start_angle: f64,
|
||||||
|
end_angle: f64,
|
||||||
|
counterclockwise: bool,
|
||||||
|
) -> Result<(), IndexSizeError> {
|
||||||
|
// ellipse() with both radii are equal and rotation is 0.
|
||||||
|
self.ellipse(
|
||||||
|
x,
|
||||||
|
y,
|
||||||
|
radius,
|
||||||
|
radius,
|
||||||
|
0.,
|
||||||
|
start_angle,
|
||||||
|
end_angle,
|
||||||
|
counterclockwise,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::too_many_arguments)]
|
||||||
|
/// <https://html.spec.whatwg.org/multipage/#dom-context-2d-ellipse>
|
||||||
|
pub fn ellipse(
|
||||||
|
&mut self,
|
||||||
|
x: f64,
|
||||||
|
y: f64,
|
||||||
|
radius_x: f64,
|
||||||
|
radius_y: f64,
|
||||||
|
rotation_angle: f64,
|
||||||
|
start_angle: f64,
|
||||||
|
end_angle: f64,
|
||||||
|
counterclockwise: bool,
|
||||||
|
) -> Result<(), IndexSizeError> {
|
||||||
|
// Step 1. If any of the arguments are infinite or NaN, then return.
|
||||||
|
if !(x.is_finite() &&
|
||||||
|
y.is_finite() &&
|
||||||
|
radius_x.is_finite() &&
|
||||||
|
radius_y.is_finite() &&
|
||||||
|
rotation_angle.is_finite() &&
|
||||||
|
start_angle.is_finite() &&
|
||||||
|
end_angle.is_finite())
|
||||||
|
{
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Step 2. If either radiusX or radiusY are negative, then throw an "IndexSizeError" DOMException.
|
||||||
|
if radius_x.is_sign_negative() || radius_y.is_sign_negative() {
|
||||||
|
return Err(IndexSizeError);
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut start = Angle::radians(start_angle);
|
||||||
|
let mut end = Angle::radians(end_angle);
|
||||||
|
|
||||||
|
// Wrap angles mod 2 * PI if necessary
|
||||||
|
if !counterclockwise && start > end + Angle::two_pi() ||
|
||||||
|
counterclockwise && end > start + Angle::two_pi()
|
||||||
|
{
|
||||||
|
start = start.positive();
|
||||||
|
end = end.positive();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate the total arc we're going to sweep.
|
||||||
|
let sweep = match counterclockwise {
|
||||||
|
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 = kurbo::Arc::new(
|
||||||
|
(x, y),
|
||||||
|
(radius_x, radius_y),
|
||||||
|
start.radians,
|
||||||
|
sweep.radians,
|
||||||
|
rotation_angle,
|
||||||
|
);
|
||||||
|
|
||||||
|
let mut iter = arc.path_elements(0.01);
|
||||||
|
let kurbo::PathEl::MoveTo(start_point) = iter.next().unwrap() else {
|
||||||
|
unreachable!()
|
||||||
|
};
|
||||||
|
|
||||||
|
self.line_to(start_point.x, start_point.y);
|
||||||
|
|
||||||
|
if sweep.radians.abs() > 1e-3 {
|
||||||
|
self.0.extend(iter);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <https://html.spec.whatwg.org/multipage/#dom-context-2d-rect>
|
||||||
|
pub fn rect(&mut self, x: f64, y: f64, w: f64, h: f64) {
|
||||||
|
// Step 1. If any of the arguments are infinite or NaN, then return.
|
||||||
|
if !(x.is_finite() && y.is_finite() && w.is_finite() && h.is_finite()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Step 2. Create a new subpath containing just the four points
|
||||||
|
// (x, y), (x+w, y), (x+w, y+h), (x, y+h), in that order,
|
||||||
|
// with those four points connected by straight lines.
|
||||||
|
self.0.move_to((x, y));
|
||||||
|
self.0.line_to((x + w, y));
|
||||||
|
self.0.line_to((x + w, y + h));
|
||||||
|
self.0.line_to((x, y + h));
|
||||||
|
|
||||||
|
// Step 3. Mark the subpath as closed.
|
||||||
|
self.0.close_path();
|
||||||
|
|
||||||
|
// Step 4. Create a new subpath with the point (x, y) as the only point in the subpath.
|
||||||
|
self.0.move_to((x, y));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <https://html.spec.whatwg.org/multipage/#dom-context-2d-ispointinpath>
|
||||||
|
pub fn is_point_in_path(&self, x: f64, y: f64, fill_rule: FillRule) -> bool {
|
||||||
|
let p = Point::new(x, y);
|
||||||
|
// Step 1. If x or y are infinite or NaN, then return false.
|
||||||
|
if !p.is_finite() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Step 2. If the point given by the x and y coordinates,
|
||||||
|
// when treated as coordinates in the canvas coordinate space unaffected by the current transformation,
|
||||||
|
// is inside the intended path for path as determined by the fill rule indicated by fillRule,
|
||||||
|
// then return true.
|
||||||
|
// Open subpaths must be implicitly closed when computing the area inside the path,
|
||||||
|
// without affecting the actual subpaths.
|
||||||
|
let mut path = self.clone();
|
||||||
|
path.close_path();
|
||||||
|
let winding = path.0.winding(p);
|
||||||
|
let is_inside = match fill_rule {
|
||||||
|
FillRule::Nonzero => winding != 0,
|
||||||
|
FillRule::Evenodd => (winding % 2) != 0,
|
||||||
|
};
|
||||||
|
if is_inside {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
// Points on the path itself must be considered to be inside the path.
|
||||||
|
path.0
|
||||||
|
.segments()
|
||||||
|
.any(|seg| seg.nearest(p, 0.00001).distance_sq < 0.00001)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn bounding_box(&self) -> Rect<f64> {
|
||||||
|
let rect = self.0.control_box();
|
||||||
|
Rect::new(
|
||||||
|
Point2D::new(rect.origin().x, rect.origin().y),
|
||||||
|
Size2D::new(rect.width(), rect.height()),
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||||
|
@ -95,17 +428,16 @@ pub enum Canvas2dMsg {
|
||||||
BezierCurveTo(Point2D<f32>, Point2D<f32>, Point2D<f32>),
|
BezierCurveTo(Point2D<f32>, Point2D<f32>, Point2D<f32>),
|
||||||
ClearRect(Rect<f32>),
|
ClearRect(Rect<f32>),
|
||||||
Clip,
|
Clip,
|
||||||
ClipPath(Vec<PathSegment>),
|
ClipPath(Path),
|
||||||
ClosePath,
|
ClosePath,
|
||||||
Ellipse(Point2D<f32>, f32, f32, f32, f32, f32, bool),
|
Ellipse(Point2D<f32>, f32, f32, f32, f32, f32, bool),
|
||||||
Fill(FillOrStrokeStyle),
|
Fill(FillOrStrokeStyle),
|
||||||
FillPath(FillOrStrokeStyle, Vec<PathSegment>),
|
FillPath(FillOrStrokeStyle, Path),
|
||||||
FillText(String, f64, f64, Option<f64>, FillOrStrokeStyle, bool),
|
FillText(String, f64, f64, Option<f64>, FillOrStrokeStyle, bool),
|
||||||
FillRect(Rect<f32>, FillOrStrokeStyle),
|
FillRect(Rect<f32>, FillOrStrokeStyle),
|
||||||
GetImageData(Rect<u32>, Size2D<u32>, IpcSender<IpcSnapshot>),
|
GetImageData(Rect<u32>, Size2D<u32>, IpcSender<IpcSnapshot>),
|
||||||
GetTransform(IpcSender<Transform2D<f32>>),
|
GetTransform(IpcSender<Transform2D<f32>>),
|
||||||
IsPointInCurrentPath(f64, f64, FillRule, IpcSender<bool>),
|
IsPointInCurrentPath(f64, f64, FillRule, IpcSender<bool>),
|
||||||
IsPointInPath(Vec<PathSegment>, f64, f64, FillRule, IpcSender<bool>),
|
|
||||||
LineTo(Point2D<f32>),
|
LineTo(Point2D<f32>),
|
||||||
MoveTo(Point2D<f32>),
|
MoveTo(Point2D<f32>),
|
||||||
MeasureText(String, IpcSender<TextMetrics>),
|
MeasureText(String, IpcSender<TextMetrics>),
|
||||||
|
@ -116,7 +448,7 @@ pub enum Canvas2dMsg {
|
||||||
SaveContext,
|
SaveContext,
|
||||||
StrokeRect(Rect<f32>, FillOrStrokeStyle),
|
StrokeRect(Rect<f32>, FillOrStrokeStyle),
|
||||||
Stroke(FillOrStrokeStyle),
|
Stroke(FillOrStrokeStyle),
|
||||||
StrokePath(FillOrStrokeStyle, Vec<PathSegment>),
|
StrokePath(FillOrStrokeStyle, Path),
|
||||||
SetLineWidth(f32),
|
SetLineWidth(f32),
|
||||||
SetLineCap(LineCapStyle),
|
SetLineCap(LineCapStyle),
|
||||||
SetLineJoin(LineJoinStyle),
|
SetLineJoin(LineJoinStyle),
|
||||||
|
|
|
@ -1,3 +0,0 @@
|
||||||
[2d.path.arc.shape.1.html]
|
|
||||||
[arc() from 0 to pi does not draw anything in the wrong half]
|
|
||||||
expected: FAIL
|
|
|
@ -1,4 +0,0 @@
|
||||||
[2d.path.isPointInpath.multi.path.html]
|
|
||||||
[Verify the winding rule in isPointInPath works for path object.]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
|
@ -1,4 +0,0 @@
|
||||||
[2d.path.arc.shape.1.html]
|
|
||||||
[arc() from 0 to pi does not draw anything in the wrong half]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
|
@ -1,4 +0,0 @@
|
||||||
[2d.path.arc.shape.1.worker.html]
|
|
||||||
[arc() from 0 to pi does not draw anything in the wrong half]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
|
@ -1,3 +0,0 @@
|
||||||
[2d.path.isPointInpath.multi.path.html]
|
|
||||||
[Verify the winding rule in isPointInPath works for path object.]
|
|
||||||
expected: FAIL
|
|
|
@ -1,3 +0,0 @@
|
||||||
[2d.path.isPointInpath.multi.path.worker.html]
|
|
||||||
[Verify the winding rule in isPointInPath works for path object.]
|
|
||||||
expected: FAIL
|
|
Loading…
Add table
Add a link
Reference in a new issue