commit 8257df91aa31e7124052f796406a03aaecda8f5f parent bbbf1876cd8cdf1cdd29667dfd57ea40bb230301 Author: Swarup Ukil <sukil@mozilla.com> Date: Wed, 3 Dec 2025 00:02:41 +0000 Bug 1993311 - Update hline and vline command parsing to support <position> values. r=boris,firefox-style-system-reviewers,firefox-svg-reviewers,layout-reviewers,longsonr Differential Revision: https://phabricator.services.mozilla.com/D271463 Diffstat:
16 files changed, 590 insertions(+), 311 deletions(-)
diff --git a/dom/svg/SVGAnimatedPathSegList.cpp b/dom/svg/SVGAnimatedPathSegList.cpp @@ -43,8 +43,19 @@ static StyleCurveControlPoint<float> MakeControlPoint(PositionType type, if (type == PositionType::Absolute) { return StyleCurveControlPoint<float>::Absolute({x, y}); } else { - return StyleCurveControlPoint<float>::Relative( - StyleRelativeControlPoint<float>{{x, y}, StyleControlReference::None}); + const auto rcp = + StyleRelativeControlPoint<float>{{x, y}, StyleControlReference::None}; + return StyleCurveControlPoint<float>::Relative(rcp); + } +} + +static StyleAxisEndPoint<float> MakeAxisEndPoint(PositionType type, + float end_point) { + if (type == PositionType::Absolute) { + const auto pos = StyleAxisPosition<float>::LengthPercent(end_point); + return StyleAxisEndPoint<float>::ToPosition(pos); + } else { + return StyleAxisEndPoint<float>::ByCoordinate(end_point); } } @@ -139,13 +150,17 @@ class MOZ_STACK_CLASS SVGPathSegmentInitWrapper final { mInit.mValues[3] ? StyleArcSize::Large : StyleArcSize::Small, mInit.mValues[2]); case 'H': - return StylePathCommand::HLine(StyleByTo::To, mInit.mValues[0]); + return StylePathCommand::HLine( + MakeAxisEndPoint(PositionType::Absolute, mInit.mValues[0])); case 'h': - return StylePathCommand::HLine(StyleByTo::By, mInit.mValues[0]); + return StylePathCommand::HLine( + MakeAxisEndPoint(PositionType::Relative, mInit.mValues[0])); case 'V': - return StylePathCommand::VLine(StyleByTo::To, mInit.mValues[0]); + return StylePathCommand::VLine( + MakeAxisEndPoint(PositionType::Absolute, mInit.mValues[0])); case 'v': - return StylePathCommand::VLine(StyleByTo::By, mInit.mValues[0]); + return StylePathCommand::VLine( + MakeAxisEndPoint(PositionType::Relative, mInit.mValues[0])); case 'S': return StylePathCommand::SmoothCubic( MakeEndPoint(PositionType::Absolute, mInit.mValues[2], diff --git a/dom/svg/SVGPathData.cpp b/dom/svg/SVGPathData.cpp @@ -189,16 +189,6 @@ static inline StyleCSSFloat GetRotate(const StyleAngle& aAngle) { return aAngle.ToDegrees(); } -static inline StyleCSSFloat Resolve(const StyleCSSFloat& aValue, - CSSCoord aBasis) { - return aValue; -} - -static inline StyleCSSFloat Resolve(const LengthPercentage& aValue, - CSSCoord aBasis) { - return aValue.ResolveToCSSPixels(aBasis); -} - template <typename Angle, typename Position, typename LP> static already_AddRefed<Path> BuildPathInternal( Span<const StyleGenericShapeCommand<Angle, Position, LP>> aPath, @@ -327,8 +317,8 @@ static already_AddRefed<Path> BuildPathInternal( break; } case Command::Tag::HLine: { - const float x = Resolve(cmd.h_line.x, aPercentageBasis.width); - if (cmd.h_line.by_to == StyleByTo::To) { + const auto x = cmd.h_line.x.ToGfxCoord(aPercentageBasis.width); + if (cmd.h_line.x.IsToPosition()) { segEnd = Point(x, segStart.y); } else { segEnd = segStart + Point(x, 0.0f); @@ -341,8 +331,8 @@ static already_AddRefed<Path> BuildPathInternal( break; } case Command::Tag::VLine: { - const float y = Resolve(cmd.v_line.y, aPercentageBasis.height); - if (cmd.v_line.by_to == StyleByTo::To) { + const auto y = cmd.v_line.y.ToGfxCoord(aPercentageBasis.height); + if (cmd.v_line.y.IsToPosition()) { segEnd = Point(segStart.x, y); } else { segEnd = segStart + Point(0.0f, y); @@ -640,19 +630,21 @@ void SVGPathData::GetMarkerPositioningData(Span<const StylePathCommand> aPath, break; } case StylePathCommand::Tag::HLine: { - if (cmd.h_line.by_to == StyleByTo::To) { - segEnd = Point(cmd.h_line.x, segStart.y) * aZoom; + const auto x = cmd.h_line.x.ToGfxCoord(); + if (cmd.h_line.x.IsToPosition()) { + segEnd = Point(x, segStart.y) * aZoom; } else { - segEnd = segStart + Point(cmd.h_line.x, 0.0f) * aZoom; + segEnd = segStart + Point(x, 0.0f) * aZoom; } segStartAngle = segEndAngle = AngleOfVector(segEnd, segStart); break; } case StylePathCommand::Tag::VLine: { - if (cmd.v_line.by_to == StyleByTo::To) { - segEnd = Point(segStart.x, cmd.v_line.y) * aZoom; + const auto y = cmd.v_line.y.ToGfxCoord(); + if (cmd.v_line.y.IsToPosition()) { + segEnd = Point(segStart.x, y) * aZoom; } else { - segEnd = segStart + Point(0.0f, cmd.v_line.y) * aZoom; + segEnd = segStart + Point(0.0f, y) * aZoom; } segStartAngle = segEndAngle = AngleOfVector(segEnd, segStart); break; diff --git a/dom/svg/SVGPathSegUtils.cpp b/dom/svg/SVGPathSegUtils.cpp @@ -188,9 +188,8 @@ void SVGPathSegUtils::TraversePathSegment(const StylePathCommand& aCommand, break; } case StylePathCommand::Tag::HLine: { - Point to(aCommand.h_line.by_to == StyleByTo::To - ? aCommand.h_line.x - : aState.pos.x + aCommand.h_line.x, + const auto x = aCommand.h_line.x.ToGfxCoord(); + Point to(aCommand.h_line.x.IsToPosition() ? x : aState.pos.x + x, aState.pos.y); if (aState.ShouldUpdateLengthAndControlPoints()) { aState.length += std::fabs(to.x - aState.pos.x); @@ -200,9 +199,9 @@ void SVGPathSegUtils::TraversePathSegment(const StylePathCommand& aCommand, break; } case StylePathCommand::Tag::VLine: { - Point to(aState.pos.x, aCommand.v_line.by_to == StyleByTo::To - ? aCommand.v_line.y - : aState.pos.y + aCommand.v_line.y); + const auto y = aCommand.v_line.y.ToGfxCoord(); + Point to(aState.pos.x, + aCommand.v_line.y.IsToPosition() ? y : aState.pos.y + y); if (aState.ShouldUpdateLengthAndControlPoints()) { aState.length += std::fabs(to.y - aState.pos.y); aState.cp1 = aState.cp2 = to; @@ -434,8 +433,8 @@ Maybe<gfx::Rect> SVGPathToAxisAlignedRect(Span<const StylePathCommand> aPath) { break; } case StylePathCommand::Tag::HLine: { - Point to = gfx::Point(cmd.h_line.x, segStart.y); - if (cmd.h_line.by_to == StyleByTo::By) { + Point to = gfx::Point(cmd.h_line.x.ToGfxCoord(), segStart.y); + if (cmd.h_line.x.IsByCoordinate()) { to.x += segStart.x; } @@ -446,8 +445,8 @@ Maybe<gfx::Rect> SVGPathToAxisAlignedRect(Span<const StylePathCommand> aPath) { break; } case StylePathCommand::Tag::VLine: { - Point to = gfx::Point(segStart.x, cmd.v_line.y); - if (cmd.h_line.by_to == StyleByTo::By) { + Point to = gfx::Point(segStart.x, cmd.v_line.y.ToGfxCoord()); + if (cmd.v_line.y.IsByCoordinate()) { to.y += segStart.y; } diff --git a/dom/svg/SVGPathSegment.cpp b/dom/svg/SVGPathSegment.cpp @@ -85,14 +85,12 @@ SVGPathSegment::SVGPathSegment(SVGPathElement* aSVGPathElement, break; } case StylePathCommand::Tag::HLine: - mCommand.AssignLiteral(aCommand.h_line.by_to == StyleByTo::To ? "H" - : "h"); - mValues.AppendElement(aCommand.h_line.x); + mCommand.AssignLiteral(aCommand.h_line.x.IsToPosition() ? "H" : "h"); + mValues.AppendElement(aCommand.h_line.x.ToGfxCoord()); break; case StylePathCommand::Tag::VLine: - mCommand.AssignLiteral(aCommand.v_line.by_to == StyleByTo::To ? "V" - : "v"); - mValues.AppendElement(aCommand.v_line.y); + mCommand.AssignLiteral(aCommand.v_line.y.IsToPosition() ? "V" : "v"); + mValues.AppendElement(aCommand.v_line.y.ToGfxCoord()); break; case StylePathCommand::Tag::SmoothCubic: mCommand.AssignLiteral(aCommand.smooth_cubic.point.IsToPosition() ? "S" diff --git a/layout/inspector/tests/test_bug877690.html b/layout/inspector/tests/test_bug877690.html @@ -292,9 +292,9 @@ function do_test() { ok(testValues(values, expected), "property box-shadow's values"); // Regression test for bug 1255379. - var shapeFunction = [ "close", "evenodd", "nonzero", "by", "to", "cw", "ccw", - "small", "large", "end", "origin", "start", "center", - "left", "right", "top", "bottom"]; + var shapeFunction = [ "close", "evenodd", "nonzero", "cw", "ccw", "small", "large", + "end", "origin", "start", "center", "left", "right", "top", + "bottom", "x-end","x-start", "y-end","y-start" ]; var expected = [ "inherit", "initial", "unset", "revert", "revert-layer", "none", "url", "polygon", "circle", "ellipse", "inset", "path", "rect", "xywh", "fill-box", "stroke-box", diff --git a/layout/style/ServoStyleConstsInlines.h b/layout/style/ServoStyleConstsInlines.h @@ -1323,6 +1323,29 @@ inline gfx::Point StyleCommandEndPoint< } template <> +inline gfx::Coord StyleAxisEndPoint<StyleCSSFloat>::ToGfxCoord( + const StyleCSSFloat* aBasis) const { + if (IsToPosition()) { + const auto pos = AsToPosition(); + MOZ_ASSERT(pos.IsLengthPercent()); + return gfx::Coord(pos.AsLengthPercent()); + } + return gfx::Coord(AsByCoordinate()); +} + +template <> +inline gfx::Coord StyleAxisEndPoint<LengthPercentage>::ToGfxCoord( + const StyleCSSFloat* aBasis) const { + MOZ_ASSERT(aBasis); + if (IsToPosition()) { + const auto pos = AsToPosition(); + MOZ_ASSERT(pos.IsLengthPercent()); + return gfx::Coord(pos.AsLengthPercent().ResolveToCSSPixels(*aBasis)); + } + return gfx::Coord(AsByCoordinate().ResolveToCSSPixels(*aBasis)); +} + +template <> inline gfx::Point StyleControlPoint<StyleShapePosition<StyleCSSFloat>, StyleCSSFloat>::ToGfxPoint( const gfx::Point aStatePos, const gfx::Point aEndPoint, diff --git a/servo/components/style/values/computed/basic_shape.rs b/servo/components/style/values/computed/basic_shape.rs @@ -62,6 +62,9 @@ pub type RelativeControlPoint = generic::RelativeControlPoint<LengthPercentage>; /// The computed value of 'CommandEndPoint'. pub type CommandEndPoint = generic::CommandEndPoint<Position, LengthPercentage>; +/// The computed value of hline and vline's endpoint. +pub type AxisEndPoint = generic::AxisEndPoint<LengthPercentage>; + /// Animate from `Shape` to `Path`, and vice versa. macro_rules! animate_shape { ( @@ -144,7 +147,6 @@ impl Animate for PathOrShapeFunction { impl From<&PathCommand> for ShapeCommand { #[inline] fn from(path: &PathCommand) -> Self { - use crate::values::computed::CSSPixelLength; match path { &PathCommand::Close => Self::Close, &PathCommand::Move { ref point } => Self::Move { @@ -153,14 +155,8 @@ impl From<&PathCommand> for ShapeCommand { &PathCommand::Line { ref point } => Self::Move { point: point.into(), }, - &PathCommand::HLine { by_to, x } => Self::HLine { - by_to, - x: LengthPercentage::new_length(CSSPixelLength::new(x)), - }, - &PathCommand::VLine { by_to, y } => Self::VLine { - by_to, - y: LengthPercentage::new_length(CSSPixelLength::new(y)), - }, + &PathCommand::HLine { ref x } => Self::HLine { x: x.into() }, + &PathCommand::VLine { ref y } => Self::VLine { y: y.into() }, &PathCommand::CubicCurve { ref point, ref control1, @@ -236,6 +232,25 @@ impl From<&generic::CommandEndPoint<ShapePosition<CSSFloat>, CSSFloat>> for Comm } } +impl From<&generic::AxisEndPoint<CSSFloat>> for AxisEndPoint { + #[inline] + fn from(p: &generic::AxisEndPoint<CSSFloat>) -> Self { + use crate::values::computed::CSSPixelLength; + use generic::AxisPosition; + match p { + generic::AxisEndPoint::ToPosition(AxisPosition::LengthPercent(lp)) => Self::ToPosition( + AxisPosition::LengthPercent(LengthPercentage::new_length(CSSPixelLength::new(*lp))), + ), + generic::AxisEndPoint::ToPosition(AxisPosition::Keyword(_)) => { + unreachable!("Invalid state: SVG path commands cannot contain a keyword.") + }, + generic::AxisEndPoint::ByCoordinate(pos) => { + Self::ByCoordinate(LengthPercentage::new_length(CSSPixelLength::new(*pos))) + }, + } + } +} + impl From<&generic::ControlPoint<ShapePosition<CSSFloat>, CSSFloat>> for ControlPoint { #[inline] fn from(p: &generic::ControlPoint<ShapePosition<CSSFloat>, CSSFloat>) -> Self { diff --git a/servo/components/style/values/generics/basic_shape.rs b/servo/components/style/values/generics/basic_shape.rs @@ -6,6 +6,7 @@ //! types that are generic over their `ToCss` implementations. use crate::values::animated::{lists, Animate, Procedure, ToAnimatedZero}; +use crate::values::computed::Percentage; use crate::values::distance::{ComputeSquaredDistance, SquaredDistance}; use crate::values::generics::{ border::GenericBorderRadius, @@ -221,6 +222,7 @@ pub enum GenericBasicShape<Angle, Position, LengthPercentage, BasicShapeRect> { PathOrShape( #[animation(field_bound)] #[css(field_bound)] + #[compute(field_bound)] GenericPathOrShapeFunction<Angle, Position, LengthPercentage>, ), } @@ -415,7 +417,11 @@ pub enum GenericPathOrShapeFunction<Angle, Position, LengthPercentage> { /// Defines a path with SVG path syntax. Path(Path), /// Defines a shape function, which is identical to path() but it uses the CSS syntax. - Shape(#[css(field_bound)] Shape<Angle, Position, LengthPercentage>), + Shape( + #[css(field_bound)] + #[compute(field_bound)] + Shape<Angle, Position, LengthPercentage>, + ), } // https://drafts.csswg.org/css-shapes/#typedef-fill-rule @@ -649,6 +655,7 @@ pub struct Shape<Angle, Position, LengthPercentage> { /// The shape command data. Note that the starting point will be the first command in this /// slice. // Note: The first command is always GenericShapeCommand::Move. + #[compute(field_bound)] pub commands: crate::OwnedSlice<GenericShapeCommand<Angle, Position, LengthPercentage>>, } @@ -768,9 +775,15 @@ pub enum GenericShapeCommand<Angle, Position, LengthPercentage> { point: CommandEndPoint<Position, LengthPercentage>, }, /// The hline command. - HLine { by_to: ByTo, x: LengthPercentage }, + HLine { + #[compute(field_bound)] + x: AxisEndPoint<LengthPercentage>, + }, /// The vline command. - VLine { by_to: ByTo, y: LengthPercentage }, + VLine { + #[compute(field_bound)] + y: AxisEndPoint<LengthPercentage>, + }, /// The cubic Bézier curve command. CubicCurve { point: CommandEndPoint<Position, LengthPercentage>, @@ -825,16 +838,12 @@ where dest.write_str("line ")?; point.to_css(dest) }, - HLine { by_to, ref x } => { + HLine { ref x } => { dest.write_str("hline ")?; - by_to.to_css(dest)?; - dest.write_char(' ')?; x.to_css(dest) }, - VLine { by_to, ref y } => { + VLine { ref y } => { dest.write_str("vline ")?; - by_to.to_css(dest)?; - dest.write_char(' ')?; y.to_css(dest) }, CubicCurve { @@ -904,8 +913,10 @@ where } } -/// This indicates the command is absolute or relative. -/// https://drafts.csswg.org/css-shapes-2/#typedef-shape-by-to +/// Defines the end point of the command, which can be specified in absolute or relative coordinates, +/// determined by their "to" or "by" components respectively. +/// https://drafts.csswg.org/css-shapes/#typedef-shape-command-end-point +#[allow(missing_docs)] #[derive( Animate, Clone, @@ -914,46 +925,53 @@ where Debug, Deserialize, MallocSizeOf, - Parse, PartialEq, Serialize, SpecifiedValueInfo, ToAnimatedValue, ToAnimatedZero, ToComputedValue, - ToCss, ToResolvedValue, ToShmem, )] -#[repr(u8)] -pub enum ByTo { - /// This indicates that the <coordinate-pair>s are relative to the command’s starting point. - By, - /// This relative to the top-left corner of the reference box. - To, +#[repr(C, u8)] +pub enum CommandEndPoint<Position, LengthPercentage> { + ToPosition(Position), + ByCoordinate(CoordinatePair<LengthPercentage>), } -impl ByTo { +impl<Position, LengthPercentage> CommandEndPoint<Position, LengthPercentage> { /// Return true if it is absolute, i.e. it is To. #[inline] pub fn is_abs(&self) -> bool { - matches!(self, ByTo::To) + matches!(self, CommandEndPoint::ToPosition(_)) } +} - /// Create ByTo based on the flag if it is absolute. - #[inline] - pub fn new(is_abs: bool) -> Self { - if is_abs { - Self::To - } else { - Self::By +impl<Position, LengthPercentage> CommandEndPoint<Position, LengthPercentage> { + fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result + where + W: Write, + Position: ToCss, + LengthPercentage: ToCss, + { + match self { + CommandEndPoint::ToPosition(pos) => { + dest.write_str("to ")?; + pos.to_css(dest) + }, + CommandEndPoint::ByCoordinate(coord) => { + dest.write_str("by ")?; + coord.to_css(dest) + }, } } } -/// Defines the end point of the command, which can be specified in absolute or relative coordinates, -/// determined by their "to" or "by" components respectively. -/// https://drafts.csswg.org/css-shapes/#typedef-shape-command-end-point +/// Defines the end point for the commands <horizontal-line-command> and <vertical-line-command>, which +/// can be specified in absolute or relative values, determined by their "to" or "by" components respectively. +/// https://drafts.csswg.org/css-shapes-1/#typedef-shape-horizontal-line-command +/// https://drafts.csswg.org/css-shapes-1/#typedef-shape-vertical-line-command #[allow(missing_docs)] #[derive( Animate, @@ -964,6 +982,7 @@ impl ByTo { Deserialize, MallocSizeOf, PartialEq, + Parse, Serialize, SpecifiedValueInfo, ToAnimatedValue, @@ -972,36 +991,111 @@ impl ByTo { ToResolvedValue, ToShmem, )] -#[repr(C, u8)] -pub enum CommandEndPoint<Position, LengthPercentage> { - ToPosition(Position), - ByCoordinate(CoordinatePair<LengthPercentage>), +#[repr(u8)] +pub enum AxisEndPoint<LengthPercentage> { + ToPosition(#[compute(field_bound)] AxisPosition<LengthPercentage>), + ByCoordinate(LengthPercentage), } -impl<Position, LengthPercentage> CommandEndPoint<Position, LengthPercentage> { +impl<LengthPercentage> AxisEndPoint<LengthPercentage> { /// Return true if it is absolute, i.e. it is To. #[inline] pub fn is_abs(&self) -> bool { - matches!(self, CommandEndPoint::ToPosition(_)) + matches!(self, AxisEndPoint::ToPosition(_)) } } -impl<Position, LengthPercentage> CommandEndPoint<Position, LengthPercentage> { +impl<LengthPercentage: ToCss> ToCss for AxisEndPoint<LengthPercentage> { fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result where W: Write, - Position: ToCss, - LengthPercentage: ToCss, { + if self.is_abs() { + dest.write_str("to ")?; + } else { + dest.write_str("by ")?; + } match self { - CommandEndPoint::ToPosition(pos) => { - dest.write_str("to ")?; - pos.to_css(dest) - }, - CommandEndPoint::ByCoordinate(coord) => { - dest.write_str("by ")?; - coord.to_css(dest) - }, + AxisEndPoint::ToPosition(pos) => pos.to_css(dest), + AxisEndPoint::ByCoordinate(coord) => coord.to_css(dest), + } + } +} + +/// Defines how the absolutely positioned end point for <horizontal-line-command> and +/// <vertical-line-command> is positioned. +/// https://drafts.csswg.org/css-shapes-1/#typedef-shape-horizontal-line-command +/// https://drafts.csswg.org/css-shapes-1/#typedef-shape-vertical-line-command +#[allow(missing_docs)] +#[derive( + Animate, + Clone, + ComputeSquaredDistance, + Copy, + Debug, + Deserialize, + MallocSizeOf, + Parse, + PartialEq, + Serialize, + SpecifiedValueInfo, + ToAnimatedValue, + ToAnimatedZero, + ToCss, + ToResolvedValue, + ToShmem, +)] +#[repr(u8)] +pub enum AxisPosition<LengthPercentage> { + LengthPercent(LengthPercentage), + Keyword(AxisPositionKeyword), +} + +/// The set of position keywords used in <horizontal-line-command> and <vertical-line-command> +/// for absolute positioning. Note: this is the shared union list between hline and vline, so +/// not every value is valid for either. I.e. hline cannot be positioned with top or y-start. +/// https://drafts.csswg.org/css-shapes-1/#typedef-shape-horizontal-line-command +/// https://drafts.csswg.org/css-shapes-1/#typedef-shape-vertical-line-command +#[allow(missing_docs)] +#[derive( + Animate, + Clone, + ComputeSquaredDistance, + Copy, + Debug, + Deserialize, + MallocSizeOf, + Parse, + PartialEq, + Serialize, + SpecifiedValueInfo, + ToAnimatedValue, + ToAnimatedZero, + ToCss, + ToResolvedValue, + ToShmem, +)] +#[repr(u8)] +pub enum AxisPositionKeyword { + Center, + Left, + Right, + Top, + Bottom, + XStart, + XEnd, + YStart, + YEnd, +} + +impl AxisPositionKeyword { + /// Returns the axis position keyword as its corresponding percentage. + #[inline] + pub fn as_percentage(&self) -> Percentage { + match self { + Self::Center => Percentage(0.5), + Self::Left | Self::Top | Self::XStart | Self::YStart => Percentage(0.), + Self::Right | Self::Bottom | Self::XEnd | Self::YEnd => Percentage(1.), } } } diff --git a/servo/components/style/values/specified/basic_shape.rs b/servo/components/style/values/specified/basic_shape.rs @@ -9,7 +9,9 @@ use crate::parser::{Parse, ParserContext}; use crate::values::computed::basic_shape::InsetRect as ComputedInsetRect; -use crate::values::computed::{Context, ToComputedValue}; +use crate::values::computed::{ + Context, LengthPercentage as ComputedLengthPercentage, ToComputedValue, +}; use crate::values::generics::basic_shape as generic; use crate::values::generics::basic_shape::{Path, PolygonCoord}; use crate::values::generics::position::GenericPositionOrAuto; @@ -22,6 +24,7 @@ use crate::values::specified::position::{Position, Side}; use crate::values::specified::url::SpecifiedUrl; use crate::values::specified::PositionComponent; use crate::values::specified::{LengthPercentage, NonNegativeLengthPercentage, SVGPathData}; +use crate::values::CSSFloat; use crate::Zero; use cssparser::Parser; use std::fmt::{self, Write}; @@ -753,7 +756,7 @@ impl generic::Shape<Angle, Position, LengthPercentage> { // from the top-left corner of the reference i.expect_ident_matching("from")?; Ok(ShapeCommand::Move { - point: generic::CommandEndPoint::parse(context, i, generic::ByTo::To)?, + point: generic::CommandEndPoint::parse_endpoint_as_abs(context, i)?, }) } else { // The further path data commands. @@ -779,7 +782,7 @@ impl Parse for ShapeCommand { input: &mut Parser<'i, 't>, ) -> Result<Self, ParseError<'i>> { use crate::values::generics::basic_shape::{ - ArcRadii, ArcSize, ArcSweep, ByTo, CommandEndPoint, ControlPoint, + ArcRadii, ArcSize, ArcSweep, AxisEndPoint, CommandEndPoint, ControlPoint, }; // <shape-command> = <move-command> | <line-command> | <hv-line-command> | @@ -787,45 +790,27 @@ impl Parse for ShapeCommand { Ok(try_match_ident_ignore_ascii_case! { input, "close" => Self::Close, "move" => { - let by_to = ByTo::parse(input)?; - let point = CommandEndPoint::parse(context, input, by_to)?; + let point = CommandEndPoint::parse(context, input)?; Self::Move { point } }, "line" => { - let by_to = ByTo::parse(input)?; - let point = CommandEndPoint::parse(context, input, by_to)?; + let point = CommandEndPoint::parse(context, input)?; Self::Line { point } }, "hline" => { - let by_to = ByTo::parse(input)?; - // FIXME(Bug 1993311): Using parse_to_position here is incomplete, we should - // parse x-start and x-end too. Furthermore, it currently can incorrectly - // parse 2 offsets as valid (i.e. hline to left 30% works), and similarly - // incorrectly parse top or bottom as valid values. - let x = if by_to.is_abs() { - convert_to_length_percentage(Position::parse(context, input)?.horizontal) - } else { - LengthPercentage::parse(context, input)? - }; - Self::HLine { by_to, x } + let x = AxisEndPoint::parse_hline(context, input)?; + Self::HLine { x } }, "vline" => { - let by_to = ByTo::parse(input)?; - // FIXME(Bug 1993311): Should parse y-start and y-end too. - let y = if by_to.is_abs() { - convert_to_length_percentage(Position::parse(context, input)?.horizontal) - } else { - LengthPercentage::parse(context, input)? - }; - Self::VLine { by_to, y } + let y = AxisEndPoint::parse_vline(context, input)?; + Self::VLine { y } }, "curve" => { - let by_to = ByTo::parse(input)?; - let point = CommandEndPoint::parse(context, input, by_to)?; + let point = CommandEndPoint::parse(context, input)?; input.expect_ident_matching("with")?; - let control1 = ControlPoint::parse(context, input, by_to)?; - if input.expect_delim('/').is_ok() { - let control2 = ControlPoint::parse(context, input, by_to)?; + let control1 = ControlPoint::parse(context, input, point.is_abs())?; + if input.try_parse(|i| i.expect_delim('/')).is_ok() { + let control2 = ControlPoint::parse(context, input, point.is_abs())?; Self::CubicCurve { point, control1, @@ -839,10 +824,9 @@ impl Parse for ShapeCommand { } }, "smooth" => { - let by_to = ByTo::parse(input)?; - let point = CommandEndPoint::parse(context, input, by_to)?; + let point = CommandEndPoint::parse(context, input)?; if input.try_parse(|i| i.expect_ident_matching("with")).is_ok() { - let control2 = ControlPoint::parse(context, input, by_to)?; + let control2 = ControlPoint::parse(context, input, point.is_abs())?; Self::SmoothCubic { point, control2, @@ -852,8 +836,7 @@ impl Parse for ShapeCommand { } }, "arc" => { - let by_to = ByTo::parse(input)?; - let point = CommandEndPoint::parse(context, input, by_to)?; + let point = CommandEndPoint::parse(context, input)?; input.expect_ident_matching("of")?; let rx = LengthPercentage::parse(context, input)?; let ry = input.try_parse(|i| LengthPercentage::parse(context, i)).ok(); @@ -913,12 +896,12 @@ impl generic::ControlPoint<Position, LengthPercentage> { fn parse<'i, 't>( context: &ParserContext, input: &mut Parser<'i, 't>, - by_to: generic::ByTo, + is_end_point_abs: bool, ) -> Result<Self, ParseError<'i>> { let coord = input.try_parse(|i| generic::CoordinatePair::parse(context, i)); // Parse <position> - if by_to.is_abs() && coord.is_err() { + if is_end_point_abs && coord.is_err() { let pos = Position::parse(context, input)?; return Ok(Self::Absolute(pos)); } @@ -936,19 +919,142 @@ impl generic::ControlPoint<Position, LengthPercentage> { } } -impl generic::CommandEndPoint<Position, LengthPercentage> { +impl Parse for generic::CommandEndPoint<Position, LengthPercentage> { /// Parse <command-end-point> = to <position> | by <coordinate-pair> - pub fn parse<'i, 't>( + fn parse<'i, 't>( context: &ParserContext, input: &mut Parser<'i, 't>, - by_to: generic::ByTo, ) -> Result<Self, ParseError<'i>> { - if by_to.is_abs() { - let point = Position::parse(context, input)?; - Ok(Self::ToPosition(point)) + if ByTo::parse(input)?.is_abs() { + Self::parse_endpoint_as_abs(context, input) } else { let point = generic::CoordinatePair::parse(context, input)?; Ok(Self::ByCoordinate(point)) } } } + +impl generic::CommandEndPoint<Position, LengthPercentage> { + /// Parse <command-end-point> = to <position> + fn parse_endpoint_as_abs<'i, 't>( + context: &ParserContext, + input: &mut Parser<'i, 't>, + ) -> Result<Self, ParseError<'i>> { + let point = Position::parse(context, input)?; + Ok(generic::CommandEndPoint::ToPosition(point)) + } +} + +impl generic::AxisEndPoint<LengthPercentage> { + /// Parse <horizontal-line-command> + pub fn parse_hline<'i, 't>( + context: &ParserContext, + input: &mut Parser<'i, 't>, + ) -> Result<Self, ParseError<'i>> { + use cssparser::Token; + use generic::{AxisPosition, AxisPositionKeyword}; + + // If the command is relative, parse for <length-percentage> only. + if !ByTo::parse(input)?.is_abs() { + return Ok(Self::ByCoordinate(LengthPercentage::parse(context, input)?)); + } + + let x = AxisPosition::parse(context, input)?; + if let AxisPosition::Keyword( + _word @ (AxisPositionKeyword::Top + | AxisPositionKeyword::Bottom + | AxisPositionKeyword::YStart + | AxisPositionKeyword::YEnd), + ) = &x + { + let location = input.current_source_location(); + let token = Token::Ident(x.to_css_string().into()); + return Err(location.new_unexpected_token_error(token)); + } + Ok(Self::ToPosition(x)) + } + + /// Parse <vertical-line-command> + pub fn parse_vline<'i, 't>( + context: &ParserContext, + input: &mut Parser<'i, 't>, + ) -> Result<Self, ParseError<'i>> { + use cssparser::Token; + use generic::{AxisPosition, AxisPositionKeyword}; + + // If the command is relative, parse for <length-percentage> only. + if !ByTo::parse(input)?.is_abs() { + return Ok(Self::ByCoordinate(LengthPercentage::parse(context, input)?)); + } + + let y = AxisPosition::parse(context, input)?; + if let AxisPosition::Keyword( + _word @ (AxisPositionKeyword::Left + | AxisPositionKeyword::Right + | AxisPositionKeyword::XStart + | AxisPositionKeyword::XEnd), + ) = &y + { + // Return an error if we parsed a different keyword. + let location = input.current_source_location(); + let token = Token::Ident(y.to_css_string().into()); + return Err(location.new_unexpected_token_error(token)); + } + Ok(Self::ToPosition(y)) + } +} + +impl ToComputedValue for generic::AxisPosition<LengthPercentage> { + type ComputedValue = generic::AxisPosition<ComputedLengthPercentage>; + + fn to_computed_value(&self, context: &Context) -> Self::ComputedValue { + match self { + Self::LengthPercent(lp) => { + Self::ComputedValue::LengthPercent(lp.to_computed_value(context)) + }, + Self::Keyword(word) => { + let lp = LengthPercentage::Percentage(word.as_percentage()); + Self::ComputedValue::LengthPercent(lp.to_computed_value(context)) + }, + } + } + + fn from_computed_value(computed: &Self::ComputedValue) -> Self { + match computed { + Self::ComputedValue::LengthPercent(lp) => { + Self::LengthPercent(LengthPercentage::from_computed_value(lp)) + }, + _ => unreachable!("Invalid state: computed value cannot be a keyword."), + } + } +} + +impl ToComputedValue for generic::AxisPosition<CSSFloat> { + type ComputedValue = Self; + + fn to_computed_value(&self, _context: &Context) -> Self { + *self + } + + fn from_computed_value(computed: &Self) -> Self { + *computed + } +} + +/// This determines whether the command is absolutely or relatively positioned. +/// https://drafts.csswg.org/css-shapes-1/#typedef-shape-command-end-point +#[derive(Clone, Copy, Debug, Parse, PartialEq)] +enum ByTo { + /// Command is relative to the command’s starting point. + By, + /// Command is relative to the top-left corner of the reference box. + To, +} + +impl ByTo { + /// Return true if it is absolute, i.e. it is To. + #[inline] + pub fn is_abs(&self) -> bool { + matches!(self, ByTo::To) + } +} diff --git a/servo/components/style/values/specified/svg_path.rs b/servo/components/style/values/specified/svg_path.rs @@ -9,8 +9,8 @@ use crate::values::animated::{lists, Animate, Procedure}; use crate::values::distance::{ComputeSquaredDistance, SquaredDistance}; use crate::values::generics::basic_shape::GenericShapeCommand; use crate::values::generics::basic_shape::{ - ArcRadii, ArcSize, ArcSweep, ByTo, CommandEndPoint, ControlPoint, ControlReference, - CoordinatePair, RelativeControlPoint, ShapePosition, + ArcRadii, ArcSize, ArcSweep, AxisEndPoint, AxisPosition, CommandEndPoint, ControlPoint, + ControlReference, CoordinatePair, RelativeControlPoint, ShapePosition, }; use crate::values::generics::position::GenericPosition; use crate::values::CSSFloat; @@ -239,32 +239,28 @@ impl PathCommand { } Line { point } }, - HLine { by_to, mut x } => { - if !by_to.is_abs() { - x += state.pos.x; - } - state.pos.x = x; + HLine { mut x } => { + x = x.to_abs(state.pos.x); + state.pos.x = x.into(); if reduce { state.last_command = *self; PathCommand::Line { point: CommandEndPoint::ToPosition(state.pos.into()), } } else { - HLine { by_to: ByTo::To, x } + HLine { x } } }, - VLine { by_to, mut y } => { - if !by_to.is_abs() { - y += state.pos.y; - } - state.pos.y = y; + VLine { mut y } => { + y = y.to_abs(state.pos.y); + state.pos.y = y.into(); if reduce { state.last_command = *self; PathCommand::Line { point: CommandEndPoint::ToPosition(state.pos.into()), } } else { - VLine { by_to: ByTo::To, y } + VLine { y } } }, CubicCurve { @@ -466,15 +462,15 @@ impl PathCommand { dest.write_char(' ')?; CoordPair::from(point).to_css(dest) }, - HLine { by_to, x } => { - dest.write_char(if by_to.is_abs() { 'H' } else { 'h' })?; + HLine { x } => { + dest.write_char(if x.is_abs() { 'H' } else { 'h' })?; dest.write_char(' ')?; - x.to_css(dest) + CSSFloat::from(x).to_css(dest) }, - VLine { by_to, y } => { - dest.write_char(if by_to.is_abs() { 'V' } else { 'v' })?; + VLine { y } => { + dest.write_char(if y.is_abs() { 'V' } else { 'v' })?; dest.write_char(' ')?; - y.to_css(dest) + CSSFloat::from(y).to_css(dest) }, SmoothCubic { point, control2 } => { dest.write_char(if point.is_abs() { 'S' } else { 's' })?; @@ -564,6 +560,19 @@ impl CommandEndPoint<ShapePosition<CSSFloat>, CSSFloat> { } } +impl AxisEndPoint<CSSFloat> { + /// Converts possibly relative end point into absolutely positioned type. + pub fn to_abs(self, base: CSSFloat) -> AxisEndPoint<CSSFloat> { + // Consume self value. + match self { + AxisEndPoint::ToPosition(_) => self, + AxisEndPoint::ByCoordinate(coord) => { + AxisEndPoint::ToPosition(AxisPosition::LengthPercent(coord + base)) + }, + } + } +} + impl ControlPoint<ShapePosition<CSSFloat>, CSSFloat> { /// Converts <control-point> into absolutely positioned control point type. pub fn to_abs( @@ -650,6 +659,19 @@ impl From<CoordPair> for ShapePosition<CSSFloat> { } } +impl From<AxisEndPoint<CSSFloat>> for CSSFloat { + #[inline] + fn from(p: AxisEndPoint<CSSFloat>) -> Self { + match p { + AxisEndPoint::ToPosition(AxisPosition::LengthPercent(a)) => a, + AxisEndPoint::ToPosition(AxisPosition::Keyword(_)) => { + unreachable!("Invalid state: SVG path commands cannot contain a keyword.") + }, + AxisEndPoint::ByCoordinate(a) => a, + } + } +} + impl ToCss for ShapePosition<CSSFloat> { fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result where @@ -721,23 +743,26 @@ impl<'a> PathParser<'a> { } let command = self.chars.next().unwrap(); - let by_to = if command.is_ascii_uppercase() { - ByTo::To - } else { - ByTo::By - }; skip_wsp(&mut self.chars); match command { b'Z' | b'z' => self.parse_closepath(), - b'L' | b'l' => self.parse_lineto(by_to), - b'H' | b'h' => self.parse_h_lineto(by_to), - b'V' | b'v' => self.parse_v_lineto(by_to), - b'C' | b'c' => self.parse_curveto(by_to), - b'S' | b's' => self.parse_smooth_curveto(by_to), - b'Q' | b'q' => self.parse_quadratic_bezier_curveto(by_to), - b'T' | b't' => self.parse_smooth_quadratic_bezier_curveto(by_to), - b'A' | b'a' => self.parse_elliptical_arc(by_to), + b'L' => self.parse_line_abs(), + b'l' => self.parse_line_rel(), + b'H' => self.parse_h_line_abs(), + b'h' => self.parse_h_line_rel(), + b'V' => self.parse_v_line_abs(), + b'v' => self.parse_v_line_rel(), + b'C' => self.parse_curve_abs(), + b'c' => self.parse_curve_rel(), + b'S' => self.parse_smooth_curve_abs(), + b's' => self.parse_smooth_curve_rel(), + b'Q' => self.parse_quadratic_bezier_curve_abs(), + b'q' => self.parse_quadratic_bezier_curve_rel(), + b'T' => self.parse_smooth_quadratic_bezier_curve_abs(), + b't' => self.parse_smooth_quadratic_bezier_curve_rel(), + b'A' => self.parse_elliptical_arc_abs(), + b'a' => self.parse_elliptical_arc_rel(), _ => return Err(()), }?; } @@ -752,11 +777,10 @@ impl<'a> PathParser<'a> { }; skip_wsp(&mut self.chars); - let by_to = if command == b'M' { ByTo::To } else { ByTo::By }; - let point = if by_to == ByTo::To { - parse_command_point_abs(&mut self.chars) + let point = if command == b'M' { + parse_command_end_abs(&mut self.chars) } else { - parse_command_point_rel(&mut self.chars) + parse_command_end_rel(&mut self.chars) }?; self.path.push(PathCommand::Move { point }); @@ -769,7 +793,11 @@ impl<'a> PathParser<'a> { // If a moveto is followed by multiple pairs of coordinates, the subsequent // pairs are treated as implicit lineto commands. - self.parse_lineto(by_to) + if point.is_abs() { + self.parse_line_abs() + } else { + self.parse_line_rel() + } } /// Parse "closepath" command. @@ -778,75 +806,117 @@ impl<'a> PathParser<'a> { Ok(()) } - /// Parse "lineto" command. - fn parse_lineto(&mut self, by_to: ByTo) -> Result<(), ()> { - if by_to.is_abs() { - parse_arguments!(self, Line, [ point => parse_command_point_abs ]) - } else { - parse_arguments!(self, Line, [ point => parse_command_point_rel ]) - } + /// Parse an absolute "lineto" ("L") command. + fn parse_line_abs(&mut self) -> Result<(), ()> { + parse_arguments!(self, Line, [ point => parse_command_end_abs ]) } - /// Parse horizontal "lineto" command. - fn parse_h_lineto(&mut self, by_to: ByTo) -> Result<(), ()> { - parse_arguments!(self, HLine, by_to: by_to, [ x => parse_number ]) + /// Parse a relative "lineto" ("l") command. + fn parse_line_rel(&mut self) -> Result<(), ()> { + parse_arguments!(self, Line, [ point => parse_command_end_rel ]) } - /// Parse vertical "lineto" command. - fn parse_v_lineto(&mut self, by_to: ByTo) -> Result<(), ()> { - parse_arguments!(self, VLine, by_to: by_to, [ y => parse_number ]) + /// Parse an absolute horizontal "lineto" ("H") command. + fn parse_h_line_abs(&mut self) -> Result<(), ()> { + parse_arguments!(self, HLine, [ x => parse_axis_end_abs ]) } - /// Parse cubic Bézier curve command. - fn parse_curveto(&mut self, by_to: ByTo) -> Result<(), ()> { - if by_to.is_abs() { - parse_arguments!(self, CubicCurve, [ - control1 => parse_control_point, control2 => parse_control_point, point => parse_command_point_abs - ]) - } else { - parse_arguments!(self, CubicCurve, [ - control1 => parse_control_point, control2 => parse_control_point, point => parse_command_point_rel - ]) - } + /// Parse a relative horizontal "lineto" ("h") command. + fn parse_h_line_rel(&mut self) -> Result<(), ()> { + parse_arguments!(self, HLine, [ x => parse_axis_end_rel ]) } - /// Parse smooth "curveto" command. - fn parse_smooth_curveto(&mut self, by_to: ByTo) -> Result<(), ()> { - if by_to.is_abs() { - parse_arguments!(self, SmoothCubic, [ - control2 => parse_control_point, point => parse_command_point_abs - ]) - } else { - parse_arguments!(self, SmoothCubic, [ - control2 => parse_control_point, point => parse_command_point_rel - ]) - } + /// Parse an absolute vertical "lineto" ("V") command. + fn parse_v_line_abs(&mut self) -> Result<(), ()> { + parse_arguments!(self, VLine, [ y => parse_axis_end_abs ]) } - /// Parse quadratic Bézier curve command. - fn parse_quadratic_bezier_curveto(&mut self, by_to: ByTo) -> Result<(), ()> { - if by_to.is_abs() { - parse_arguments!(self, QuadCurve, [ - control1 => parse_control_point, point => parse_command_point_abs - ]) - } else { - parse_arguments!(self, QuadCurve, [ - control1 => parse_control_point, point => parse_command_point_rel - ]) - } + /// Parse a relative vertical "lineto" ("v") command. + fn parse_v_line_rel(&mut self) -> Result<(), ()> { + parse_arguments!(self, VLine, [ y => parse_axis_end_rel ]) } - /// Parse smooth quadratic Bézier curveto command. - fn parse_smooth_quadratic_bezier_curveto(&mut self, by_to: ByTo) -> Result<(), ()> { - if by_to.is_abs() { - parse_arguments!(self, SmoothQuad, [ point => parse_command_point_abs ]) - } else { - parse_arguments!(self, SmoothQuad, [ point => parse_command_point_rel ]) - } + /// Parse an absolute cubic Bézier curve ("C") command. + fn parse_curve_abs(&mut self) -> Result<(), ()> { + parse_arguments!(self, CubicCurve, [ + control1 => parse_control_point, control2 => parse_control_point, point => parse_command_end_abs + ]) + } + + /// Parse a relative cubic Bézier curve ("c") command. + fn parse_curve_rel(&mut self) -> Result<(), ()> { + parse_arguments!(self, CubicCurve, [ + control1 => parse_control_point, control2 => parse_control_point, point => parse_command_end_rel + ]) + } + + /// Parse an absolute smooth "curveto" ("S") command. + fn parse_smooth_curve_abs(&mut self) -> Result<(), ()> { + parse_arguments!(self, SmoothCubic, [ + control2 => parse_control_point, point => parse_command_end_abs + ]) + } + + /// Parse a relative smooth "curveto" ("s") command. + fn parse_smooth_curve_rel(&mut self) -> Result<(), ()> { + parse_arguments!(self, SmoothCubic, [ + control2 => parse_control_point, point => parse_command_end_rel + ]) } - /// Parse elliptical arc curve command. - fn parse_elliptical_arc(&mut self, by_to: ByTo) -> Result<(), ()> { + /// Parse an absolute quadratic Bézier curve ("Q") command. + fn parse_quadratic_bezier_curve_abs(&mut self) -> Result<(), ()> { + parse_arguments!(self, QuadCurve, [ + control1 => parse_control_point, point => parse_command_end_abs + ]) + } + + /// Parse a relative quadratic Bézier curve ("q") command. + fn parse_quadratic_bezier_curve_rel(&mut self) -> Result<(), ()> { + parse_arguments!(self, QuadCurve, [ + control1 => parse_control_point, point => parse_command_end_rel + ]) + } + + /// Parse an absolute smooth quadratic Bézier curveto ("T") command. + fn parse_smooth_quadratic_bezier_curve_abs(&mut self) -> Result<(), ()> { + parse_arguments!(self, SmoothQuad, [ point => parse_command_end_abs ]) + } + + /// Parse a relative smooth quadratic Bézier curveto ("t") command. + fn parse_smooth_quadratic_bezier_curve_rel(&mut self) -> Result<(), ()> { + parse_arguments!(self, SmoothQuad, [ point => parse_command_end_rel ]) + } + + /// Parse an absolute elliptical arc curve ("A") command. + fn parse_elliptical_arc_abs(&mut self) -> Result<(), ()> { + let (parse_arc_size, parse_arc_sweep) = Self::arc_flag_parsers(); + parse_arguments!(self, Arc, [ + radii => parse_arc_radii, + rotate => parse_number, + arc_size => parse_arc_size, + arc_sweep => parse_arc_sweep, + point => parse_command_end_abs + ]) + } + + /// Parse a relative elliptical arc curve ("a") command. + fn parse_elliptical_arc_rel(&mut self) -> Result<(), ()> { + let (parse_arc_size, parse_arc_sweep) = Self::arc_flag_parsers(); + parse_arguments!(self, Arc, [ + radii => parse_arc_radii, + rotate => parse_number, + arc_size => parse_arc_size, + arc_sweep => parse_arc_sweep, + point => parse_command_end_rel + ]) + } + + /// Helper that returns parsers for the arc-size and arc-sweep flags. + fn arc_flag_parsers() -> ( + impl Fn(&mut Peekable<Cloned<slice::Iter<'_, u8>>>) -> Result<ArcSize, ()>, + impl Fn(&mut Peekable<Cloned<slice::Iter<'_, u8>>>) -> Result<ArcSweep, ()>, + ) { // Parse a flag whose value is '0' or '1'; otherwise, return Err(()). let parse_arc_size = |iter: &mut Peekable<Cloned<slice::Iter<u8>>>| match iter.next() { Some(c) if c == b'1' => Ok(ArcSize::Large), @@ -858,23 +928,7 @@ impl<'a> PathParser<'a> { Some(c) if c == b'0' => Ok(ArcSweep::Ccw), _ => Err(()), }; - if by_to.is_abs() { - parse_arguments!(self, Arc, [ - radii => parse_arc, - rotate => parse_number, - arc_size => parse_arc_size, - arc_sweep => parse_arc_sweep, - point => parse_command_point_abs - ]) - } else { - parse_arguments!(self, Arc, [ - radii => parse_arc, - rotate => parse_number, - arc_size => parse_arc_size, - arc_sweep => parse_arc_sweep, - point => parse_command_point_rel - ]) - } + (parse_arc_size, parse_arc_sweep) } } @@ -886,16 +940,16 @@ fn parse_coord(iter: &mut Peekable<Cloned<slice::Iter<u8>>>) -> Result<CoordPair Ok(CoordPair::new(x, y)) } -/// Parse a pair of numbers that describes the absolutely positioned endpoint. -fn parse_command_point_abs( +/// Parse a pair of numbers that describes the absolutely positioned end point. +fn parse_command_end_abs( iter: &mut Peekable<Cloned<slice::Iter<u8>>>, ) -> Result<CommandEndPoint<ShapePosition<CSSFloat>, CSSFloat>, ()> { let coord = parse_coord(iter)?; Ok(CommandEndPoint::ToPosition(coord.into())) } -/// Parse a pair of numbers that describes the relatively positioned endpoint. -fn parse_command_point_rel( +/// Parse a pair of numbers that describes the relatively positioned end point. +fn parse_command_end_rel( iter: &mut Peekable<Cloned<slice::Iter<u8>>>, ) -> Result<CommandEndPoint<ShapePosition<CSSFloat>, CSSFloat>, ()> { let coord = parse_coord(iter)?; @@ -916,7 +970,24 @@ fn parse_control_point( })) } -fn parse_arc(iter: &mut Peekable<Cloned<slice::Iter<u8>>>) -> Result<ArcRadii<CSSFloat>, ()> { +/// Parse a number that describes the absolutely positioned axis end point. +fn parse_axis_end_abs( + iter: &mut Peekable<Cloned<slice::Iter<u8>>>, +) -> Result<AxisEndPoint<f32>, ()> { + let value = parse_number(iter)?; + Ok(AxisEndPoint::ToPosition(AxisPosition::LengthPercent(value))) +} + +/// Parse a number that describes the relatively positioned axis end point. +fn parse_axis_end_rel( + iter: &mut Peekable<Cloned<slice::Iter<u8>>>, +) -> Result<AxisEndPoint<f32>, ()> { + let value = parse_number(iter)?; + Ok(AxisEndPoint::ByCoordinate(value)) +} + +/// Parse a pair of numbers that describes the size of the ellipse that the arc is taken from. +fn parse_arc_radii(iter: &mut Peekable<Cloned<slice::Iter<u8>>>) -> Result<ArcRadii<CSSFloat>, ()> { let coord = parse_coord(iter)?; Ok(ArcRadii { rx: coord.x, diff --git a/servo/ports/geckolib/cbindgen.toml b/servo/ports/geckolib/cbindgen.toml @@ -868,6 +868,13 @@ renaming_overrides_prefixing = true }; """ +"AxisEndPoint" = """ + inline gfx::Coord ToGfxCoord(const float* aBasis = nullptr) const; + gfx::Coord ToGfxCoord(const float& aBasis) const { + return ToGfxCoord(&aBasis); + }; +""" + "ControlPoint" = """ inline gfx::Point ToGfxPoint( const gfx::Point aStatePos, const gfx::Point aEndPoint, diff --git a/testing/web-platform/meta/css/css-shapes/shape-functions/shape-function-computed.html.ini b/testing/web-platform/meta/css/css-shapes/shape-functions/shape-function-computed.html.ini @@ -1,20 +0,0 @@ -[shape-function-computed.html] - [Property clip-path value 'shape(from center, curve to center bottom with top right / bottom right)'] - expected: - if (os == "mac") and not debug: [PASS, FAIL] - if (os == "android") and not debug: [PASS, FAIL] - - [Property clip-path value 'shape(from center, curve by 20px 20px with 10px 30px from end / 12px 32px from start)'] - expected: - if (os == "mac") and not debug: [PASS, FAIL] - if (os == "android") and not debug: [PASS, FAIL] - - [Property clip-path value 'shape(from center right, curve by 20px 20px with 10px 30px from origin / 12px 32px from origin)'] - expected: - if (os == "mac") and not debug: [PASS, FAIL] - if (os == "android") and not debug: [PASS, FAIL] - - [Property clip-path value 'shape(from 20px 40px, curve to top right with 10px 30px from origin / 12px 32px from origin)'] - expected: - if (os == "mac") and not debug: [PASS, FAIL] - if (os == "android") and not debug: [PASS, FAIL] diff --git a/testing/web-platform/meta/css/css-shapes/shape-functions/shape-function-invalid.html.ini b/testing/web-platform/meta/css/css-shapes/shape-functions/shape-function-invalid.html.ini @@ -1,15 +0,0 @@ -[shape-function-invalid.html] - [e.style['clip-path'\] = "shape(from 20px 40px, move to 20px 30px, hline to top)" should not set the property value] - expected: FAIL - - [e.style['clip-path'\] = "shape(from 20px 40px, move to 20px 30px, hline to bottom)" should not set the property value] - expected: FAIL - - [e.style['clip-path'\] = "shape(from 20px 40px, move to 20px 30px, vline to left)" should not set the property value] - expected: FAIL - - [e.style['clip-path'\] = "shape(from 20px 40px, move to 20px 30px, vline to right)" should not set the property value] - expected: FAIL - - [e.style['clip-path'\] = "shape(from 20px 40px, move to 20px 30px, hline to right, vline to bottom left, close)" should not set the property value] - expected: FAIL diff --git a/testing/web-platform/meta/css/css-shapes/shape-functions/shape-function-valid.html.ini b/testing/web-platform/meta/css/css-shapes/shape-functions/shape-function-valid.html.ini @@ -1,12 +0,0 @@ -[shape-function-valid.html] - [e.style['clip-path'\] = "shape(from 20px 40px, move to 20px 30px, hline to left, hline to center, hline to right)" should set the property value] - expected: FAIL - - [e.style['clip-path'\] = "shape(from 20px 40px, move to 20px 30px, hline to x-start, vline to y-start)" should set the property value] - expected: FAIL - - [e.style['clip-path'\] = "shape(from 20px 40px, move to 20px 30px, vline to top, vline to center, vline to bottom)" should set the property value] - expected: FAIL - - [e.style['clip-path'\] = "shape(from 20px 40px, move to 20px 30px, vline to y-start, vline to y-end)" should set the property value] - expected: FAIL diff --git a/testing/web-platform/meta/css/motion/parsing/offset-path-shape-parsing.html.ini b/testing/web-platform/meta/css/motion/parsing/offset-path-shape-parsing.html.ini @@ -7,6 +7,3 @@ [e.style['offset-path'\] = "shape(evenodd from 0px 0px, close)" should set the property value] expected: FAIL - - [e.style['offset-path'\] = "shape(from 10px 10px, curve to 50px 20px with 10rem 1% 12px)" should not set the property value] - expected: FAIL diff --git a/testing/web-platform/tests/css/css-shapes/shape-functions/shape-function-computed.html b/testing/web-platform/tests/css/css-shapes/shape-functions/shape-function-computed.html @@ -22,6 +22,15 @@ test_computed_value("clip-path", "shape(from 20px 40px, move to 20px 30px, hline test_computed_value("clip-path", "shape(from 20px 40px, move to 20px 30px, vline to 100px)"); test_computed_value("clip-path", "shape(from 20px 40px, move to 20px 30px, vline by 100%)"); +test_computed_value("clip-path", "shape(from 20px 40px, move to 20px 30px, hline to left, hline to center, hline to right)", + "shape(from 20px 40px, move to 20px 30px, hline to 0%, hline to 50%, hline to 100%)"); +test_computed_value("clip-path", "shape(from 20px 40px, move to 20px 30px, hline to x-start, vline to y-start)", + "shape(from 20px 40px, move to 20px 30px, hline to 0%, vline to 0%)"); +test_computed_value("clip-path", "shape(from 20px 40px, move to 20px 30px, vline to top, vline to center, vline to bottom)", + "shape(from 20px 40px, move to 20px 30px, vline to 0%, vline to 50%, vline to 100%)"); +test_computed_value("clip-path", "shape(from 20px 40px, move to 20px 30px, vline to y-start, vline to y-end)", + "shape(from 20px 40px, move to 20px 30px, vline to 0%, vline to 100%)"); + test_computed_value("clip-path", "shape(from 20px 40px, curve by 20px 20px with 10px 30px)"); test_computed_value("clip-path", "shape(from 20px 40px, curve by 20px 20px with 10px 30px / 12px 32px)");