Bug 1246764 - Part 2: Define path() for clip-path. r=emilio

For now, |clip-path: path()| is chrome-only, and not for shape-outside,
so we only implement the parser for clip-path. Besides, I didn't put
path() in BasicShape because path() doesn't use the reference box to
resolve the percentage or keywords (i.e. SVG path only accept floating
point or integer number as the css pixel value). Therefore, I add it into
ShapeSource, instead of BasicShape.

Differential Revision: https://phabricator.services.mozilla.com/D3633
This commit is contained in:
Boris Chiou 2018-08-14 18:58:18 -07:00
Родитель 2e062cc88c
Коммит a1909a88ff
8 изменённых файлов: 166 добавлений и 31 удалений

Просмотреть файл

@ -4884,6 +4884,7 @@ exports.CSS_PROPERTIES = {
"margin-box", "margin-box",
"none", "none",
"padding-box", "padding-box",
"path",
"polygon", "polygon",
"stroke-box", "stroke-box",
"unset", "unset",
@ -8269,6 +8270,7 @@ exports.CSS_PROPERTIES = {
"margin-box", "margin-box",
"none", "none",
"padding-box", "padding-box",
"path",
"polygon", "polygon",
"radial-gradient", "radial-gradient",
"repeating-linear-gradient", "repeating-linear-gradient",

Просмотреть файл

@ -190,7 +190,7 @@ function do_test() {
// Regression test for bug 1255379. // Regression test for bug 1255379.
var expected = [ "inherit", "initial", "unset", "none", "url", var expected = [ "inherit", "initial", "unset", "none", "url",
"polygon", "circle", "ellipse", "inset", "polygon", "circle", "ellipse", "inset", "path",
"fill-box", "stroke-box", "view-box", "margin-box", "fill-box", "stroke-box", "view-box", "margin-box",
"border-box", "padding-box", "content-box" ]; "border-box", "padding-box", "content-box" ];
var values = InspectorUtils.getCSSValuesForProperty("clip-path"); var values = InspectorUtils.getCSSValuesForProperty("clip-path");

Просмотреть файл

@ -1978,9 +1978,14 @@ struct StyleSVGPath final
return mPath; return mPath;
} }
StyleFillRule FillRule() const
{
return mFillRule;
}
bool operator==(const StyleSVGPath& aOther) const bool operator==(const StyleSVGPath& aOther) const
{ {
return mPath == aOther.mPath; return mPath == aOther.mPath && mFillRule == aOther.mFillRule;
} }
bool operator!=(const StyleSVGPath& aOther) const bool operator!=(const StyleSVGPath& aOther) const
@ -1990,6 +1995,7 @@ struct StyleSVGPath final
private: private:
nsTArray<StylePathCommand> mPath; nsTArray<StylePathCommand> mPath;
StyleFillRule mFillRule = StyleFillRule::Nonzero;
}; };
struct StyleShapeSource final struct StyleShapeSource final

Просмотреть файл

@ -8129,6 +8129,19 @@ if (false) {
other_values: [ "green", "#fc3" ], other_values: [ "green", "#fc3" ],
invalid_values: [ "000000", "ff00ff" ] invalid_values: [ "000000", "ff00ff" ]
}; };
// |clip-path: path()| is chrome-only.
gCSSProperties["clip-path"].other_values.push(
"path(nonzero, 'M 10 10 h 100 v 100 h-100 v-100 z')",
"path(evenodd, 'M 10 10 h 100 v 100 h-100 v-100 z')",
"path('M10,30A20,20 0,0,1 50,30A20,20 0,0,1 90,30Q90,60 50,90Q10,60 10,30z')",
);
gCSSProperties["clip-path"].invalid_values.push(
"path(nonzero)",
"path(evenodd, '')",
"path(abs, 'M 10 10 L 10 10 z')",
);
} }
if (IsCSSPropertyPrefEnabled("layout.css.filters.enabled")) { if (IsCSSPropertyPrefEnabled("layout.css.filters.enabled")) {

Просмотреть файл

@ -670,10 +670,11 @@ pub mod basic_shape {
use values::computed::position; use values::computed::position;
use values::computed::url::ComputedUrl; use values::computed::url::ComputedUrl;
use values::generics::basic_shape::{BasicShape as GenericBasicShape, InsetRect, Polygon}; use values::generics::basic_shape::{BasicShape as GenericBasicShape, InsetRect, Polygon};
use values::generics::basic_shape::{Circle, Ellipse, FillRule, PolygonCoord}; use values::generics::basic_shape::{Circle, Ellipse, FillRule, Path, PolygonCoord};
use values::generics::basic_shape::{GeometryBox, ShapeBox, ShapeSource}; use values::generics::basic_shape::{GeometryBox, ShapeBox, ShapeSource};
use values::generics::border::BorderRadius as GenericBorderRadius; use values::generics::border::BorderRadius as GenericBorderRadius;
use values::generics::rect::Rect; use values::generics::rect::Rect;
use values::specified::SVGPathData;
impl StyleShapeSource { impl StyleShapeSource {
/// Convert StyleShapeSource to ShapeSource except URL and Image /// Convert StyleShapeSource to ShapeSource except URL and Image
@ -698,7 +699,34 @@ pub mod basic_shape {
Some(ShapeSource::Shape(shape, reference_box)) Some(ShapeSource::Shape(shape, reference_box))
}, },
StyleShapeSourceType::URL | StyleShapeSourceType::Image => None, StyleShapeSourceType::URL | StyleShapeSourceType::Image => None,
StyleShapeSourceType::Path => None, StyleShapeSourceType::Path => {
let path = self.to_svg_path().expect("expect an SVGPathData");
let gecko_path = unsafe { &*self.__bindgen_anon_1.mSVGPath.as_ref().mPtr };
let fill = if gecko_path.mFillRule == StyleFillRule::Evenodd {
FillRule::Evenodd
} else {
FillRule::Nonzero
};
Some(ShapeSource::Path(Path { fill, path }))
},
}
}
/// Generate a SVGPathData from StyleShapeSource if possible.
fn to_svg_path(&self) -> Option<SVGPathData> {
use gecko_bindings::structs::StylePathCommand;
use values::specified::svg_path::PathCommand;
match self.mType {
StyleShapeSourceType::Path => {
let gecko_path = unsafe { &*self.__bindgen_anon_1.mSVGPath.as_ref().mPtr };
let result: Vec<PathCommand> =
gecko_path.mPath.iter().map(|gecko: &StylePathCommand| {
// unsafe: cbindgen ensures the representation is the same.
unsafe{ ::std::mem::transmute(*gecko) }
}).collect();
Some(SVGPathData::new(result.into_boxed_slice()))
},
_ => None,
} }
} }
} }
@ -742,17 +770,9 @@ pub mod basic_shape {
impl<'a> From<&'a StyleShapeSource> for OffsetPath { impl<'a> From<&'a StyleShapeSource> for OffsetPath {
fn from(other: &'a StyleShapeSource) -> Self { fn from(other: &'a StyleShapeSource) -> Self {
use gecko_bindings::structs::StylePathCommand;
use values::specified::svg_path::{SVGPathData, PathCommand};
match other.mType { match other.mType {
StyleShapeSourceType::Path => { StyleShapeSourceType::Path => {
let gecko_path = unsafe { &*other.__bindgen_anon_1.mSVGPath.as_ref().mPtr }; OffsetPath::Path(other.to_svg_path().expect("Cannot convert to SVGPathData"))
let result: Vec<PathCommand> =
gecko_path.mPath.iter().map(|gecko: &StylePathCommand| {
// unsafe: cbindgen ensures the representation is the same.
unsafe{ ::std::mem::transmute(*gecko) }
}).collect();
OffsetPath::Path(SVGPathData::new(result.into_boxed_slice()))
}, },
StyleShapeSourceType::None => OffsetPath::none(), StyleShapeSourceType::None => OffsetPath::none(),
StyleShapeSourceType::Shape | StyleShapeSourceType::Shape |

Просмотреть файл

@ -3683,27 +3683,16 @@ fn static_assert() {
${impl_simple_type_with_conversion("touch_action")} ${impl_simple_type_with_conversion("touch_action")}
pub fn set_offset_path(&mut self, v: longhands::offset_path::computed_value::T) { pub fn set_offset_path(&mut self, v: longhands::offset_path::computed_value::T) {
use gecko_bindings::bindings::{Gecko_NewStyleMotion, Gecko_NewStyleSVGPath}; use gecko_bindings::bindings::{Gecko_NewStyleMotion, Gecko_SetStyleMotion};
use gecko_bindings::bindings::Gecko_SetStyleMotion;
use gecko_bindings::structs::StyleShapeSourceType; use gecko_bindings::structs::StyleShapeSourceType;
use values::generics::basic_shape::FillRule;
use values::specified::OffsetPath; use values::specified::OffsetPath;
let motion = unsafe { Gecko_NewStyleMotion().as_mut().unwrap() }; let motion = unsafe { Gecko_NewStyleMotion().as_mut().unwrap() };
match v { match v {
OffsetPath::None => motion.mOffsetPath.mType = StyleShapeSourceType::None, OffsetPath::None => motion.mOffsetPath.mType = StyleShapeSourceType::None,
OffsetPath::Path(servo_path) => { OffsetPath::Path(p) => {
motion.mOffsetPath.mType = StyleShapeSourceType::Path; set_style_svg_path(&mut motion.mOffsetPath, &p, FillRule::Nonzero)
let gecko_path = unsafe {
let ref mut source = motion.mOffsetPath;
Gecko_NewStyleSVGPath(source);
&mut source.__bindgen_anon_1.mSVGPath.as_mut().mPtr.as_mut().unwrap().mPath
};
unsafe { gecko_path.set_len(servo_path.commands().len() as u32) };
debug_assert_eq!(gecko_path.len(), servo_path.commands().len());
for (servo, gecko) in servo_path.commands().iter().zip(gecko_path.iter_mut()) {
// unsafe: cbindgen ensures the representation is the same.
*gecko = unsafe { transmute(*servo) };
}
}, },
} }
unsafe { Gecko_SetStyleMotion(&mut self.gecko.mMotion, motion) }; unsafe { Gecko_SetStyleMotion(&mut self.gecko.mMotion, motion) };
@ -4982,6 +4971,35 @@ fn static_assert() {
} }
</%self:impl_trait> </%self:impl_trait>
// Set SVGPathData to StyleShapeSource.
fn set_style_svg_path(
shape_source: &mut structs::mozilla::StyleShapeSource,
servo_path: &values::specified::svg_path::SVGPathData,
fill: values::generics::basic_shape::FillRule,
) {
use gecko_bindings::bindings::Gecko_NewStyleSVGPath;
use gecko_bindings::structs::StyleShapeSourceType;
// Setup type.
shape_source.mType = StyleShapeSourceType::Path;
// Setup path.
let gecko_path = unsafe {
Gecko_NewStyleSVGPath(shape_source);
&mut shape_source.__bindgen_anon_1.mSVGPath.as_mut().mPtr.as_mut().unwrap()
};
unsafe { gecko_path.mPath.set_len(servo_path.commands().len() as u32) };
debug_assert_eq!(gecko_path.mPath.len(), servo_path.commands().len());
for (servo, gecko) in servo_path.commands().iter().zip(gecko_path.mPath.iter_mut()) {
// unsafe: cbindgen ensures the representation is the same.
*gecko = unsafe { transmute(*servo) };
}
// Setup fill-rule.
// unsafe: cbindgen ensures the representation is the same.
gecko_path.mFillRule = unsafe { transmute(fill) };
}
<%def name="impl_shape_source(ident, gecko_ffi_name)"> <%def name="impl_shape_source(ident, gecko_ffi_name)">
pub fn set_${ident}(&mut self, v: longhands::${ident}::computed_value::T) { pub fn set_${ident}(&mut self, v: longhands::${ident}::computed_value::T) {
use gecko_bindings::bindings::{Gecko_NewBasicShape, Gecko_DestroyShapeSource}; use gecko_bindings::bindings::{Gecko_NewBasicShape, Gecko_DestroyShapeSource};
@ -5021,6 +5039,7 @@ fn static_assert() {
${ident}.mReferenceBox = reference.into(); ${ident}.mReferenceBox = reference.into();
${ident}.mType = StyleShapeSourceType::Box; ${ident}.mType = StyleShapeSourceType::Box;
} }
ShapeSource::Path(p) => set_style_svg_path(${ident}, &p.path, p.fill),
ShapeSource::Shape(servo_shape, maybe_box) => { ShapeSource::Shape(servo_shape, maybe_box) => {
fn init_shape(${ident}: &mut StyleShapeSource, basic_shape_type: StyleBasicShapeType) fn init_shape(${ident}: &mut StyleShapeSource, basic_shape_type: StyleBasicShapeType)
-> &mut StyleBasicShape { -> &mut StyleBasicShape {

Просмотреть файл

@ -12,6 +12,7 @@ use values::distance::{ComputeSquaredDistance, SquaredDistance};
use values::generics::border::BorderRadius; use values::generics::border::BorderRadius;
use values::generics::position::Position; use values::generics::position::Position;
use values::generics::rect::Rect; use values::generics::rect::Rect;
use values::specified::SVGPathData;
/// A clipping shape, for `clip-path`. /// A clipping shape, for `clip-path`.
pub type ClippingShape<BasicShape, Url> = ShapeSource<BasicShape, GeometryBox, Url>; pub type ClippingShape<BasicShape, Url> = ShapeSource<BasicShape, GeometryBox, Url>;
@ -54,6 +55,9 @@ pub enum ShapeSource<BasicShape, ReferenceBox, ImageOrUrl> {
#[animation(error)] #[animation(error)]
Box(ReferenceBox), Box(ReferenceBox),
#[animation(error)] #[animation(error)]
#[css(function)]
Path(Path),
#[animation(error)]
None, None,
} }
@ -144,6 +148,19 @@ pub enum FillRule {
Evenodd, Evenodd,
} }
/// The path function defined in css-shape-2.
///
/// https://drafts.csswg.org/css-shapes-2/#funcdef-path
#[css(comma)]
#[derive(Clone, Debug, MallocSizeOf, PartialEq, SpecifiedValueInfo, ToComputedValue, ToCss)]
pub struct Path {
/// The filling rule for the svg path.
#[css(skip_if = "fill_is_default")]
pub fill: FillRule,
/// The svg path data.
pub path: SVGPathData,
}
// FIXME(nox): Implement ComputeSquaredDistance for T types and stop // FIXME(nox): Implement ComputeSquaredDistance for T types and stop
// using PartialEq here, this will let us derive this impl. // using PartialEq here, this will let us derive this impl.
impl<B, T, U> ComputeSquaredDistance for ShapeSource<B, T, U> impl<B, T, U> ComputeSquaredDistance for ShapeSource<B, T, U>

Просмотреть файл

@ -14,9 +14,11 @@ use std::fmt::{self, Write};
use style_traits::{CssWriter, ParseError, StyleParseErrorKind, ToCss}; use style_traits::{CssWriter, ParseError, StyleParseErrorKind, ToCss};
use values::computed::Percentage; use values::computed::Percentage;
use values::generics::basic_shape as generic; use values::generics::basic_shape as generic;
use values::generics::basic_shape::{FillRule, GeometryBox, PolygonCoord, ShapeBox, ShapeSource}; use values::generics::basic_shape::{FillRule, GeometryBox, Path, PolygonCoord};
use values::generics::basic_shape::{ShapeBox, ShapeSource};
use values::generics::rect::Rect; use values::generics::rect::Rect;
use values::specified::LengthOrPercentage; use values::specified::LengthOrPercentage;
use values::specified::SVGPathData;
use values::specified::border::BorderRadius; use values::specified::border::BorderRadius;
use values::specified::image::Image; use values::specified::image::Image;
use values::specified::position::{HorizontalPosition, Position, PositionComponent}; use values::specified::position::{HorizontalPosition, Position, PositionComponent};
@ -47,12 +49,42 @@ pub type ShapeRadius = generic::ShapeRadius<LengthOrPercentage>;
/// The specified value of `Polygon` /// The specified value of `Polygon`
pub type Polygon = generic::Polygon<LengthOrPercentage>; pub type Polygon = generic::Polygon<LengthOrPercentage>;
impl<ReferenceBox, ImageOrUrl> Parse for ShapeSource<BasicShape, ReferenceBox, ImageOrUrl> impl Parse for ClippingShape {
#[inline]
fn parse<'i, 't>(
context: &ParserContext,
input: &mut Parser<'i, 't>,
) -> Result<Self, ParseError<'i>> {
// |clip-path:path()| is a chrome-only property value support for now. `path()` is
// defined in css-shape-2, but the spec is not stable enough, and we haven't decided
// to make it public yet. However, it has some benefits for the front-end, so we
// implement it.
if context.chrome_rules_enabled() {
if let Ok(p) = input.try(|i| Path::parse(context, i)) {
return Ok(ShapeSource::Path(p));
}
}
Self::parse_internal(context, input)
}
}
impl Parse for FloatAreaShape {
#[inline]
fn parse<'i, 't>(
context: &ParserContext,
input: &mut Parser<'i, 't>,
) -> Result<Self, ParseError<'i>> {
Self::parse_internal(context, input)
}
}
impl<ReferenceBox, ImageOrUrl> ShapeSource<BasicShape, ReferenceBox, ImageOrUrl>
where where
ReferenceBox: Parse, ReferenceBox: Parse,
ImageOrUrl: Parse, ImageOrUrl: Parse,
{ {
fn parse<'i, 't>( /// The internal parser for ShapeSource.
fn parse_internal<'i, 't>(
context: &ParserContext, context: &ParserContext,
input: &mut Parser<'i, 't>, input: &mut Parser<'i, 't>,
) -> Result<Self, ParseError<'i>> { ) -> Result<Self, ParseError<'i>> {
@ -393,3 +425,29 @@ impl Polygon {
}) })
} }
} }
impl Parse for Path {
fn parse<'i, 't>(
context: &ParserContext,
input: &mut Parser<'i, 't>,
) -> Result<Self, ParseError<'i>> {
input.expect_function_matching("path")?;
input.parse_nested_block(|i| Self::parse_function_arguments(context, i))
}
}
impl Path {
/// Parse the inner arguments of a `path` function.
fn parse_function_arguments<'i, 't>(
context: &ParserContext,
input: &mut Parser<'i, 't>,
) -> Result<Self, ParseError<'i>> {
let fill = input.try(|i| -> Result<_, ParseError> {
let fill = FillRule::parse(i)?;
i.expect_comma()?;
Ok(fill)
}).unwrap_or_default();
let path = SVGPathData::parse(context, input)?;
Ok(Path { fill, path })
}
}