mirror of
https://github.com/servo/servo.git
synced 2025-06-06 16:45:39 +00:00
parent
f0ea3c6150
commit
251eeb2c2d
53 changed files with 1566 additions and 262 deletions
|
@ -27,6 +27,77 @@ use webrender_traits::{CrossProcessCompositorApi, ImageUpdate, SerializableImage
|
|||
|
||||
use crate::raqote_backend::Repetition;
|
||||
|
||||
fn to_path(path: &[PathSegment], mut builder: Box<dyn GenericPathBuilder>) -> Path {
|
||||
let mut build_ref = PathBuilderRef {
|
||||
builder: &mut builder,
|
||||
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),
|
||||
),
|
||||
}
|
||||
}
|
||||
builder.finish()
|
||||
}
|
||||
|
||||
/// The canvas data stores a state machine for the current status of
|
||||
/// the path data and any relevant transformations that are
|
||||
/// applied to it. The Azure drawing API expects the path to be in
|
||||
|
@ -125,6 +196,15 @@ pub trait GenericPathBuilder {
|
|||
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>,
|
||||
);
|
||||
fn finish(&mut self) -> Path;
|
||||
}
|
||||
|
||||
|
@ -192,6 +272,69 @@ impl PathBuilderRef<'_> {
|
|||
.arc(center, radius, start_angle, end_angle, ccw);
|
||||
}
|
||||
|
||||
fn arc_to(&mut self, cp1: &Point2D<f32>, cp2: &Point2D<f32>, radius: f32) {
|
||||
let cp0 = if let (Some(inverse), Some(point)) =
|
||||
(self.transform.inverse(), self.builder.get_current_point())
|
||||
{
|
||||
inverse.transform_point(Point2D::new(point.x, point.y))
|
||||
} else {
|
||||
*cp1
|
||||
};
|
||||
if (cp0.x == cp1.x && cp0.y == cp1.y) || cp1 == cp2 || radius == 0.0 {
|
||||
self.line_to(cp1);
|
||||
return;
|
||||
}
|
||||
|
||||
// if all three control points lie on a single straight line,
|
||||
// connect the first two by a straight line
|
||||
let direction = (cp2.x - cp1.x) * (cp0.y - cp1.y) + (cp2.y - cp1.y) * (cp1.x - cp0.x);
|
||||
if direction == 0.0 {
|
||||
self.line_to(cp1);
|
||||
return;
|
||||
}
|
||||
|
||||
// otherwise, draw the Arc
|
||||
let a2 = (cp0.x - cp1.x).powi(2) + (cp0.y - cp1.y).powi(2);
|
||||
let b2 = (cp1.x - cp2.x).powi(2) + (cp1.y - cp2.y).powi(2);
|
||||
let d = {
|
||||
let c2 = (cp0.x - cp2.x).powi(2) + (cp0.y - cp2.y).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 = (cp1.x - cp0.x) / a2.sqrt();
|
||||
let any = (cp1.y - cp0.y) / a2.sqrt();
|
||||
let tp1 = Point2D::new(cp1.x - anx * d, cp1.y - any * d);
|
||||
|
||||
// second tangent point
|
||||
let bnx = (cp1.x - cp2.x) / b2.sqrt();
|
||||
let bny = (cp1.y - cp2.y) / b2.sqrt();
|
||||
let tp2 = Point2D::new(cp1.x - bnx * d, cp1.y - 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.line_to(&self.transform.transform_point(tp1));
|
||||
if [cx, cy, angle_start, angle_end]
|
||||
.iter()
|
||||
.all(|x| x.is_finite())
|
||||
{
|
||||
self.arc(
|
||||
&self.transform.transform_point(Point2D::new(cx, cy)),
|
||||
radius,
|
||||
angle_start,
|
||||
angle_end,
|
||||
anticlockwise,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn ellipse(
|
||||
&mut self,
|
||||
|
@ -215,11 +358,24 @@ impl PathBuilderRef<'_> {
|
|||
);
|
||||
}
|
||||
|
||||
fn current_point(&mut self) -> Option<Point2D<f32>> {
|
||||
let inverse = self.transform.inverse()?;
|
||||
self.builder
|
||||
.get_current_point()
|
||||
.map(|point| inverse.transform_point(Point2D::new(point.x, point.y)))
|
||||
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,
|
||||
);
|
||||
}
|
||||
|
||||
fn close(&mut self) {
|
||||
|
@ -957,6 +1113,20 @@ impl<'a> CanvasData<'a> {
|
|||
);
|
||||
}
|
||||
|
||||
pub fn fill_path(&mut self, path: &[PathSegment]) {
|
||||
if self.state.fill_style.is_zero_size_gradient() {
|
||||
return; // Paint nothing if gradient size is zero.
|
||||
}
|
||||
|
||||
let path = to_path(path, self.drawtarget.create_path_builder());
|
||||
|
||||
self.drawtarget.fill(
|
||||
&path,
|
||||
self.state.fill_style.clone(),
|
||||
&self.state.draw_options,
|
||||
);
|
||||
}
|
||||
|
||||
pub fn stroke(&mut self) {
|
||||
if self.state.stroke_style.is_zero_size_gradient() {
|
||||
return; // Paint nothing if gradient size is zero.
|
||||
|
@ -971,12 +1141,32 @@ impl<'a> CanvasData<'a> {
|
|||
);
|
||||
}
|
||||
|
||||
pub fn stroke_path(&mut self, path: &[PathSegment]) {
|
||||
if self.state.stroke_style.is_zero_size_gradient() {
|
||||
return; // Paint nothing if gradient size is zero.
|
||||
}
|
||||
|
||||
let path = to_path(path, self.drawtarget.create_path_builder());
|
||||
|
||||
self.drawtarget.stroke(
|
||||
&path,
|
||||
self.state.stroke_style.clone(),
|
||||
&self.state.stroke_opts,
|
||||
&self.state.draw_options,
|
||||
);
|
||||
}
|
||||
|
||||
pub fn clip(&mut self) {
|
||||
self.ensure_path();
|
||||
let path = self.path().clone();
|
||||
self.drawtarget.push_clip(&path);
|
||||
}
|
||||
|
||||
pub fn clip_path(&mut self, path: &[PathSegment]) {
|
||||
let path = to_path(path, self.drawtarget.create_path_builder());
|
||||
self.drawtarget.push_clip(&path);
|
||||
}
|
||||
|
||||
pub fn is_point_in_path(
|
||||
&mut self,
|
||||
x: f64,
|
||||
|
@ -996,6 +1186,26 @@ impl<'a> CanvasData<'a> {
|
|||
chan.send(result).unwrap();
|
||||
}
|
||||
|
||||
pub fn is_point_in_path_(
|
||||
&mut self,
|
||||
path: &[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 result = to_path(path, self.drawtarget.create_path_builder()).contains_point(
|
||||
x,
|
||||
y,
|
||||
path_transform,
|
||||
);
|
||||
chan.send(result).unwrap();
|
||||
}
|
||||
|
||||
pub fn move_to(&mut self, point: &Point2D<f32>) {
|
||||
self.path_builder().move_to(point);
|
||||
}
|
||||
|
@ -1105,69 +1315,7 @@ impl<'a> CanvasData<'a> {
|
|||
}
|
||||
|
||||
pub fn arc_to(&mut self, cp1: &Point2D<f32>, cp2: &Point2D<f32>, radius: f32) {
|
||||
let cp0 = match self.path_builder().current_point() {
|
||||
Some(p) => p,
|
||||
None => {
|
||||
self.path_builder().move_to(cp1);
|
||||
*cp1
|
||||
},
|
||||
};
|
||||
let cp1 = *cp1;
|
||||
let cp2 = *cp2;
|
||||
|
||||
if (cp0.x == cp1.x && cp0.y == cp1.y) || cp1 == cp2 || radius == 0.0 {
|
||||
self.line_to(&cp1);
|
||||
return;
|
||||
}
|
||||
|
||||
// if all three control points lie on a single straight line,
|
||||
// connect the first two by a straight line
|
||||
let direction = (cp2.x - cp1.x) * (cp0.y - cp1.y) + (cp2.y - cp1.y) * (cp1.x - cp0.x);
|
||||
if direction == 0.0 {
|
||||
self.line_to(&cp1);
|
||||
return;
|
||||
}
|
||||
|
||||
// otherwise, draw the Arc
|
||||
let a2 = (cp0.x - cp1.x).powi(2) + (cp0.y - cp1.y).powi(2);
|
||||
let b2 = (cp1.x - cp2.x).powi(2) + (cp1.y - cp2.y).powi(2);
|
||||
let d = {
|
||||
let c2 = (cp0.x - cp2.x).powi(2) + (cp0.y - cp2.y).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 = (cp1.x - cp0.x) / a2.sqrt();
|
||||
let any = (cp1.y - cp0.y) / a2.sqrt();
|
||||
let tp1 = Point2D::new(cp1.x - anx * d, cp1.y - any * d);
|
||||
|
||||
// second tangent point
|
||||
let bnx = (cp1.x - cp2.x) / b2.sqrt();
|
||||
let bny = (cp1.y - cp2.y) / b2.sqrt();
|
||||
let tp2 = Point2D::new(cp1.x - bnx * d, cp1.y - 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.line_to(&tp1);
|
||||
if [cx, cy, angle_start, angle_end]
|
||||
.iter()
|
||||
.all(|x| x.is_finite())
|
||||
{
|
||||
self.arc(
|
||||
&Point2D::new(cx, cy),
|
||||
radius,
|
||||
angle_start,
|
||||
angle_end,
|
||||
anticlockwise,
|
||||
);
|
||||
}
|
||||
self.path_builder().arc_to(cp1, cp2, radius);
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
|
|
|
@ -139,14 +139,26 @@ impl<'a> CanvasPaintThread<'a> {
|
|||
self.canvas(canvas_id).set_fill_style(style);
|
||||
self.canvas(canvas_id).fill();
|
||||
},
|
||||
Canvas2dMsg::FillPath(style, path) => {
|
||||
self.canvas(canvas_id).set_fill_style(style);
|
||||
self.canvas(canvas_id).fill_path(&path[..]);
|
||||
},
|
||||
Canvas2dMsg::Stroke(style) => {
|
||||
self.canvas(canvas_id).set_stroke_style(style);
|
||||
self.canvas(canvas_id).stroke();
|
||||
},
|
||||
Canvas2dMsg::StrokePath(style, path) => {
|
||||
self.canvas(canvas_id).set_stroke_style(style);
|
||||
self.canvas(canvas_id).stroke_path(&path[..]);
|
||||
},
|
||||
Canvas2dMsg::Clip => self.canvas(canvas_id).clip(),
|
||||
Canvas2dMsg::IsPointInPath(x, y, fill_rule, chan) => self
|
||||
Canvas2dMsg::ClipPath(path) => self.canvas(canvas_id).clip_path(&path[..]),
|
||||
Canvas2dMsg::IsPointInCurrentPath(x, y, fill_rule, chan) => self
|
||||
.canvas(canvas_id)
|
||||
.is_point_in_path(x, y, fill_rule, chan),
|
||||
Canvas2dMsg::IsPointInPath(path, x, y, fill_rule, chan) => self
|
||||
.canvas(canvas_id)
|
||||
.is_point_in_path_(&path[..], x, y, fill_rule, chan),
|
||||
Canvas2dMsg::DrawImage(
|
||||
ref image_data,
|
||||
image_size,
|
||||
|
|
|
@ -813,6 +813,32 @@ impl GenericPathBuilder for PathBuilder {
|
|||
});
|
||||
}
|
||||
|
||||
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 get_current_point(&mut self) -> Option<Point2D<f32>> {
|
||||
let path = self.finish();
|
||||
self.0 = Some(path.as_raqote().clone().into());
|
||||
|
|
|
@ -9,7 +9,7 @@ use std::sync::Arc;
|
|||
|
||||
use canvas_traits::canvas::{
|
||||
Canvas2dMsg, CanvasId, CanvasMsg, CompositionOrBlending, Direction, FillOrStrokeStyle,
|
||||
FillRule, LineCapStyle, LineJoinStyle, LinearGradientStyle, RadialGradientStyle,
|
||||
FillRule, LineCapStyle, LineJoinStyle, LinearGradientStyle, PathSegment, RadialGradientStyle,
|
||||
RepetitionStyle, TextAlign, TextBaseline, TextMetrics as CanvasTextMetrics,
|
||||
};
|
||||
use cssparser::color::clamp_unit_f32;
|
||||
|
@ -1521,18 +1521,36 @@ impl CanvasState {
|
|||
self.send_canvas_2d_msg(Canvas2dMsg::Fill(style));
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/#dom-context-2d-fill
|
||||
pub(crate) fn fill_(&self, path: Vec<PathSegment>, _fill_rule: CanvasFillRule) {
|
||||
// TODO: Process fill rule
|
||||
let style = self.state.borrow().fill_style.to_fill_or_stroke_style();
|
||||
self.send_canvas_2d_msg(Canvas2dMsg::FillPath(style, path));
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/#dom-context-2d-stroke
|
||||
pub(crate) fn stroke(&self) {
|
||||
let style = self.state.borrow().stroke_style.to_fill_or_stroke_style();
|
||||
self.send_canvas_2d_msg(Canvas2dMsg::Stroke(style));
|
||||
}
|
||||
|
||||
pub(crate) fn stroke_(&self, path: Vec<PathSegment>) {
|
||||
let style = self.state.borrow().stroke_style.to_fill_or_stroke_style();
|
||||
self.send_canvas_2d_msg(Canvas2dMsg::StrokePath(style, path));
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/#dom-context-2d-clip
|
||||
pub(crate) fn clip(&self, _fill_rule: CanvasFillRule) {
|
||||
// TODO: Process fill rule
|
||||
self.send_canvas_2d_msg(Canvas2dMsg::Clip);
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/#dom-context-2d-clip
|
||||
pub(crate) fn clip_(&self, path: Vec<PathSegment>, _fill_rule: CanvasFillRule) {
|
||||
// TODO: Process fill rule
|
||||
self.send_canvas_2d_msg(Canvas2dMsg::ClipPath(path));
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/#dom-context-2d-ispointinpath
|
||||
pub(crate) fn is_point_in_path(
|
||||
&self,
|
||||
|
@ -1551,7 +1569,30 @@ impl CanvasState {
|
|||
};
|
||||
let (sender, receiver) =
|
||||
profiled_ipc::channel::<bool>(global.time_profiler_chan().clone()).unwrap();
|
||||
self.send_canvas_2d_msg(Canvas2dMsg::IsPointInPath(x, y, fill_rule, sender));
|
||||
self.send_canvas_2d_msg(Canvas2dMsg::IsPointInCurrentPath(x, y, fill_rule, sender));
|
||||
receiver.recv().unwrap()
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/#dom-context-2d-ispointinpath
|
||||
pub(crate) fn is_point_in_path_(
|
||||
&self,
|
||||
global: &GlobalScope,
|
||||
path: Vec<PathSegment>,
|
||||
x: f64,
|
||||
y: f64,
|
||||
fill_rule: CanvasFillRule,
|
||||
) -> bool {
|
||||
if !(x.is_finite() && y.is_finite()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
let fill_rule = match fill_rule {
|
||||
CanvasFillRule::Nonzero => FillRule::Nonzero,
|
||||
CanvasFillRule::Evenodd => FillRule::Evenodd,
|
||||
};
|
||||
let (sender, receiver) =
|
||||
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()
|
||||
}
|
||||
|
||||
|
|
|
@ -32,6 +32,7 @@ use crate::dom::globalscope::GlobalScope;
|
|||
use crate::dom::htmlcanvaselement::HTMLCanvasElement;
|
||||
use crate::dom::imagedata::ImageData;
|
||||
use crate::dom::node::{Node, NodeDamage, NodeTraits};
|
||||
use crate::dom::path2d::Path2D;
|
||||
use crate::dom::textmetrics::TextMetrics;
|
||||
use crate::script_runtime::CanGc;
|
||||
|
||||
|
@ -288,23 +289,46 @@ impl CanvasRenderingContext2DMethods<crate::DomTypeHolder> for CanvasRenderingCo
|
|||
self.mark_as_dirty();
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/#dom-context-2d-fill
|
||||
fn Fill_(&self, path: &Path2D, fill_rule: CanvasFillRule) {
|
||||
self.canvas_state.fill_(path.segments(), fill_rule);
|
||||
self.mark_as_dirty();
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/#dom-context-2d-stroke
|
||||
fn Stroke(&self) {
|
||||
self.canvas_state.stroke();
|
||||
self.mark_as_dirty();
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/#dom-context-2d-stroke
|
||||
fn Stroke_(&self, path: &Path2D) {
|
||||
self.canvas_state.stroke_(path.segments());
|
||||
self.mark_as_dirty();
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/#dom-context-2d-clip
|
||||
fn Clip(&self, fill_rule: CanvasFillRule) {
|
||||
self.canvas_state.clip(fill_rule)
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/#dom-context-2d-clip
|
||||
fn Clip_(&self, path: &Path2D, fill_rule: CanvasFillRule) {
|
||||
self.canvas_state.clip_(path.segments(), fill_rule)
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/#dom-context-2d-ispointinpath
|
||||
fn IsPointInPath(&self, x: f64, y: f64, fill_rule: CanvasFillRule) -> bool {
|
||||
self.canvas_state
|
||||
.is_point_in_path(&self.global(), x, y, fill_rule)
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/#dom-context-2d-ispointinpath
|
||||
fn IsPointInPath_(&self, path: &Path2D, x: f64, y: f64, fill_rule: CanvasFillRule) -> bool {
|
||||
self.canvas_state
|
||||
.is_point_in_path_(&self.global(), path.segments(), x, y, fill_rule)
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/#dom-context-2d-filltext
|
||||
fn FillText(&self, text: DOMString, x: f64, y: f64, max_width: Option<f64>, can_gc: CanGc) {
|
||||
self.canvas_state
|
||||
|
|
|
@ -466,6 +466,7 @@ pub(crate) mod paintrenderingcontext2d;
|
|||
pub(crate) mod paintsize;
|
||||
pub(crate) mod paintworkletglobalscope;
|
||||
pub(crate) mod pannernode;
|
||||
pub(crate) mod path2d;
|
||||
pub(crate) mod performance;
|
||||
#[allow(dead_code)]
|
||||
pub(crate) mod performanceentry;
|
||||
|
|
|
@ -27,6 +27,7 @@ use crate::dom::dommatrix::DOMMatrix;
|
|||
use crate::dom::globalscope::GlobalScope;
|
||||
use crate::dom::imagedata::ImageData;
|
||||
use crate::dom::offscreencanvas::OffscreenCanvas;
|
||||
use crate::dom::path2d::Path2D;
|
||||
use crate::dom::textmetrics::TextMetrics;
|
||||
use crate::script_runtime::CanGc;
|
||||
|
||||
|
@ -437,21 +438,41 @@ impl OffscreenCanvasRenderingContext2DMethods<crate::DomTypeHolder>
|
|||
self.context.Fill(fill_rule)
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/#dom-context-2d-fill
|
||||
fn Fill_(&self, path: &Path2D, fill_rule: CanvasFillRule) {
|
||||
self.context.Fill_(path, fill_rule)
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/#dom-context-2d-stroke
|
||||
fn Stroke(&self) {
|
||||
self.context.Stroke()
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/#dom-context-2d-stroke
|
||||
fn Stroke_(&self, path: &Path2D) {
|
||||
self.context.Stroke_(path)
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/#dom-context-2d-clip
|
||||
fn Clip(&self, fill_rule: CanvasFillRule) {
|
||||
self.context.Clip(fill_rule)
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/#dom-context-2d-clip
|
||||
fn Clip_(&self, path: &Path2D, fill_rule: CanvasFillRule) {
|
||||
self.context.Clip_(path, fill_rule)
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/#dom-context-2d-ispointinpath
|
||||
fn IsPointInPath(&self, x: f64, y: f64, fill_rule: CanvasFillRule) -> bool {
|
||||
self.context.IsPointInPath(x, y, fill_rule)
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/#dom-context-2d-ispointinpath
|
||||
fn IsPointInPath_(&self, path: &Path2D, x: f64, y: f64, fill_rule: CanvasFillRule) -> bool {
|
||||
self.context.IsPointInPath_(path, x, y, fill_rule)
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/#dom-context-2d-scale
|
||||
fn Scale(&self, x: f64, y: f64) {
|
||||
self.context.Scale(x, y)
|
||||
|
|
|
@ -29,6 +29,7 @@ use crate::dom::canvasgradient::CanvasGradient;
|
|||
use crate::dom::canvaspattern::CanvasPattern;
|
||||
use crate::dom::dommatrix::DOMMatrix;
|
||||
use crate::dom::paintworkletglobalscope::PaintWorkletGlobalScope;
|
||||
use crate::dom::path2d::Path2D;
|
||||
use crate::script_runtime::CanGc;
|
||||
|
||||
#[dom_struct]
|
||||
|
@ -192,22 +193,43 @@ impl PaintRenderingContext2DMethods<crate::DomTypeHolder> for PaintRenderingCont
|
|||
self.canvas_state.fill(fill_rule)
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/#dom-context-2d-fill
|
||||
fn Fill_(&self, path: &Path2D, fill_rule: CanvasFillRule) {
|
||||
self.canvas_state.fill_(path.segments(), fill_rule)
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/#dom-context-2d-stroke
|
||||
fn Stroke(&self) {
|
||||
self.canvas_state.stroke()
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/#dom-context-2d-stroke
|
||||
fn Stroke_(&self, path: &Path2D) {
|
||||
self.canvas_state.stroke_(path.segments())
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/#dom-context-2d-clip
|
||||
fn Clip(&self, fill_rule: CanvasFillRule) {
|
||||
self.canvas_state.clip(fill_rule)
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/#dom-context-2d-clip
|
||||
fn Clip_(&self, path: &Path2D, fill_rule: CanvasFillRule) {
|
||||
self.canvas_state.clip_(path.segments(), fill_rule)
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/#dom-context-2d-ispointinpath
|
||||
fn IsPointInPath(&self, x: f64, y: f64, fill_rule: CanvasFillRule) -> bool {
|
||||
self.canvas_state
|
||||
.is_point_in_path(&self.global(), x, y, fill_rule)
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/#dom-context-2d-ispointinpath
|
||||
fn IsPointInPath_(&self, path: &Path2D, x: f64, y: f64, fill_rule: CanvasFillRule) -> bool {
|
||||
self.canvas_state
|
||||
.is_point_in_path_(&self.global(), path.segments(), x, y, fill_rule)
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/#dom-context-2d-drawimage
|
||||
fn DrawImage(&self, image: CanvasImageSource, dx: f64, dy: f64) -> ErrorResult {
|
||||
self.canvas_state.draw_image(None, image, dx, dy)
|
||||
|
|
313
components/script/dom/path2d.rs
Normal file
313
components/script/dom/path2d.rs
Normal file
|
@ -0,0 +1,313 @@
|
|||
/* 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/. */
|
||||
|
||||
use std::cell::RefCell;
|
||||
|
||||
use canvas_traits::canvas::PathSegment;
|
||||
use dom_struct::dom_struct;
|
||||
use js::rust::HandleObject;
|
||||
use script_bindings::str::DOMString;
|
||||
|
||||
use crate::dom::bindings::codegen::Bindings::CanvasRenderingContext2DBinding::Path2DMethods;
|
||||
use crate::dom::bindings::error::{Error, Fallible};
|
||||
use crate::dom::bindings::reflector::{Reflector, reflect_dom_object_with_proto};
|
||||
use crate::dom::bindings::root::DomRoot;
|
||||
use crate::dom::globalscope::GlobalScope;
|
||||
use crate::script_runtime::CanGc;
|
||||
use crate::svgpath::PathParser;
|
||||
|
||||
#[dom_struct]
|
||||
pub(crate) struct Path2D {
|
||||
reflector_: Reflector,
|
||||
#[no_trace]
|
||||
path: RefCell<Vec<PathSegment>>,
|
||||
}
|
||||
|
||||
impl Path2D {
|
||||
pub(crate) fn new() -> Path2D {
|
||||
Self {
|
||||
reflector_: Reflector::new(),
|
||||
path: RefCell::new(vec![]),
|
||||
}
|
||||
}
|
||||
pub(crate) fn new_with_path(other: &Path2D) -> Path2D {
|
||||
Self {
|
||||
reflector_: Reflector::new(),
|
||||
path: other.path.clone(),
|
||||
}
|
||||
}
|
||||
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 {
|
||||
reflector_: Reflector::new(),
|
||||
path: RefCell::new(path_segments),
|
||||
}
|
||||
}
|
||||
pub(crate) fn push(&self, seg: PathSegment) {
|
||||
self.path.borrow_mut().push(seg);
|
||||
}
|
||||
pub(crate) fn segments(&self) -> Vec<PathSegment> {
|
||||
self.path.borrow().clone()
|
||||
}
|
||||
}
|
||||
|
||||
impl Path2DMethods<crate::DomTypeHolder> for Path2D {
|
||||
/// <https://html.spec.whatwg.org/multipage/#dom-path2d-addpath>
|
||||
fn AddPath(&self, other: &Path2D) {
|
||||
// Step 7. Add all the subpaths in c to a.
|
||||
let mut dest = self.path.borrow_mut();
|
||||
dest.extend(other.path.borrow().iter().copied());
|
||||
}
|
||||
|
||||
/// <https://html.spec.whatwg.org/multipage/#dom-context-2d-closepath>
|
||||
fn ClosePath(&self) {
|
||||
self.push(PathSegment::ClosePath);
|
||||
}
|
||||
|
||||
/// <https://html.spec.whatwg.org/multipage/#dom-context-2d-moveto>
|
||||
fn MoveTo(&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. 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>
|
||||
fn LineTo(&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;
|
||||
}
|
||||
|
||||
self.push(PathSegment::LineTo {
|
||||
x: x as f32,
|
||||
y: y as f32,
|
||||
});
|
||||
}
|
||||
|
||||
/// <https://html.spec.whatwg.org/multipage/#dom-context-2d-quadraticcurveto>
|
||||
fn QuadraticCurveTo(&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;
|
||||
}
|
||||
|
||||
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>
|
||||
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.
|
||||
if !(cp1x.is_finite() &&
|
||||
cp1y.is_finite() &&
|
||||
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>
|
||||
fn ArcTo(&self, x1: f64, y1: f64, x2: f64, y2: f64, r: f64) -> Fallible<()> {
|
||||
// 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() && r.is_finite())
|
||||
{
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// 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>
|
||||
fn Rect(&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.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>
|
||||
fn Arc(
|
||||
&self,
|
||||
x: f64,
|
||||
y: f64,
|
||||
r: f64,
|
||||
start: f64,
|
||||
end: f64,
|
||||
anticlockwise: bool,
|
||||
) -> Fallible<()> {
|
||||
// Step 1. If any of the arguments are infinite or NaN, then return.
|
||||
if !(x.is_finite() &&
|
||||
y.is_finite() &&
|
||||
r.is_finite() &&
|
||||
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>
|
||||
fn Ellipse(
|
||||
&self,
|
||||
x: f64,
|
||||
y: f64,
|
||||
rx: f64,
|
||||
ry: f64,
|
||||
rotation: f64,
|
||||
start: f64,
|
||||
end: f64,
|
||||
anticlockwise: bool,
|
||||
) -> Fallible<()> {
|
||||
// Step 1. If any of the arguments are infinite or NaN, then return.
|
||||
if !(x.is_finite() &&
|
||||
y.is_finite() &&
|
||||
rx.is_finite() &&
|
||||
ry.is_finite() &&
|
||||
rotation.is_finite() &&
|
||||
start.is_finite() &&
|
||||
end.is_finite())
|
||||
{
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// Step 2. If either radiusX or radiusY are negative, then throw an "IndexSizeError" DOMException.
|
||||
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>
|
||||
fn Constructor(
|
||||
global: &GlobalScope,
|
||||
proto: Option<HandleObject>,
|
||||
can_gc: CanGc,
|
||||
) -> DomRoot<Path2D> {
|
||||
reflect_dom_object_with_proto(Box::new(Self::new()), global, proto, can_gc)
|
||||
}
|
||||
|
||||
/// <https://html.spec.whatwg.org/multipage/#dom-path2d-dev>
|
||||
fn Constructor_(
|
||||
global: &GlobalScope,
|
||||
proto: Option<HandleObject>,
|
||||
can_gc: CanGc,
|
||||
other: &Path2D,
|
||||
) -> DomRoot<Path2D> {
|
||||
reflect_dom_object_with_proto(Box::new(Self::new_with_path(other)), global, proto, can_gc)
|
||||
}
|
||||
|
||||
/// <https://html.spec.whatwg.org/multipage/#dom-path2d-dev>
|
||||
fn Constructor__(
|
||||
global: &GlobalScope,
|
||||
proto: Option<HandleObject>,
|
||||
can_gc: CanGc,
|
||||
path_string: DOMString,
|
||||
) -> DomRoot<Path2D> {
|
||||
reflect_dom_object_with_proto(
|
||||
Box::new(Self::new_with_str(path_string.str())),
|
||||
global,
|
||||
proto,
|
||||
can_gc,
|
||||
)
|
||||
}
|
||||
}
|
|
@ -78,6 +78,8 @@ mod drag_data_store;
|
|||
mod links;
|
||||
mod xpath;
|
||||
|
||||
mod svgpath;
|
||||
|
||||
pub use init::init;
|
||||
pub use script_runtime::JSEngineSetup;
|
||||
pub use script_thread::ScriptThread;
|
||||
|
|
13
components/script/svgpath/mod.rs
Normal file
13
components/script/svgpath/mod.rs
Normal file
|
@ -0,0 +1,13 @@
|
|||
/* 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;
|
198
components/script/svgpath/number.rs
Normal file
198
components/script/svgpath/number.rs
Normal file
|
@ -0,0 +1,198 @@
|
|||
// 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
|
||||
}
|
393
components/script/svgpath/path.rs
Normal file
393
components/script/svgpath/path.rs
Normal file
|
@ -0,0 +1,393 @@
|
|||
// 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 }
|
||||
);
|
||||
}
|
162
components/script/svgpath/stream.rs
Normal file
162
components/script/svgpath/stream.rs
Normal file
|
@ -0,0 +1,162 @@
|
|||
// 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),
|
||||
}
|
||||
}
|
||||
}
|
|
@ -117,15 +117,15 @@ interface mixin CanvasDrawPath {
|
|||
// path API (see also CanvasPath)
|
||||
undefined beginPath();
|
||||
undefined fill(optional CanvasFillRule fillRule = "nonzero");
|
||||
//void fill(Path2D path, optional CanvasFillRule fillRule = "nonzero");
|
||||
undefined fill(Path2D path, optional CanvasFillRule fillRule = "nonzero");
|
||||
undefined stroke();
|
||||
//void stroke(Path2D path);
|
||||
undefined stroke(Path2D path);
|
||||
undefined clip(optional CanvasFillRule fillRule = "nonzero");
|
||||
//void clip(Path2D path, optional CanvasFillRule fillRule = "nonzero");
|
||||
undefined clip(Path2D path, optional CanvasFillRule fillRule = "nonzero");
|
||||
boolean isPointInPath(unrestricted double x, unrestricted double y,
|
||||
optional CanvasFillRule fillRule = "nonzero");
|
||||
//boolean isPointInPath(Path2D path, unrestricted double x, unrestricted double y,
|
||||
// optional CanvasFillRule fillRule = "nonzero");
|
||||
boolean isPointInPath(Path2D path, unrestricted double x, unrestricted double y,
|
||||
optional CanvasFillRule fillRule = "nonzero");
|
||||
//boolean isPointInStroke(unrestricted double x, unrestricted double y);
|
||||
//boolean isPointInStroke(Path2D path, unrestricted double x, unrestricted double y);
|
||||
};
|
||||
|
@ -262,3 +262,12 @@ interface ImageData {
|
|||
[Throws] readonly attribute Uint8ClampedArray data;
|
||||
//readonly attribute PredefinedColorSpace colorSpace;
|
||||
};
|
||||
|
||||
[Exposed=(Window,Worker)]
|
||||
interface Path2D {
|
||||
constructor();
|
||||
constructor(Path2D other);
|
||||
constructor(DOMString pathString);
|
||||
undefined addPath(Path2D path/*, SVGMatrix? transformation*/);
|
||||
};
|
||||
Path2D includes CanvasPath;
|
||||
|
|
|
@ -13,6 +13,59 @@ use serde_bytes::ByteBuf;
|
|||
use style::color::AbsoluteColor;
|
||||
use style::properties::style_structs::Font as FontStyleStruct;
|
||||
|
||||
#[derive(Clone, Copy, Debug, Deserialize, MallocSizeOf, PartialEq, Serialize)]
|
||||
pub enum PathSegment {
|
||||
ClosePath,
|
||||
MoveTo {
|
||||
x: f32,
|
||||
y: f32,
|
||||
},
|
||||
LineTo {
|
||||
x: f32,
|
||||
y: f32,
|
||||
},
|
||||
Quadratic {
|
||||
cpx: f32,
|
||||
cpy: f32,
|
||||
x: f32,
|
||||
y: f32,
|
||||
},
|
||||
Bezier {
|
||||
cp1x: f32,
|
||||
cp1y: f32,
|
||||
cp2x: f32,
|
||||
cp2y: f32,
|
||||
x: f32,
|
||||
y: f32,
|
||||
},
|
||||
ArcTo {
|
||||
cp1x: f32,
|
||||
cp1y: f32,
|
||||
cp2x: f32,
|
||||
cp2y: f32,
|
||||
radius: f32,
|
||||
},
|
||||
Ellipse {
|
||||
x: f32,
|
||||
y: f32,
|
||||
radius_x: f32,
|
||||
radius_y: f32,
|
||||
rotation: f32,
|
||||
start_angle: f32,
|
||||
end_angle: f32,
|
||||
anticlockwise: bool,
|
||||
},
|
||||
SvgArc {
|
||||
radius_x: f32,
|
||||
radius_y: f32,
|
||||
rotation: f32,
|
||||
large_arc: bool,
|
||||
sweep: bool,
|
||||
x: f32,
|
||||
y: f32,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||
pub enum FillRule {
|
||||
Nonzero,
|
||||
|
@ -41,14 +94,17 @@ pub enum Canvas2dMsg {
|
|||
BezierCurveTo(Point2D<f32>, Point2D<f32>, Point2D<f32>),
|
||||
ClearRect(Rect<f32>),
|
||||
Clip,
|
||||
ClipPath(Vec<PathSegment>),
|
||||
ClosePath,
|
||||
Ellipse(Point2D<f32>, f32, f32, f32, f32, f32, bool),
|
||||
Fill(FillOrStrokeStyle),
|
||||
FillPath(FillOrStrokeStyle, Vec<PathSegment>),
|
||||
FillText(String, f64, f64, Option<f64>, FillOrStrokeStyle, bool),
|
||||
FillRect(Rect<f32>, FillOrStrokeStyle),
|
||||
GetImageData(Rect<u64>, Size2D<u64>, IpcBytesSender),
|
||||
GetTransform(IpcSender<Transform2D<f32>>),
|
||||
IsPointInPath(f64, f64, FillRule, IpcSender<bool>),
|
||||
IsPointInCurrentPath(f64, f64, FillRule, IpcSender<bool>),
|
||||
IsPointInPath(Vec<PathSegment>, f64, f64, FillRule, IpcSender<bool>),
|
||||
LineTo(Point2D<f32>),
|
||||
MoveTo(Point2D<f32>),
|
||||
MeasureText(String, IpcSender<TextMetrics>),
|
||||
|
@ -59,6 +115,7 @@ pub enum Canvas2dMsg {
|
|||
SaveContext,
|
||||
StrokeRect(Rect<f32>, FillOrStrokeStyle),
|
||||
Stroke(FillOrStrokeStyle),
|
||||
StrokePath(FillOrStrokeStyle, Vec<PathSegment>),
|
||||
SetLineWidth(f32),
|
||||
SetLineCap(LineCapStyle),
|
||||
SetLineJoin(LineJoinStyle),
|
||||
|
|
|
@ -215,6 +215,9 @@ def uncomment(line):
|
|||
|
||||
|
||||
def is_apache_licensed(header):
|
||||
if "SPDX-License-Identifier: Apache-2.0 OR MIT" in header:
|
||||
return True
|
||||
|
||||
if APACHE in header:
|
||||
return any(c in header for c in COPYRIGHT)
|
||||
|
||||
|
|
2
tests/wpt/meta-legacy-layout/html/canvas/element/layers/2d.layer.nested.html.ini
vendored
Normal file
2
tests/wpt/meta-legacy-layout/html/canvas/element/layers/2d.layer.nested.html.ini
vendored
Normal file
|
@ -0,0 +1,2 @@
|
|||
[2d.layer.nested.html]
|
||||
expected: FAIL
|
|
@ -0,0 +1,2 @@
|
|||
[2d.layer.shadow-from-outside-canvas.long-distance-with-clipping.ctx-filter.html]
|
||||
expected: FAIL
|
|
@ -0,0 +1,2 @@
|
|||
[2d.layer.shadow-from-outside-canvas.long-distance-with-clipping.layer-filter.tentative.html]
|
||||
expected: FAIL
|
|
@ -0,0 +1,2 @@
|
|||
[2d.layer.shadow-from-outside-canvas.short-distance-with-clipping.ctx-filter.html]
|
||||
expected: FAIL
|
|
@ -0,0 +1,2 @@
|
|||
[2d.layer.shadow-from-outside-canvas.short-distance-with-clipping.layer-filter.tentative.html]
|
||||
expected: FAIL
|
2
tests/wpt/meta-legacy-layout/html/canvas/offscreen/layers/2d.layer.nested.html.ini
vendored
Normal file
2
tests/wpt/meta-legacy-layout/html/canvas/offscreen/layers/2d.layer.nested.html.ini
vendored
Normal file
|
@ -0,0 +1,2 @@
|
|||
[2d.layer.nested.html]
|
||||
expected: FAIL
|
|
@ -0,0 +1,2 @@
|
|||
[2d.layer.shadow-from-outside-canvas.long-distance-with-clipping.ctx-filter.html]
|
||||
expected: FAIL
|
|
@ -0,0 +1,2 @@
|
|||
[2d.layer.shadow-from-outside-canvas.long-distance-with-clipping.layer-filter.tentative.html]
|
||||
expected: FAIL
|
|
@ -0,0 +1,2 @@
|
|||
[2d.layer.shadow-from-outside-canvas.short-distance-with-clipping.ctx-filter.html]
|
||||
expected: FAIL
|
|
@ -0,0 +1,2 @@
|
|||
[2d.layer.shadow-from-outside-canvas.short-distance-with-clipping.layer-filter.tentative.html]
|
||||
expected: FAIL
|
2
tests/wpt/meta/html/canvas/element/layers/2d.layer.nested.html.ini
vendored
Normal file
2
tests/wpt/meta/html/canvas/element/layers/2d.layer.nested.html.ini
vendored
Normal file
|
@ -0,0 +1,2 @@
|
|||
[2d.layer.nested.html]
|
||||
expected: FAIL
|
|
@ -0,0 +1,2 @@
|
|||
[2d.layer.shadow-from-outside-canvas.long-distance-with-clipping.ctx-filter.html]
|
||||
expected: FAIL
|
|
@ -0,0 +1,2 @@
|
|||
[2d.layer.shadow-from-outside-canvas.long-distance-with-clipping.layer-filter.tentative.html]
|
||||
expected: FAIL
|
|
@ -0,0 +1,2 @@
|
|||
[2d.layer.shadow-from-outside-canvas.short-distance-with-clipping.ctx-filter.html]
|
||||
expected: FAIL
|
|
@ -0,0 +1,2 @@
|
|||
[2d.layer.shadow-from-outside-canvas.short-distance-with-clipping.layer-filter.tentative.html]
|
||||
expected: FAIL
|
|
@ -1,4 +0,0 @@
|
|||
[2d.path.arc.negative.html]
|
||||
[arc() with negative radius throws INDEX_SIZE_ERR]
|
||||
expected: FAIL
|
||||
|
|
@ -1,4 +0,0 @@
|
|||
[2d.path.arcTo.negative.html]
|
||||
[arcTo() with negative radius throws an exception]
|
||||
expected: FAIL
|
||||
|
|
@ -1,4 +0,0 @@
|
|||
[2d.path.isPointInpath.invalid.html]
|
||||
[Verify isPointInPath throws exceptions with invalid inputs.]
|
||||
expected: FAIL
|
||||
|
2
tests/wpt/meta/html/canvas/offscreen/layers/2d.layer.nested.html.ini
vendored
Normal file
2
tests/wpt/meta/html/canvas/offscreen/layers/2d.layer.nested.html.ini
vendored
Normal file
|
@ -0,0 +1,2 @@
|
|||
[2d.layer.nested.html]
|
||||
expected: FAIL
|
|
@ -0,0 +1,2 @@
|
|||
[2d.layer.shadow-from-outside-canvas.long-distance-with-clipping.ctx-filter.html]
|
||||
expected: FAIL
|
|
@ -0,0 +1,2 @@
|
|||
[2d.layer.shadow-from-outside-canvas.long-distance-with-clipping.layer-filter.tentative.html]
|
||||
expected: FAIL
|
|
@ -0,0 +1,2 @@
|
|||
[2d.layer.shadow-from-outside-canvas.short-distance-with-clipping.ctx-filter.html]
|
||||
expected: FAIL
|
|
@ -0,0 +1,2 @@
|
|||
[2d.layer.shadow-from-outside-canvas.short-distance-with-clipping.layer-filter.tentative.html]
|
||||
expected: FAIL
|
|
@ -1,4 +0,0 @@
|
|||
[2d.path.arc.negative.html]
|
||||
[arc() with negative radius throws INDEX_SIZE_ERR]
|
||||
expected: FAIL
|
||||
|
|
@ -1,4 +0,0 @@
|
|||
[2d.path.arc.negative.worker.html]
|
||||
[arc() with negative radius throws INDEX_SIZE_ERR]
|
||||
expected: FAIL
|
||||
|
|
@ -1,4 +0,0 @@
|
|||
[2d.path.arcTo.negative.html]
|
||||
[arcTo() with negative radius throws an exception]
|
||||
expected: FAIL
|
||||
|
|
@ -1,4 +0,0 @@
|
|||
[2d.path.arcTo.negative.worker.html]
|
||||
[arcTo() with negative radius throws an exception]
|
||||
expected: FAIL
|
||||
|
|
@ -1,3 +0,0 @@
|
|||
[2d.path.isPointInpath.invalid.html]
|
||||
[Verify isPointInPath throws exceptions with invalid inputs.]
|
||||
expected: FAIL
|
|
@ -1,3 +0,0 @@
|
|||
[2d.path.isPointInpath.invalid.worker.html]
|
||||
[Verify isPointInPath throws exceptions with invalid inputs.]
|
||||
expected: FAIL
|
48
tests/wpt/meta/html/dom/idlharness.any.js.ini
vendored
48
tests/wpt/meta/html/dom/idlharness.any.js.ini
vendored
|
@ -5,57 +5,9 @@
|
|||
[ImageData interface: attribute colorSpace]
|
||||
expected: FAIL
|
||||
|
||||
[Path2D interface: existence and properties of interface object]
|
||||
expected: FAIL
|
||||
|
||||
[Path2D interface object length]
|
||||
expected: FAIL
|
||||
|
||||
[Path2D interface object name]
|
||||
expected: FAIL
|
||||
|
||||
[Path2D interface: existence and properties of interface prototype object]
|
||||
expected: FAIL
|
||||
|
||||
[Path2D interface: existence and properties of interface prototype object's "constructor" property]
|
||||
expected: FAIL
|
||||
|
||||
[Path2D interface: existence and properties of interface prototype object's @@unscopables property]
|
||||
expected: FAIL
|
||||
|
||||
[Path2D interface: operation addPath(Path2D, optional DOMMatrix2DInit)]
|
||||
expected: FAIL
|
||||
|
||||
[Path2D interface: operation closePath()]
|
||||
expected: FAIL
|
||||
|
||||
[Path2D interface: operation moveTo(unrestricted double, unrestricted double)]
|
||||
expected: FAIL
|
||||
|
||||
[Path2D interface: operation lineTo(unrestricted double, unrestricted double)]
|
||||
expected: FAIL
|
||||
|
||||
[Path2D interface: operation quadraticCurveTo(unrestricted double, unrestricted double, unrestricted double, unrestricted double)]
|
||||
expected: FAIL
|
||||
|
||||
[Path2D interface: operation bezierCurveTo(unrestricted double, unrestricted double, unrestricted double, unrestricted double, unrestricted double, unrestricted double)]
|
||||
expected: FAIL
|
||||
|
||||
[Path2D interface: operation arcTo(unrestricted double, unrestricted double, unrestricted double, unrestricted double, unrestricted double)]
|
||||
expected: FAIL
|
||||
|
||||
[Path2D interface: operation rect(unrestricted double, unrestricted double, unrestricted double, unrestricted double)]
|
||||
expected: FAIL
|
||||
|
||||
[Path2D interface: operation roundRect(unrestricted double, unrestricted double, unrestricted double, unrestricted double, optional (unrestricted double or DOMPointInit or sequence<(unrestricted double or DOMPointInit)>))]
|
||||
expected: FAIL
|
||||
|
||||
[Path2D interface: operation arc(unrestricted double, unrestricted double, unrestricted double, unrestricted double, unrestricted double, optional boolean)]
|
||||
expected: FAIL
|
||||
|
||||
[Path2D interface: operation ellipse(unrestricted double, unrestricted double, unrestricted double, unrestricted double, unrestricted double, unrestricted double, unrestricted double, optional boolean)]
|
||||
expected: FAIL
|
||||
|
||||
[ImageBitmapRenderingContext interface: existence and properties of interface object]
|
||||
expected: FAIL
|
||||
|
||||
|
|
|
@ -176,12 +176,6 @@
|
|||
[DataTransfer interface: operation getData(DOMString)]
|
||||
expected: FAIL
|
||||
|
||||
[Path2D interface: operation closePath()]
|
||||
expected: FAIL
|
||||
|
||||
[Path2D interface: existence and properties of interface prototype object's @@unscopables property]
|
||||
expected: FAIL
|
||||
|
||||
[History interface: attribute scrollRestoration]
|
||||
expected: FAIL
|
||||
|
||||
|
@ -368,9 +362,6 @@
|
|||
[Stringification of window.applicationCache]
|
||||
expected: FAIL
|
||||
|
||||
[Path2D interface: operation rect(unrestricted double, unrestricted double, unrestricted double, unrestricted double)]
|
||||
expected: FAIL
|
||||
|
||||
[DataTransfer interface: attribute dropEffect]
|
||||
expected: FAIL
|
||||
|
||||
|
@ -455,9 +446,6 @@
|
|||
[ApplicationCache interface: constant UNCACHED on interface prototype object]
|
||||
expected: FAIL
|
||||
|
||||
[Path2D interface: operation ellipse(unrestricted double, unrestricted double, unrestricted double, unrestricted double, unrestricted double, unrestricted double, unrestricted double, optional boolean)]
|
||||
expected: FAIL
|
||||
|
||||
[SVGElement interface: attribute dataset]
|
||||
expected: FAIL
|
||||
|
||||
|
@ -482,9 +470,6 @@
|
|||
[CanvasRenderingContext2D interface: document.createElement("canvas").getContext("2d") must inherit property "drawFocusIfNeeded(Element)" with the proper type]
|
||||
expected: FAIL
|
||||
|
||||
[Path2D interface: operation arcTo(unrestricted double, unrestricted double, unrestricted double, unrestricted double, unrestricted double)]
|
||||
expected: FAIL
|
||||
|
||||
[SVGElement interface: attribute autofocus]
|
||||
expected: FAIL
|
||||
|
||||
|
@ -500,9 +485,6 @@
|
|||
[SVGElement interface: attribute onmouseover]
|
||||
expected: FAIL
|
||||
|
||||
[Path2D interface: operation addPath(Path2D, optional DOMMatrix2DInit)]
|
||||
expected: FAIL
|
||||
|
||||
[BarProp interface: existence and properties of interface object]
|
||||
expected: FAIL
|
||||
|
||||
|
@ -545,9 +527,6 @@
|
|||
[ApplicationCache interface: attribute onnoupdate]
|
||||
expected: FAIL
|
||||
|
||||
[Path2D interface: existence and properties of interface object]
|
||||
expected: FAIL
|
||||
|
||||
[External interface: window.external must inherit property "IsSearchProviderInstalled()" with the proper type]
|
||||
expected: FAIL
|
||||
|
||||
|
@ -569,9 +548,6 @@
|
|||
[External must be primary interface of window.external]
|
||||
expected: FAIL
|
||||
|
||||
[Path2D interface object name]
|
||||
expected: FAIL
|
||||
|
||||
[ApplicationCache interface: constant OBSOLETE on interface object]
|
||||
expected: FAIL
|
||||
|
||||
|
@ -593,12 +569,6 @@
|
|||
[SVGElement interface: attribute onwebkitanimationiteration]
|
||||
expected: FAIL
|
||||
|
||||
[Path2D interface object length]
|
||||
expected: FAIL
|
||||
|
||||
[Path2D interface: existence and properties of interface prototype object]
|
||||
expected: FAIL
|
||||
|
||||
[DragEvent interface: existence and properties of interface prototype object]
|
||||
expected: FAIL
|
||||
|
||||
|
@ -620,15 +590,9 @@
|
|||
[DOMStringList interface: location.ancestorOrigins must inherit property "item(unsigned long)" with the proper type]
|
||||
expected: FAIL
|
||||
|
||||
[Path2D interface: existence and properties of interface prototype object's "constructor" property]
|
||||
expected: FAIL
|
||||
|
||||
[SVGElement interface: attribute onmouseenter]
|
||||
expected: FAIL
|
||||
|
||||
[Path2D interface: operation quadraticCurveTo(unrestricted double, unrestricted double, unrestricted double, unrestricted double)]
|
||||
expected: FAIL
|
||||
|
||||
[CanvasRenderingContext2D interface: operation strokeText(DOMString, unrestricted double, unrestricted double, optional unrestricted double)]
|
||||
expected: FAIL
|
||||
|
||||
|
@ -638,15 +602,9 @@
|
|||
[SharedWorker interface object length]
|
||||
expected: FAIL
|
||||
|
||||
[Path2D interface: operation lineTo(unrestricted double, unrestricted double)]
|
||||
expected: FAIL
|
||||
|
||||
[DragEvent interface: existence and properties of interface object]
|
||||
expected: FAIL
|
||||
|
||||
[Path2D interface: operation arc(unrestricted double, unrestricted double, unrestricted double, unrestricted double, unrestricted double, optional boolean)]
|
||||
expected: FAIL
|
||||
|
||||
[ApplicationCache interface: window.applicationCache must inherit property "onprogress" with the proper type]
|
||||
expected: FAIL
|
||||
|
||||
|
@ -695,9 +653,6 @@
|
|||
[ImageBitmapRenderingContext interface: attribute canvas]
|
||||
expected: FAIL
|
||||
|
||||
[Path2D interface: operation bezierCurveTo(unrestricted double, unrestricted double, unrestricted double, unrestricted double, unrestricted double, unrestricted double)]
|
||||
expected: FAIL
|
||||
|
||||
[ApplicationCache interface: window.applicationCache must inherit property "status" with the proper type]
|
||||
expected: FAIL
|
||||
|
||||
|
@ -866,9 +821,6 @@
|
|||
[SVGElement interface: attribute onratechange]
|
||||
expected: FAIL
|
||||
|
||||
[Path2D interface: operation moveTo(unrestricted double, unrestricted double)]
|
||||
expected: FAIL
|
||||
|
||||
[DragEvent interface: existence and properties of interface prototype object's "constructor" property]
|
||||
expected: FAIL
|
||||
|
||||
|
@ -4784,57 +4736,9 @@
|
|||
[ImageData interface: new ImageData(10, 10) must inherit property "colorSpace" with the proper type]
|
||||
expected: FAIL
|
||||
|
||||
[Path2D interface: existence and properties of interface object]
|
||||
expected: FAIL
|
||||
|
||||
[Path2D interface object length]
|
||||
expected: FAIL
|
||||
|
||||
[Path2D interface object name]
|
||||
expected: FAIL
|
||||
|
||||
[Path2D interface: existence and properties of interface prototype object]
|
||||
expected: FAIL
|
||||
|
||||
[Path2D interface: existence and properties of interface prototype object's "constructor" property]
|
||||
expected: FAIL
|
||||
|
||||
[Path2D interface: existence and properties of interface prototype object's @@unscopables property]
|
||||
expected: FAIL
|
||||
|
||||
[Path2D interface: operation addPath(Path2D, optional DOMMatrix2DInit)]
|
||||
expected: FAIL
|
||||
|
||||
[Path2D interface: operation closePath()]
|
||||
expected: FAIL
|
||||
|
||||
[Path2D interface: operation moveTo(unrestricted double, unrestricted double)]
|
||||
expected: FAIL
|
||||
|
||||
[Path2D interface: operation lineTo(unrestricted double, unrestricted double)]
|
||||
expected: FAIL
|
||||
|
||||
[Path2D interface: operation quadraticCurveTo(unrestricted double, unrestricted double, unrestricted double, unrestricted double)]
|
||||
expected: FAIL
|
||||
|
||||
[Path2D interface: operation bezierCurveTo(unrestricted double, unrestricted double, unrestricted double, unrestricted double, unrestricted double, unrestricted double)]
|
||||
expected: FAIL
|
||||
|
||||
[Path2D interface: operation arcTo(unrestricted double, unrestricted double, unrestricted double, unrestricted double, unrestricted double)]
|
||||
expected: FAIL
|
||||
|
||||
[Path2D interface: operation rect(unrestricted double, unrestricted double, unrestricted double, unrestricted double)]
|
||||
expected: FAIL
|
||||
|
||||
[Path2D interface: operation roundRect(unrestricted double, unrestricted double, unrestricted double, unrestricted double, optional (unrestricted double or DOMPointInit or sequence<(unrestricted double or DOMPointInit)>))]
|
||||
expected: FAIL
|
||||
|
||||
[Path2D interface: operation arc(unrestricted double, unrestricted double, unrestricted double, unrestricted double, unrestricted double, optional boolean)]
|
||||
expected: FAIL
|
||||
|
||||
[Path2D interface: operation ellipse(unrestricted double, unrestricted double, unrestricted double, unrestricted double, unrestricted double, unrestricted double, unrestricted double, optional boolean)]
|
||||
expected: FAIL
|
||||
|
||||
[ImageBitmapRenderingContext interface: existence and properties of interface object]
|
||||
expected: FAIL
|
||||
|
||||
|
|
|
@ -1,2 +0,0 @@
|
|||
[historical.html]
|
||||
expected: ERROR
|
|
@ -5,9 +5,6 @@
|
|||
[The CanvasPath interface object should be exposed.]
|
||||
expected: FAIL
|
||||
|
||||
[The Path2D interface object should be exposed.]
|
||||
expected: FAIL
|
||||
|
||||
[The IDBRequest interface object should be exposed.]
|
||||
expected: FAIL
|
||||
|
||||
|
|
4
tests/wpt/mozilla/meta/MANIFEST.json
vendored
4
tests/wpt/mozilla/meta/MANIFEST.json
vendored
|
@ -13503,14 +13503,14 @@
|
|||
]
|
||||
],
|
||||
"interfaces.https.html": [
|
||||
"81f4d942f94366d8f9ecf22cfc3e1e22fe4ab8f1",
|
||||
"ad0b03ac70483c152220978cee8b49e74b330fc6",
|
||||
[
|
||||
null,
|
||||
{}
|
||||
]
|
||||
],
|
||||
"interfaces.worker.js": [
|
||||
"06eb8d3ba2334951cb1e0f791527ba118d4f13ec",
|
||||
"f93f9c9f6c877e1915217b64591fe7e34fa7244c",
|
||||
[
|
||||
"mozilla/interfaces.worker.html",
|
||||
{}
|
||||
|
|
|
@ -223,6 +223,7 @@ test_interfaces([
|
|||
"OscillatorNode",
|
||||
"PageTransitionEvent",
|
||||
"PannerNode",
|
||||
"Path2D",
|
||||
"Performance",
|
||||
"PerformanceEntry",
|
||||
"PerformanceMark",
|
||||
|
|
|
@ -45,6 +45,7 @@ test_interfaces([
|
|||
"Notification",
|
||||
"OffscreenCanvas",
|
||||
"OffscreenCanvasRenderingContext2D",
|
||||
"Path2D",
|
||||
"Performance",
|
||||
"PerformanceEntry",
|
||||
"PerformanceMark",
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue