mirror of
https://github.com/mozilla/gecko-dev.git
synced 2025-02-26 20:30:41 +00:00
Bug 1823463 - Support shape() for clip-path property in style. r=emilio
Implement the style part for shape(). Besides, update some issues in the test file, e.g. avoid using viewport height so we get the fixed result on different devices. I will refactor `PathCommand` to let it be a specialization of `GenericShapeCommand` in the following path. Differential Revision: https://phabricator.services.mozilla.com/D202882
This commit is contained in:
parent
bf0867146e
commit
b9d9282f7f
@ -731,6 +731,9 @@ already_AddRefed<gfx::Path> MotionPathUtils::BuildPath(
|
||||
// building its gfx::Path directly by its SVGPathData without other
|
||||
// reference. https://github.com/w3c/fxtf-drafts/issues/504
|
||||
return BuildSVGPath(aBasicShape.AsPath().path, aPathBuilder);
|
||||
case StyleBasicShape::Tag::Shape:
|
||||
// TODO: Bug 1884424. Suport shape() for offset-path.
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
|
@ -2508,6 +2508,7 @@ nsFloatManager::ShapeInfo::CreateBasicShape(const StyleBasicShape& aBasicShape,
|
||||
return CreateInset(aBasicShape, aShapeMargin, aFrame, aShapeBoxRect, aWM,
|
||||
aContainerSize);
|
||||
case StyleBasicShape::Tag::Path:
|
||||
case StyleBasicShape::Tag::Shape:
|
||||
MOZ_ASSERT_UNREACHABLE("Unsupported basic shape");
|
||||
}
|
||||
return nullptr;
|
||||
|
@ -1,6 +1,7 @@
|
||||
[DEFAULT]
|
||||
prefs = [
|
||||
"layout.css.basic-shape-rect.enabled=true",
|
||||
"layout.css.basic-shape-shape.enabled=true",
|
||||
"layout.css.basic-shape-xywh.enabled=true",
|
||||
"layout.css.properties-and-values.enabled=true",
|
||||
"layout.css.transition-behavior.enabled=true",
|
||||
|
@ -217,10 +217,13 @@ 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" ];
|
||||
var expected = [ "inherit", "initial", "unset", "revert", "revert-layer",
|
||||
"none", "url", "polygon", "circle", "ellipse", "inset",
|
||||
"path", "rect", "xywh", "fill-box", "stroke-box", "view-box",
|
||||
"margin-box", "border-box", "padding-box", "content-box" ];
|
||||
"path", "rect", "xywh", "fill-box", "stroke-box",
|
||||
"view-box", "margin-box", "border-box", "padding-box",
|
||||
"content-box", ...shapeFunction ];
|
||||
var values = InspectorUtils.getCSSValuesForProperty("clip-path");
|
||||
ok(testValues(values, expected), "property clip-path's values");
|
||||
|
||||
|
@ -515,6 +515,9 @@ cbindgen-types = [
|
||||
{ gecko = "StyleBasicShape", servo = "crate::values::computed::basic_shape::BasicShape" },
|
||||
{ gecko = "StyleGenericInsetRect", servo = "crate::values::generics::basic_shape::InsetRect" },
|
||||
{ gecko = "StyleInsetRect", servo = "crate::values::computed::basic_shape::InsetRect" },
|
||||
{ gecko = "StyleShape", servo = "crate::values::computed::basic_shape::Shape" },
|
||||
{ gecko = "StyleShapeCommand", servo = "crate::values::computed::basic_shape::ShapeCommand" },
|
||||
{ gecko = "StyleGenericShapeCommand", servo = "crate::values::generics::basic_shape::ShapeCommand" },
|
||||
{ gecko = "StyleArcSlice", servo = "style_traits::arc_slice::ArcSlice" },
|
||||
{ gecko = "StyleForgottenArcSlicePtr", servo = "style_traits::arc_slice::ForgottenArcSlicePtr" },
|
||||
{ gecko = "StyleOwnedSlice", servo = "style_traits::owned_slice::OwnedSlice" },
|
||||
|
@ -16,6 +16,7 @@ prefs = [
|
||||
"layout.css.scroll-driven-animations.enabled=true",
|
||||
"layout.css.animation-composition.enabled=true",
|
||||
"layout.css.basic-shape-rect.enabled=true",
|
||||
"layout.css.basic-shape-shape.enabled=true",
|
||||
"layout.css.basic-shape-xywh.enabled=true",
|
||||
"layout.css.transform-box-content-stroke.enabled=true",
|
||||
"layout.css.transition-behavior.enabled=true",
|
||||
|
@ -1035,6 +1035,32 @@ if (IsCSSPropertyPrefEnabled("layout.css.basic-shape-rect.enabled")) {
|
||||
);
|
||||
}
|
||||
|
||||
var basicShapeShapeValues = [];
|
||||
if (IsCSSPropertyPrefEnabled("layout.css.basic-shape-shape.enabled")) {
|
||||
basicShapeShapeValues.push(
|
||||
"shape(evenodd from 0px 0px, line to 10px 10px)",
|
||||
"shape(nonzero from 0px 0px, line to 10px 10px)",
|
||||
"shape(from 0px 0%, line to 10px 10%)",
|
||||
"shape(from 10px 10px, move by 10px 5px, line by 20px 40%, close)",
|
||||
"shape(from 10px 10px, hline by 10px, vline to 5rem)",
|
||||
"shape(from 10px 10px, vline by 5%, hline to 1vw)",
|
||||
"shape(from 10px 10px, curve to 50px 20px via 10rem 1%)",
|
||||
"shape(from 10px 10px, smooth to 50px 20px via 10rem 1%)",
|
||||
"shape(from 10% 1rem, arc to 50px 1pt of 20% cw large rotate 25deg)"
|
||||
);
|
||||
|
||||
// It's fine to include this for properties which don't support shape(),
|
||||
// e.g. shape-outside, because they must reject these values.
|
||||
basicShapeInvalidValues.push(
|
||||
"shape()",
|
||||
"shape(evenodd, from 0px 0px)",
|
||||
"shape(from 0px 0px line to 10px 10px)",
|
||||
"shape(from 0px 0px)",
|
||||
"shape(close)",
|
||||
"shape(nonzero, close)"
|
||||
);
|
||||
}
|
||||
|
||||
if (/* mozGradientsEnabled */ true) {
|
||||
// Maybe one day :(
|
||||
// Extend gradient lists with valid/invalid moz-prefixed expressions:
|
||||
@ -8827,7 +8853,8 @@ var gCSSProperties = {
|
||||
.concat(basicShapeSVGBoxValues)
|
||||
.concat(basicShapeOtherValues)
|
||||
.concat(basicShapeOtherValuesWithFillRule)
|
||||
.concat(basicShapeXywhRectValues),
|
||||
.concat(basicShapeXywhRectValues)
|
||||
.concat(basicShapeShapeValues),
|
||||
invalid_values: [
|
||||
"path(nonzero)",
|
||||
"path(abs, 'M 10 10 L 10 10 z')",
|
||||
|
@ -123,6 +123,9 @@ already_AddRefed<Path> CSSClipPathInstance::CreateClipPath(
|
||||
return CreateClipPathInset(aDrawTarget, r);
|
||||
case StyleBasicShape::Tag::Path:
|
||||
return CreateClipPathPath(aDrawTarget, r);
|
||||
case StyleBasicShape::Tag::Shape:
|
||||
// TODO: Support shape() in this patch series.
|
||||
return nullptr;
|
||||
default:
|
||||
MOZ_MAKE_COMPILER_ASSUME_IS_UNREACHABLE("Unexpected shape type");
|
||||
}
|
||||
|
@ -8477,6 +8477,13 @@
|
||||
mirror: always
|
||||
rust: true
|
||||
|
||||
# Is support for shape() enabled?
|
||||
- name: layout.css.basic-shape-shape.enabled
|
||||
type: RelaxedAtomicBool
|
||||
value: @IS_NIGHTLY_BUILD@
|
||||
mirror: always
|
||||
rust: true
|
||||
|
||||
# Is support for xywh() enabled?
|
||||
- name: layout.css.basic-shape-xywh.enabled
|
||||
type: RelaxedAtomicBool
|
||||
|
@ -7,6 +7,7 @@
|
||||
//!
|
||||
//! [basic-shape]: https://drafts.csswg.org/css-shapes/#typedef-basic-shape
|
||||
|
||||
use crate::values::computed::angle::Angle;
|
||||
use crate::values::computed::url::ComputedUrl;
|
||||
use crate::values::computed::{Image, LengthPercentage, NonNegativeLengthPercentage, Position};
|
||||
use crate::values::generics::basic_shape as generic;
|
||||
@ -21,8 +22,13 @@ pub type ClipPath = generic::GenericClipPath<BasicShape, ComputedUrl>;
|
||||
pub type ShapeOutside = generic::GenericShapeOutside<BasicShape, Image>;
|
||||
|
||||
/// A computed basic shape.
|
||||
pub type BasicShape =
|
||||
generic::GenericBasicShape<Position, LengthPercentage, NonNegativeLengthPercentage, InsetRect>;
|
||||
pub type BasicShape = generic::GenericBasicShape<
|
||||
Angle,
|
||||
Position,
|
||||
LengthPercentage,
|
||||
NonNegativeLengthPercentage,
|
||||
InsetRect,
|
||||
>;
|
||||
|
||||
/// The computed value of `inset()`.
|
||||
pub type InsetRect = generic::GenericInsetRect<LengthPercentage, NonNegativeLengthPercentage>;
|
||||
@ -35,3 +41,9 @@ pub type Ellipse = generic::Ellipse<Position, NonNegativeLengthPercentage>;
|
||||
|
||||
/// The computed value of `ShapeRadius`.
|
||||
pub type ShapeRadius = generic::GenericShapeRadius<NonNegativeLengthPercentage>;
|
||||
|
||||
/// The computed value of `shape()`.
|
||||
pub type Shape = generic::Shape<Angle, LengthPercentage>;
|
||||
|
||||
/// The computed value of `ShapeCommand`.
|
||||
pub type ShapeCommand = generic::GenericShapeCommand<Angle, LengthPercentage>;
|
||||
|
@ -181,8 +181,13 @@ pub use self::GenericShapeOutside as ShapeOutside;
|
||||
ToShmem,
|
||||
)]
|
||||
#[repr(C, u8)]
|
||||
pub enum GenericBasicShape<Position, LengthPercentage, NonNegativeLengthPercentage, BasicShapeRect>
|
||||
{
|
||||
pub enum GenericBasicShape<
|
||||
Angle,
|
||||
Position,
|
||||
LengthPercentage,
|
||||
NonNegativeLengthPercentage,
|
||||
BasicShapeRect,
|
||||
> {
|
||||
/// The <basic-shape-rect>.
|
||||
Rect(BasicShapeRect),
|
||||
/// Defines a circle with a center and a radius.
|
||||
@ -201,8 +206,8 @@ pub enum GenericBasicShape<Position, LengthPercentage, NonNegativeLengthPercenta
|
||||
Polygon(GenericPolygon<LengthPercentage>),
|
||||
/// Defines a path with SVG path syntax.
|
||||
Path(Path),
|
||||
// TODO: Bug 1823463. Add shape().
|
||||
// https://drafts.csswg.org/css-shapes-2/#shape-function
|
||||
/// Defines a shape function, which is identical to path(() but it uses the CSS syntax.
|
||||
Shape(#[css(field_bound)] Shape<Angle, LengthPercentage>),
|
||||
}
|
||||
|
||||
pub use self::GenericBasicShape as BasicShape;
|
||||
@ -397,9 +402,9 @@ pub enum FillRule {
|
||||
Evenodd,
|
||||
}
|
||||
|
||||
/// The path function defined in css-shape-2.
|
||||
/// The path function.
|
||||
///
|
||||
/// https://drafts.csswg.org/css-shapes-2/#funcdef-path
|
||||
/// https://drafts.csswg.org/css-shapes-1/#funcdef-basic-shape-path
|
||||
#[derive(
|
||||
Animate,
|
||||
Clone,
|
||||
@ -565,3 +570,454 @@ impl Default for FillRule {
|
||||
fn is_default<T: Default + PartialEq>(fill: &T) -> bool {
|
||||
*fill == Default::default()
|
||||
}
|
||||
|
||||
/// The shape function defined in css-shape-2.
|
||||
/// shape() = shape(<fill-rule>? from <coordinate-pair>, <shape-command>#)
|
||||
///
|
||||
/// https://drafts.csswg.org/css-shapes-2/#shape-function
|
||||
#[derive(
|
||||
Clone,
|
||||
Debug,
|
||||
Deserialize,
|
||||
MallocSizeOf,
|
||||
PartialEq,
|
||||
Serialize,
|
||||
SpecifiedValueInfo,
|
||||
ToAnimatedValue,
|
||||
ToComputedValue,
|
||||
ToResolvedValue,
|
||||
ToShmem,
|
||||
)]
|
||||
#[repr(C)]
|
||||
pub struct Shape<Angle, LengthPercentage> {
|
||||
/// The filling rule for this shape.
|
||||
pub fill: FillRule,
|
||||
/// 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.
|
||||
pub commands: crate::OwnedSlice<GenericShapeCommand<Angle, LengthPercentage>>,
|
||||
}
|
||||
|
||||
impl<Angle, LengthPercentage> Animate for Shape<Angle, LengthPercentage>
|
||||
where
|
||||
Angle: Animate,
|
||||
LengthPercentage: Animate,
|
||||
{
|
||||
fn animate(&self, other: &Self, procedure: Procedure) -> Result<Self, ()> {
|
||||
if self.fill != other.fill {
|
||||
return Err(());
|
||||
}
|
||||
let commands =
|
||||
lists::by_computed_value::animate(&self.commands, &other.commands, procedure)?;
|
||||
Ok(Self {
|
||||
fill: self.fill,
|
||||
commands,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<Angle, LengthPercentage> ComputeSquaredDistance for Shape<Angle, LengthPercentage>
|
||||
where
|
||||
Angle: ComputeSquaredDistance,
|
||||
LengthPercentage: ComputeSquaredDistance,
|
||||
{
|
||||
fn compute_squared_distance(&self, other: &Self) -> Result<SquaredDistance, ()> {
|
||||
if self.fill != other.fill {
|
||||
return Err(());
|
||||
}
|
||||
lists::by_computed_value::squared_distance(&self.commands, &other.commands)
|
||||
}
|
||||
}
|
||||
|
||||
impl<Angle, LengthPercentage> ToCss for Shape<Angle, LengthPercentage>
|
||||
where
|
||||
Angle: ToCss + Zero,
|
||||
LengthPercentage: PartialEq + ToCss,
|
||||
{
|
||||
fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
|
||||
where
|
||||
W: Write,
|
||||
{
|
||||
use style_traits::values::SequenceWriter;
|
||||
|
||||
// Per spec, we must have the first move command and at least one following command.
|
||||
debug_assert!(self.commands.len() > 1);
|
||||
|
||||
dest.write_str("shape(")?;
|
||||
if !is_default(&self.fill) {
|
||||
self.fill.to_css(dest)?;
|
||||
dest.write_char(' ')?;
|
||||
}
|
||||
dest.write_str("from ")?;
|
||||
match self.commands[0] {
|
||||
ShapeCommand::Move {
|
||||
by_to: _,
|
||||
ref point,
|
||||
} => point.to_css(dest)?,
|
||||
_ => unreachable!("The first command must be move"),
|
||||
}
|
||||
dest.write_str(", ")?;
|
||||
{
|
||||
let mut writer = SequenceWriter::new(dest, ", ");
|
||||
for command in self.commands.iter().skip(1) {
|
||||
writer.item(command)?;
|
||||
}
|
||||
}
|
||||
dest.write_char(')')
|
||||
}
|
||||
}
|
||||
|
||||
/// This is a more general shape(path) command type, for both shape() and path().
|
||||
///
|
||||
/// https://www.w3.org/TR/SVG11/paths.html#PathData
|
||||
/// https://drafts.csswg.org/css-shapes-2/#shape-function
|
||||
#[derive(
|
||||
Animate,
|
||||
Clone,
|
||||
ComputeSquaredDistance,
|
||||
Copy,
|
||||
Debug,
|
||||
Deserialize,
|
||||
MallocSizeOf,
|
||||
PartialEq,
|
||||
Serialize,
|
||||
SpecifiedValueInfo,
|
||||
ToAnimatedValue,
|
||||
ToAnimatedZero,
|
||||
ToComputedValue,
|
||||
ToResolvedValue,
|
||||
ToShmem,
|
||||
)]
|
||||
#[allow(missing_docs)]
|
||||
#[repr(C, u8)]
|
||||
pub enum GenericShapeCommand<Angle, LengthPercentage> {
|
||||
/// The move command.
|
||||
Move {
|
||||
by_to: ByTo,
|
||||
point: CoordinatePair<LengthPercentage>,
|
||||
},
|
||||
/// The line command.
|
||||
Line {
|
||||
by_to: ByTo,
|
||||
point: CoordinatePair<LengthPercentage>,
|
||||
},
|
||||
/// The hline command.
|
||||
HLine { by_to: ByTo, x: LengthPercentage },
|
||||
/// The vline command.
|
||||
VLine { by_to: ByTo, y: LengthPercentage },
|
||||
/// The cubic Bézier curve command.
|
||||
CubicCurve {
|
||||
by_to: ByTo,
|
||||
point: CoordinatePair<LengthPercentage>,
|
||||
control1: CoordinatePair<LengthPercentage>,
|
||||
control2: CoordinatePair<LengthPercentage>,
|
||||
},
|
||||
/// The quadratic Bézier curve command.
|
||||
QuadCurve {
|
||||
by_to: ByTo,
|
||||
point: CoordinatePair<LengthPercentage>,
|
||||
control1: CoordinatePair<LengthPercentage>,
|
||||
},
|
||||
/// The smooth command.
|
||||
SmoothCubic {
|
||||
by_to: ByTo,
|
||||
point: CoordinatePair<LengthPercentage>,
|
||||
control2: CoordinatePair<LengthPercentage>,
|
||||
},
|
||||
/// The smooth quadratic Bézier curve command.
|
||||
SmoothQuad {
|
||||
by_to: ByTo,
|
||||
point: CoordinatePair<LengthPercentage>,
|
||||
},
|
||||
/// The arc command.
|
||||
Arc {
|
||||
by_to: ByTo,
|
||||
point: CoordinatePair<LengthPercentage>,
|
||||
radii: CoordinatePair<LengthPercentage>,
|
||||
arc_sweep: ArcSweep,
|
||||
arc_size: ArcSize,
|
||||
rotate: Angle,
|
||||
},
|
||||
/// The closepath command.
|
||||
Close,
|
||||
}
|
||||
|
||||
pub use self::GenericShapeCommand as ShapeCommand;
|
||||
|
||||
impl<Angle, LengthPercentage> ToCss for ShapeCommand<Angle, LengthPercentage>
|
||||
where
|
||||
Angle: ToCss + Zero,
|
||||
LengthPercentage: PartialEq + ToCss,
|
||||
{
|
||||
fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
|
||||
where
|
||||
W: fmt::Write,
|
||||
{
|
||||
use self::ShapeCommand::*;
|
||||
match *self {
|
||||
Move { by_to, ref point } => {
|
||||
dest.write_str("move ")?;
|
||||
by_to.to_css(dest)?;
|
||||
dest.write_char(' ')?;
|
||||
point.to_css(dest)
|
||||
},
|
||||
Line { by_to, ref point } => {
|
||||
dest.write_str("line ")?;
|
||||
by_to.to_css(dest)?;
|
||||
dest.write_char(' ')?;
|
||||
point.to_css(dest)
|
||||
},
|
||||
HLine { by_to, ref x } => {
|
||||
dest.write_str("hline ")?;
|
||||
by_to.to_css(dest)?;
|
||||
dest.write_char(' ')?;
|
||||
x.to_css(dest)
|
||||
},
|
||||
VLine { by_to, ref y } => {
|
||||
dest.write_str("vline ")?;
|
||||
by_to.to_css(dest)?;
|
||||
dest.write_char(' ')?;
|
||||
y.to_css(dest)
|
||||
},
|
||||
CubicCurve {
|
||||
by_to,
|
||||
ref point,
|
||||
ref control1,
|
||||
ref control2,
|
||||
} => {
|
||||
dest.write_str("curve ")?;
|
||||
by_to.to_css(dest)?;
|
||||
dest.write_char(' ')?;
|
||||
point.to_css(dest)?;
|
||||
dest.write_str(" via ")?;
|
||||
control1.to_css(dest)?;
|
||||
dest.write_char(' ')?;
|
||||
control2.to_css(dest)
|
||||
},
|
||||
QuadCurve {
|
||||
by_to,
|
||||
ref point,
|
||||
ref control1,
|
||||
} => {
|
||||
dest.write_str("curve ")?;
|
||||
by_to.to_css(dest)?;
|
||||
dest.write_char(' ')?;
|
||||
point.to_css(dest)?;
|
||||
dest.write_str(" via ")?;
|
||||
control1.to_css(dest)
|
||||
},
|
||||
SmoothCubic {
|
||||
by_to,
|
||||
ref point,
|
||||
ref control2,
|
||||
} => {
|
||||
dest.write_str("smooth ")?;
|
||||
by_to.to_css(dest)?;
|
||||
dest.write_char(' ')?;
|
||||
point.to_css(dest)?;
|
||||
dest.write_str(" via ")?;
|
||||
control2.to_css(dest)
|
||||
},
|
||||
SmoothQuad { by_to, ref point } => {
|
||||
dest.write_str("smooth ")?;
|
||||
by_to.to_css(dest)?;
|
||||
dest.write_char(' ')?;
|
||||
point.to_css(dest)
|
||||
},
|
||||
Arc {
|
||||
by_to,
|
||||
ref point,
|
||||
ref radii,
|
||||
arc_sweep,
|
||||
arc_size,
|
||||
ref rotate,
|
||||
} => {
|
||||
dest.write_str("arc ")?;
|
||||
by_to.to_css(dest)?;
|
||||
dest.write_char(' ')?;
|
||||
point.to_css(dest)?;
|
||||
dest.write_str(" of ")?;
|
||||
radii.x.to_css(dest)?;
|
||||
if radii.x != radii.y {
|
||||
dest.write_char(' ')?;
|
||||
radii.y.to_css(dest)?;
|
||||
}
|
||||
|
||||
if matches!(arc_sweep, ArcSweep::Cw) {
|
||||
dest.write_str(" cw")?;
|
||||
}
|
||||
|
||||
if matches!(arc_size, ArcSize::Large) {
|
||||
dest.write_str(" large")?;
|
||||
}
|
||||
|
||||
if !rotate.is_zero() {
|
||||
dest.write_str(" rotate ")?;
|
||||
rotate.to_css(dest)?;
|
||||
}
|
||||
Ok(())
|
||||
},
|
||||
Close => dest.write_str("close"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// This indicates the command is absolute or relative.
|
||||
/// https://drafts.csswg.org/css-shapes-2/#typedef-shape-by-to
|
||||
#[derive(
|
||||
Animate,
|
||||
Clone,
|
||||
ComputeSquaredDistance,
|
||||
Copy,
|
||||
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,
|
||||
}
|
||||
|
||||
/// Defines a pair of coordinates, representing a rightward and downward offset, respectively, from
|
||||
/// a specified reference point. Percentages are resolved against the width or height,
|
||||
/// respectively, of the reference box.
|
||||
/// https://drafts.csswg.org/css-shapes-2/#typedef-shape-coordinate-pair
|
||||
#[allow(missing_docs)]
|
||||
#[derive(
|
||||
Animate,
|
||||
Clone,
|
||||
ComputeSquaredDistance,
|
||||
Copy,
|
||||
Debug,
|
||||
Deserialize,
|
||||
MallocSizeOf,
|
||||
PartialEq,
|
||||
Serialize,
|
||||
SpecifiedValueInfo,
|
||||
ToAnimatedValue,
|
||||
ToAnimatedZero,
|
||||
ToComputedValue,
|
||||
ToCss,
|
||||
ToResolvedValue,
|
||||
ToShmem,
|
||||
)]
|
||||
#[repr(C)]
|
||||
pub struct CoordinatePair<LengthPercentage> {
|
||||
x: LengthPercentage,
|
||||
y: LengthPercentage,
|
||||
}
|
||||
|
||||
impl<LengthPercentage> CoordinatePair<LengthPercentage> {
|
||||
/// Create a CoordinatePair.
|
||||
#[inline]
|
||||
pub fn new(x: LengthPercentage, y: LengthPercentage) -> Self {
|
||||
Self { x, y }
|
||||
}
|
||||
}
|
||||
|
||||
/// This indicates that the arc that is traced around the ellipse clockwise or counter-clockwise
|
||||
/// from the center.
|
||||
/// https://drafts.csswg.org/css-shapes-2/#typedef-shape-arc-sweep
|
||||
#[derive(
|
||||
Clone,
|
||||
Copy,
|
||||
Debug,
|
||||
Deserialize,
|
||||
MallocSizeOf,
|
||||
Parse,
|
||||
PartialEq,
|
||||
Serialize,
|
||||
SpecifiedValueInfo,
|
||||
ToAnimatedValue,
|
||||
ToAnimatedZero,
|
||||
ToComputedValue,
|
||||
ToCss,
|
||||
ToResolvedValue,
|
||||
ToShmem,
|
||||
)]
|
||||
#[repr(u8)]
|
||||
pub enum ArcSweep {
|
||||
/// Counter-clockwise. The default value. (This also represents 0 in the svg path.)
|
||||
Ccw = 0,
|
||||
/// Clockwise. (This also represents 1 in the svg path.)
|
||||
Cw = 1,
|
||||
}
|
||||
|
||||
impl Animate for ArcSweep {
|
||||
fn animate(&self, other: &Self, procedure: Procedure) -> Result<Self, ()> {
|
||||
// If an arc command has different <arc-sweep> between its starting and ending list, then
|
||||
// the interpolated result uses cw for any progress value between 0 and 1.
|
||||
(*self as i32)
|
||||
.animate(&(*other as i32), procedure)
|
||||
.map(|v| if v > 0 { ArcSweep::Cw } else { ArcSweep::Ccw })
|
||||
}
|
||||
}
|
||||
|
||||
impl ComputeSquaredDistance for ArcSweep {
|
||||
fn compute_squared_distance(&self, other: &Self) -> Result<SquaredDistance, ()> {
|
||||
(*self as i32).compute_squared_distance(&(*other as i32))
|
||||
}
|
||||
}
|
||||
|
||||
/// This indicates that the larger or smaller, respectively, of the two possible arcs must be
|
||||
/// chosen.
|
||||
/// https://drafts.csswg.org/css-shapes-2/#typedef-shape-arc-size
|
||||
#[derive(
|
||||
Clone,
|
||||
Copy,
|
||||
Debug,
|
||||
Deserialize,
|
||||
MallocSizeOf,
|
||||
Parse,
|
||||
PartialEq,
|
||||
Serialize,
|
||||
SpecifiedValueInfo,
|
||||
ToAnimatedValue,
|
||||
ToAnimatedZero,
|
||||
ToComputedValue,
|
||||
ToCss,
|
||||
ToResolvedValue,
|
||||
ToShmem,
|
||||
)]
|
||||
#[repr(u8)]
|
||||
pub enum ArcSize {
|
||||
/// Choose the small one. The default value. (This also represents 0 in the svg path.)
|
||||
Small = 0,
|
||||
/// Choose the large one. (This also represents 1 in the svg path.)
|
||||
Large = 1,
|
||||
}
|
||||
|
||||
impl Animate for ArcSize {
|
||||
fn animate(&self, other: &Self, procedure: Procedure) -> Result<Self, ()> {
|
||||
// If it has different <arc-size> keywords, then the interpolated result uses large for any
|
||||
// progress value between 0 and 1.
|
||||
(*self as i32)
|
||||
.animate(&(*other as i32), procedure)
|
||||
.map(|v| {
|
||||
if v > 0 {
|
||||
ArcSize::Large
|
||||
} else {
|
||||
ArcSize::Small
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl ComputeSquaredDistance for ArcSize {
|
||||
fn compute_squared_distance(&self, other: &Self) -> Result<SquaredDistance, ()> {
|
||||
(*self as i32).compute_squared_distance(&(*other as i32))
|
||||
}
|
||||
}
|
||||
|
@ -14,6 +14,7 @@ use crate::values::generics::basic_shape as generic;
|
||||
use crate::values::generics::basic_shape::{Path, PolygonCoord};
|
||||
use crate::values::generics::position::{GenericPosition, GenericPositionOrAuto};
|
||||
use crate::values::generics::rect::Rect;
|
||||
use crate::values::specified::angle::Angle;
|
||||
use crate::values::specified::border::BorderRadius;
|
||||
use crate::values::specified::image::Image;
|
||||
use crate::values::specified::length::LengthPercentageOrAuto;
|
||||
@ -40,6 +41,7 @@ pub type ShapePosition = GenericPosition<LengthPercentage, LengthPercentage>;
|
||||
|
||||
/// A specified basic shape.
|
||||
pub type BasicShape = generic::GenericBasicShape<
|
||||
Angle,
|
||||
ShapePosition,
|
||||
LengthPercentage,
|
||||
NonNegativeLengthPercentage,
|
||||
@ -61,6 +63,9 @@ pub type ShapeRadius = generic::ShapeRadius<NonNegativeLengthPercentage>;
|
||||
/// The specified value of `Polygon`.
|
||||
pub type Polygon = generic::GenericPolygon<LengthPercentage>;
|
||||
|
||||
/// The specified value of `ShapeCommand`.
|
||||
pub type ShapeCommand = generic::GenericShapeCommand<Angle, LengthPercentage>;
|
||||
|
||||
/// The specified value of `xywh()`.
|
||||
/// Defines a rectangle via offsets from the top and left edge of the reference box, and a
|
||||
/// specified width and height.
|
||||
@ -168,8 +173,8 @@ bitflags! {
|
||||
const POLYGON = 1 << 5;
|
||||
/// path().
|
||||
const PATH = 1 << 6;
|
||||
// TODO: Bug 1823463. Add shape().
|
||||
// const SHAPE = 1 << 7;
|
||||
/// shape().
|
||||
const SHAPE = 1 << 7;
|
||||
|
||||
/// All flags.
|
||||
const ALL =
|
||||
@ -248,7 +253,9 @@ impl Parse for ClipPath {
|
||||
input,
|
||||
ClipPath::Shape,
|
||||
ClipPath::Box,
|
||||
AllowedBasicShapes::ALL,
|
||||
// TODO: Bug 1884424. We will merge SHAPE into ALL when supporting shape() on
|
||||
// offset-path.
|
||||
AllowedBasicShapes::ALL | AllowedBasicShapes::SHAPE,
|
||||
)
|
||||
}
|
||||
}
|
||||
@ -331,6 +338,13 @@ impl BasicShape {
|
||||
"path" if flags.contains(AllowedBasicShapes::PATH) => {
|
||||
Path::parse_function_arguments(i, shape_type).map(BasicShape::Path)
|
||||
},
|
||||
"shape"
|
||||
if flags.contains(AllowedBasicShapes::SHAPE)
|
||||
&& static_prefs::pref!("layout.css.basic-shape-shape.enabled") =>
|
||||
{
|
||||
generic::Shape::parse_function_arguments(context, i, shape_type)
|
||||
.map(BasicShape::Shape)
|
||||
},
|
||||
_ => Err(location
|
||||
.new_custom_error(StyleParseErrorKind::UnexpectedFunction(function.clone()))),
|
||||
}
|
||||
@ -490,7 +504,11 @@ impl Ellipse {
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_fill_rule<'i, 't>(input: &mut Parser<'i, 't>, shape_type: ShapeType) -> FillRule {
|
||||
fn parse_fill_rule<'i, 't>(
|
||||
input: &mut Parser<'i, 't>,
|
||||
shape_type: ShapeType,
|
||||
expect_comma: bool,
|
||||
) -> FillRule {
|
||||
match shape_type {
|
||||
// Per [1] and [2], we ignore `<fill-rule>` for outline shapes, so always use a default
|
||||
// value.
|
||||
@ -508,7 +526,9 @@ fn parse_fill_rule<'i, 't>(input: &mut Parser<'i, 't>, shape_type: ShapeType) ->
|
||||
ShapeType::Filled => input
|
||||
.try_parse(|i| -> Result<_, ParseError> {
|
||||
let fill = FillRule::parse(i)?;
|
||||
i.expect_comma()?;
|
||||
if expect_comma {
|
||||
i.expect_comma()?;
|
||||
}
|
||||
Ok(fill)
|
||||
})
|
||||
.unwrap_or_default(),
|
||||
@ -532,7 +552,7 @@ impl Polygon {
|
||||
input: &mut Parser<'i, 't>,
|
||||
shape_type: ShapeType,
|
||||
) -> Result<Self, ParseError<'i>> {
|
||||
let fill = parse_fill_rule(input, shape_type);
|
||||
let fill = parse_fill_rule(input, shape_type, true /* has comma */);
|
||||
let coordinates = input
|
||||
.parse_comma_separated(|i| {
|
||||
Ok(PolygonCoord(
|
||||
@ -554,7 +574,7 @@ impl Path {
|
||||
) -> Result<Self, ParseError<'i>> {
|
||||
use crate::values::specified::svg_path::AllowEmpty;
|
||||
|
||||
let fill = parse_fill_rule(input, shape_type);
|
||||
let fill = parse_fill_rule(input, shape_type, true /* has comma */);
|
||||
let path = SVGPathData::parse(input, AllowEmpty::No)?;
|
||||
Ok(Path { fill, path })
|
||||
}
|
||||
@ -717,3 +737,168 @@ impl ToComputedValue for BasicShapeRect {
|
||||
Self::Inset(ToComputedValue::from_computed_value(computed))
|
||||
}
|
||||
}
|
||||
|
||||
impl generic::Shape<Angle, LengthPercentage> {
|
||||
/// Parse the inner arguments of a `shape` function.
|
||||
/// shape() = shape(<fill-rule>? from <coordinate-pair>, <shape-command>#)
|
||||
fn parse_function_arguments<'i, 't>(
|
||||
context: &ParserContext,
|
||||
input: &mut Parser<'i, 't>,
|
||||
shape_type: ShapeType,
|
||||
) -> Result<Self, ParseError<'i>> {
|
||||
let fill = parse_fill_rule(input, shape_type, false /* no following comma */);
|
||||
|
||||
let mut first = true;
|
||||
let commands = input.parse_comma_separated(|i| {
|
||||
if first {
|
||||
first = false;
|
||||
|
||||
// The starting point for the first shape-command. It adds an initial absolute
|
||||
// moveto to the list of path data commands, with the <coordinate-pair> measured
|
||||
// from the top-left corner of the reference
|
||||
i.expect_ident_matching("from")?;
|
||||
Ok(ShapeCommand::Move {
|
||||
by_to: generic::ByTo::To,
|
||||
point: generic::CoordinatePair::parse(context, i)?,
|
||||
})
|
||||
} else {
|
||||
// The further path data commands.
|
||||
ShapeCommand::parse(context, i)
|
||||
}
|
||||
})?;
|
||||
|
||||
// We must have one starting point and at least one following <shape-command>.
|
||||
if commands.len() < 2 {
|
||||
return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError));
|
||||
}
|
||||
|
||||
Ok(Self {
|
||||
fill,
|
||||
commands: commands.into(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Parse for ShapeCommand {
|
||||
fn parse<'i, 't>(
|
||||
context: &ParserContext,
|
||||
input: &mut Parser<'i, 't>,
|
||||
) -> Result<Self, ParseError<'i>> {
|
||||
use crate::values::generics::basic_shape::{ArcSize, ArcSweep, ByTo, CoordinatePair};
|
||||
|
||||
// <shape-command> = <move-command> | <line-command> | <hv-line-command> |
|
||||
// <curve-command> | <smooth-command> | <arc-command> | close
|
||||
Ok(try_match_ident_ignore_ascii_case! { input,
|
||||
"close" => Self::Close,
|
||||
"move" => {
|
||||
let by_to = ByTo::parse(input)?;
|
||||
let point = CoordinatePair::parse(context, input)?;
|
||||
Self::Move { by_to, point }
|
||||
},
|
||||
"line" => {
|
||||
let by_to = ByTo::parse(input)?;
|
||||
let point = CoordinatePair::parse(context, input)?;
|
||||
Self::Line { by_to, point }
|
||||
},
|
||||
"hline" => {
|
||||
let by_to = ByTo::parse(input)?;
|
||||
let x = LengthPercentage::parse(context, input)?;
|
||||
Self::HLine { by_to, x }
|
||||
},
|
||||
"vline" => {
|
||||
let by_to = ByTo::parse(input)?;
|
||||
let y = LengthPercentage::parse(context, input)?;
|
||||
Self::VLine { by_to, y }
|
||||
},
|
||||
"curve" => {
|
||||
let by_to = ByTo::parse(input)?;
|
||||
let point = CoordinatePair::parse(context, input)?;
|
||||
input.expect_ident_matching("via")?;
|
||||
let control1 = CoordinatePair::parse(context, input)?;
|
||||
match input.try_parse(|i| CoordinatePair::parse(context, i)) {
|
||||
Ok(control2) => Self::CubicCurve {
|
||||
by_to,
|
||||
point,
|
||||
control1,
|
||||
control2,
|
||||
},
|
||||
Err(_) => Self::QuadCurve {
|
||||
by_to,
|
||||
point,
|
||||
control1,
|
||||
},
|
||||
}
|
||||
},
|
||||
"smooth" => {
|
||||
let by_to = ByTo::parse(input)?;
|
||||
let point = CoordinatePair::parse(context, input)?;
|
||||
if input.try_parse(|i| i.expect_ident_matching("via")).is_ok() {
|
||||
let control2 = CoordinatePair::parse(context, input)?;
|
||||
Self::SmoothCubic {
|
||||
by_to,
|
||||
point,
|
||||
control2,
|
||||
}
|
||||
} else {
|
||||
Self::SmoothQuad { by_to, point }
|
||||
}
|
||||
},
|
||||
"arc" => {
|
||||
let by_to = ByTo::parse(input)?;
|
||||
let point = CoordinatePair::parse(context, input)?;
|
||||
input.expect_ident_matching("of")?;
|
||||
let rx = LengthPercentage::parse(context, input)?;
|
||||
let ry = input
|
||||
.try_parse(|i| LengthPercentage::parse(context, i))
|
||||
.unwrap_or(rx.clone());
|
||||
let radii = CoordinatePair::new(rx, ry);
|
||||
|
||||
// [<arc-sweep> || <arc-size> || rotate <angle>]?
|
||||
let mut arc_sweep = None;
|
||||
let mut arc_size = None;
|
||||
let mut rotate = None;
|
||||
loop {
|
||||
if arc_sweep.is_none() {
|
||||
arc_sweep = input.try_parse(ArcSweep::parse).ok();
|
||||
}
|
||||
|
||||
if arc_size.is_none() {
|
||||
arc_size = input.try_parse(ArcSize::parse).ok();
|
||||
if arc_size.is_some() {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if rotate.is_none()
|
||||
&& input
|
||||
.try_parse(|i| i.expect_ident_matching("rotate"))
|
||||
.is_ok()
|
||||
{
|
||||
rotate = Some(Angle::parse(context, input)?);
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
}
|
||||
Self::Arc {
|
||||
by_to,
|
||||
point,
|
||||
radii,
|
||||
arc_sweep: arc_sweep.unwrap_or(ArcSweep::Ccw),
|
||||
arc_size: arc_size.unwrap_or(ArcSize::Small),
|
||||
rotate: rotate.unwrap_or(Angle::zero()),
|
||||
}
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Parse for generic::CoordinatePair<LengthPercentage> {
|
||||
fn parse<'i, 't>(
|
||||
context: &ParserContext,
|
||||
input: &mut Parser<'i, 't>,
|
||||
) -> Result<Self, ParseError<'i>> {
|
||||
let x = LengthPercentage::parse(context, input)?;
|
||||
let y = LengthPercentage::parse(context, input)?;
|
||||
Ok(Self::new(x, y))
|
||||
}
|
||||
}
|
||||
|
@ -1 +1 @@
|
||||
prefs: [layout.css.basic-shape-rect.enabled:true, layout.css.basic-shape-xywh.enabled:true]
|
||||
prefs: [layout.css.basic-shape-rect.enabled:true, layout.css.basic-shape-shape.enabled:true, layout.css.basic-shape-xywh.enabled:true]
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -1,60 +0,0 @@
|
||||
[clip-path-shape-parsing.html]
|
||||
[e.style['clip-path'\] = "shape(from 0px 0px, line to 10px 10px)" should set the property value]
|
||||
expected: FAIL
|
||||
|
||||
[e.style['clip-path'\] = "shape(evenodd from 0px 0px, line to 10px 10px)" should set the property value]
|
||||
expected: FAIL
|
||||
|
||||
[e.style['clip-path'\] = "shape(nonzero from 0px 0px, line to 10px 10px)" should set the property value]
|
||||
expected: FAIL
|
||||
|
||||
[e.style['clip-path'\] = "shape( from 0px 0px, line to 10px 10px )" should set the property value]
|
||||
expected: FAIL
|
||||
|
||||
[e.style['clip-path'\] = "shape(from 1em 50%, line to 10px 10px)" should set the property value]
|
||||
expected: FAIL
|
||||
|
||||
[e.style['clip-path'\] = "shape(EvenOdd from 0px 0Px, CLOSE)" should set the property value]
|
||||
expected: FAIL
|
||||
|
||||
[e.style['clip-path'\] = "shape(from 1ch 50px, line to 10rem 10vh)" should set the property value]
|
||||
expected: FAIL
|
||||
|
||||
[e.style['clip-path'\] = "shape(from 1ch -50px, line to -10% 12px)" should set the property value]
|
||||
expected: FAIL
|
||||
|
||||
[e.style['clip-path'\] = "shape(from 10px 10px, move by 10px 5px, line by 20px 40%, close)" should set the property value]
|
||||
expected: FAIL
|
||||
|
||||
[e.style['clip-path'\] = "shape(from 10px 10px, hline by 10px, vline to 5rem)" should set the property value]
|
||||
expected: FAIL
|
||||
|
||||
[e.style['clip-path'\] = "shape(from 10px 10px, vline by 5%, hline to 1vw)" should set the property value]
|
||||
expected: FAIL
|
||||
|
||||
[e.style['clip-path'\] = "shape(from 10px 10px, curve to 50px 20px via 10rem 1%)" should set the property value]
|
||||
expected: FAIL
|
||||
|
||||
[e.style['clip-path'\] = "shape(from 10px 10px, curve to 50px 20px via 10rem 1px 20vh 1ch)" should set the property value]
|
||||
expected: FAIL
|
||||
|
||||
[e.style['clip-path'\] = "shape(from 10px 10px, curve by 50px 20px via 10rem 1px 20vh 1ch)" should set the property value]
|
||||
expected: FAIL
|
||||
|
||||
[e.style['clip-path'\] = "shape(from 10px 10px, smooth to 50px 20px via 10rem 1%)" should set the property value]
|
||||
expected: FAIL
|
||||
|
||||
[e.style['clip-path'\] = "shape(from 10px 10px, smooth to 50px 1pt)" should set the property value]
|
||||
expected: FAIL
|
||||
|
||||
[e.style['clip-path'\] = "shape(from 10px 10px, arc to 50px 1pt of 10px 10px)" should set the property value]
|
||||
expected: FAIL
|
||||
|
||||
[e.style['clip-path'\] = "shape(from 10px 10px, arc to 50px 1pt of 10px 10px small rotate 0deg)" should set the property value]
|
||||
expected: FAIL
|
||||
|
||||
[e.style['clip-path'\] = "shape(from 10% 1rem, arc to 50px 1pt of 20% cw large rotate 25deg)" should set the property value]
|
||||
expected: FAIL
|
||||
|
||||
[e.style['clip-path'\] = "shape(evenodd from 0px 0px, close)" should set the property value]
|
||||
expected: FAIL
|
@ -1,10 +1,10 @@
|
||||
<!DOCTYPE html>
|
||||
<meta charset="UTF-8">
|
||||
<meta charset="UTF-8">
|
||||
<title>clip-path-interpolation</title>
|
||||
<link rel="stylesheet" type="text/css" href="/fonts/ahem.css" />
|
||||
<link rel="help" href="https://drafts.fxtf.org/css-masking-1/#the-clip-path">
|
||||
<meta name="assert" content="clip-path supports animation">
|
||||
<link rel="help" href="https://drafts.csswg.org/css-shapes-2/#interpolating-shape">
|
||||
<meta name="assert" content="clip-path supports animation for shape()">
|
||||
|
||||
<script src="/resources/testharness.js"></script>
|
||||
<script src="/resources/testharnessreport.js"></script>
|
||||
@ -149,14 +149,14 @@ test_interpolation({
|
||||
{at: -0.3, expect: 'shape(from 2% 2px, arc to 18% -12px of 7px 17px ccw small, arc by 12% -2px of 33px 33px rotate -42deg cw large , arc to 25% 20px of 10px 5px ccw small)'},
|
||||
{at: 0, expect: 'shape(from 5% 5px, arc to 15% -15px of 10px 20px, arc by 15% -5px of 30px cw rotate 30deg large, arc to 25% 20px of 10px 5px small)'},
|
||||
{at: 0.3, expect: 'shape(from 8% 8px, arc to 12% -18px of 13px 23px ccw small, arc by 18% -8px of 27px 27px rotate 102deg cw large, arc to 25% 20px of 10px 5px ccw small )'},
|
||||
{at: 0.5, expect: 'shape(from 10% 10px, arc to 10% -20px of 15px 25px ccw small, arc by 20% -10px of 25px rotate 150deg cw small, arc to 25% 20px of 10px 5px cw small)'},
|
||||
{at: 0.5, expect: 'shape(from 10% 10px, arc to 10% -20px of 15px 25px ccw small, arc by 20% -10px of 25px rotate 150deg cw large, arc to 25% 20px of 10px 5px cw small)'},
|
||||
{at: 1, expect: 'shape(from 15% 15px, arc to 5% -25px of 20px 30px, arc by 25% -15px of 20px rotate 270deg cw small, arc to 25% 20px of 10px 5px cw small)'},
|
||||
{at: 1.5, expect: 'shape(from 20% 20px, arc to 0% -30px of 25px 35px ccw small, arc by 30% -20px of 15px rotate 390deg cw small, arc to 25% 20px of 10px 5px cw small)'},
|
||||
]);
|
||||
|
||||
test_interpolation({
|
||||
property: 'clip-path',
|
||||
from: 'shape(from 5px -5%, hline to 10px, vline by 10rem, hline by 1vh, close, vline by 3pt)',
|
||||
from: 'shape(from 5px -5%, hline to 10px, vline by 10rem, hline by 8.25px, close, vline by 3pt)',
|
||||
to: 'shape(from -5px 5px, hline to 20px, vline by 10%, hline by 1em, close, vline by 6pt)',
|
||||
}, [
|
||||
{at: -0.3, expect: 'shape(from 8px calc(-6.5% - 1.5px), hline to 7px, vline by calc(-3% + 208px), hline by 5.92px, close, vline by 2.8px)'},
|
||||
|
Loading…
x
Reference in New Issue
Block a user