Bug 1338388 - Part 9: stylo: Support stroke-dasharray and stroke-dashoffset; r?heycam draft
authorManish Goregaokar <manishearth@gmail.com>
Thu, 09 Feb 2017 17:43:52 -0800
changeset 486532 998b816e75f7f1057b7ecc29c03efd603e63f1ec
parent 486531 62f8444622902894f534ac912af973ea79922c0e
child 486533 9b1713a4bc7147eb5cc9e65959c96e4066b6c920
push id46017
push userbmo:manishearth@gmail.com
push dateSat, 18 Feb 2017 08:18:38 +0000
reviewersheycam
bugs1338388
milestone54.0a1
Bug 1338388 - Part 9: stylo: Support stroke-dasharray and stroke-dashoffset; r?heycam MozReview-Commit-ID: 4QKKzJ1DVYP
layout/style/ServoBindings.cpp
layout/style/ServoBindings.h
servo/components/style/gecko_bindings/bindings.rs
servo/components/style/parser.rs
servo/components/style/properties/gecko.mako.rs
servo/components/style/properties/helpers.mako.rs
servo/components/style/properties/longhand/inherited_svg.mako.rs
servo/components/style/values/computed/mod.rs
servo/components/style/values/specified/mod.rs
--- a/layout/style/ServoBindings.cpp
+++ b/layout/style/ServoBindings.cpp
@@ -1074,16 +1074,29 @@ Gecko_nsStyleSVGPaint_SetURLValue(nsStyl
   aPaint->SetPaintServer(url.get(), NS_RGB(0, 0, 0));
 }
 
 void Gecko_nsStyleSVGPaint_Reset(nsStyleSVGPaint* aPaint)
 {
   aPaint->SetNone();
 }
 
+void
+Gecko_nsStyleSVG_SetDashArrayLength(nsStyleSVG* aSvg, uint32_t aLen)
+{
+  aSvg->mStrokeDasharray.Clear();
+  aSvg->mStrokeDasharray.SetLength(aLen);
+}
+
+void
+Gecko_nsStyleSVG_CopyDashArray(nsStyleSVG* aDst, const nsStyleSVG* aSrc)
+{
+  aDst->mStrokeDasharray = aSrc->mStrokeDasharray;
+}
+
 css::URLValue*
 Gecko_NewURLValue(ServoBundledURI aURI)
 {
   RefPtr<css::URLValue> url = aURI.IntoCssUrl();
   return url.forget().take();
 }
 
 NS_IMPL_THREADSAFE_FFI_REFCOUNTING(css::URLValue, CSSURLValue);
--- a/layout/style/ServoBindings.h
+++ b/layout/style/ServoBindings.h
@@ -293,16 +293,19 @@ void Gecko_StyleClipPath_SetURLValue(moz
 void Gecko_ResetFilters(nsStyleEffects* effects, size_t new_len);
 void Gecko_CopyFiltersFrom(nsStyleEffects* aSrc, nsStyleEffects* aDest);
 void Gecko_nsStyleFilter_SetURLValue(nsStyleFilter* effects, ServoBundledURI uri);
 
 void Gecko_nsStyleSVGPaint_CopyFrom(nsStyleSVGPaint* dest, const nsStyleSVGPaint* src);
 void Gecko_nsStyleSVGPaint_SetURLValue(nsStyleSVGPaint* paint, ServoBundledURI uri);
 void Gecko_nsStyleSVGPaint_Reset(nsStyleSVGPaint* paint);
 
+void Gecko_nsStyleSVG_SetDashArrayLength(nsStyleSVG* svg, uint32_t len);
+void Gecko_nsStyleSVG_CopyDashArray(nsStyleSVG* dst, const nsStyleSVG* src);
+
 mozilla::css::URLValue* Gecko_NewURLValue(ServoBundledURI uri);
 NS_DECL_THREADSAFE_FFI_REFCOUNTING(mozilla::css::URLValue, CSSURLValue);
 
 void Gecko_FillAllBackgroundLists(nsStyleImageLayers* layers, uint32_t max_len);
 void Gecko_FillAllMaskLists(nsStyleImageLayers* layers, uint32_t max_len);
 NS_DECL_THREADSAFE_FFI_REFCOUNTING(nsStyleCoord::Calc, Calc);
 
 nsCSSShadowArray* Gecko_NewCSSShadowArray(uint32_t len);
--- a/servo/components/style/gecko_bindings/bindings.rs
+++ b/servo/components/style/gecko_bindings/bindings.rs
@@ -718,16 +718,24 @@ extern "C" {
 extern "C" {
     pub fn Gecko_nsStyleSVGPaint_SetURLValue(paint: *mut nsStyleSVGPaint,
                                              uri: ServoBundledURI);
 }
 extern "C" {
     pub fn Gecko_nsStyleSVGPaint_Reset(paint: *mut nsStyleSVGPaint);
 }
 extern "C" {
+    pub fn Gecko_nsStyleSVG_SetDashArrayLength(svg: *mut nsStyleSVG,
+                                               len: u32);
+}
+extern "C" {
+    pub fn Gecko_nsStyleSVG_CopyDashArray(dst: *mut nsStyleSVG,
+                                          src: *const nsStyleSVG);
+}
+extern "C" {
     pub fn Gecko_NewURLValue(uri: ServoBundledURI) -> *mut URLValue;
 }
 extern "C" {
     pub fn Gecko_AddRefCSSURLValueArbitraryThread(aPtr: *mut URLValue);
 }
 extern "C" {
     pub fn Gecko_ReleaseCSSURLValueArbitraryThread(aPtr: *mut URLValue);
 }
--- a/servo/components/style/parser.rs
+++ b/servo/components/style/parser.rs
@@ -105,13 +105,29 @@ pub trait Parse : Sized {
 }
 
 impl<T> Parse for Vec<T> where T: Parse + OneOrMoreCommaSeparated {
     fn parse(context: &ParserContext, input: &mut Parser) -> Result<Self, ()> {
         input.parse_comma_separated(|input| T::parse(context, input))
     }
 }
 
+/// Parse a non-empty space-separated or comma-separated list of objects parsed by parse_one
+pub fn parse_space_or_comma_separated<F, T>(input: &mut Parser, mut parse_one: F)
+        -> Result<Vec<T>, ()>
+        where F: FnMut(&mut Parser) -> Result<T, ()> {
+    let first = parse_one(input)?;
+    let mut vec = vec![first];
+    loop {
+        let _ = input.try(|i| i.expect_comma());
+        if let Ok(val) = input.try(|i| parse_one(i)) {
+            vec.push(val)
+        } else {
+            break
+        }
+    }
+    Ok(vec)
+}
 impl Parse for UnicodeRange {
     fn parse(_context: &ParserContext, input: &mut Parser) -> Result<Self, ()> {
         UnicodeRange::parse(input)
     }
 }
--- a/servo/components/style/properties/gecko.mako.rs
+++ b/servo/components/style/properties/gecko.mako.rs
@@ -2795,17 +2795,17 @@ clip-path
         use gecko_bindings::bindings::Gecko_CopyClipPathValueFrom;
         unsafe {
             Gecko_CopyClipPathValueFrom(&mut self.gecko.mClipPath, &other.gecko.mClipPath);
         }
     }
 </%self:impl_trait>
 
 <%self:impl_trait style_struct_name="InheritedSVG"
-                  skip_longhands="paint-order"
+                  skip_longhands="paint-order stroke-dasharray"
                   skip_additionals="*">
     pub fn set_paint_order(&mut self, v: longhands::paint_order::computed_value::T) {
         use self::longhands::paint_order;
 
         if v.0 == 0 {
             self.gecko.mPaintOrder = structs::NS_STYLE_PAINT_ORDER_NORMAL as u8;
         } else {
             let mut order = 0;
@@ -2820,16 +2820,35 @@ clip-path
                 order |= geckoval << (pos * structs::NS_STYLE_PAINT_ORDER_BITWIDTH as u8);
             }
 
             self.gecko.mPaintOrder = order;
         }
     }
 
     ${impl_simple_copy('paint_order', 'mPaintOrder')}
+
+    pub fn set_stroke_dasharray(&mut self, v: longhands::stroke_dasharray::computed_value::T) {
+        unsafe {
+            bindings::Gecko_nsStyleSVG_SetDashArrayLength(&mut self.gecko, v.0.len() as u32);
+        }
+
+        for (mut gecko, servo) in self.gecko.mStrokeDasharray.iter_mut().zip(v.0.into_iter()) {
+            match servo {
+                Either::First(lop) => gecko.set(lop),
+                Either::Second(number) => gecko.set_value(CoordDataValue::Factor(number)),
+            }
+        }
+    }
+
+    pub fn copy_stroke_dasharray_from(&mut self, other: &Self) {
+        unsafe {
+            bindings::Gecko_nsStyleSVG_CopyDashArray(&mut self.gecko, &other.gecko);
+        }
+    }
 </%self:impl_trait>
 
 <%self:impl_trait style_struct_name="Color"
                   skip_longhands="*">
     pub fn set_color(&mut self, v: longhands::color::computed_value::T) {
         let result = convert_rgba_to_nscolor(&v);
         ${set_gecko_property("mColor", "result")}
     }
--- a/servo/components/style/properties/helpers.mako.rs
+++ b/servo/components/style/properties/helpers.mako.rs
@@ -61,17 +61,17 @@
     to True for cases where Servo takes a single value
     and Stylo supports vector values.
 
     Setting allow_empty to False allows for cases where the vector
     is empty. The grammar for these is usually "none | <thing> [ , <thing> ]*".
     We assume that the default/initial value is an empty vector for these.
     `initial_value` need not be defined for these.
 </%doc>
-<%def name="vector_longhand(name, gecko_only=False, allow_empty=False, delegate_animate=False, **kwargs)">
+<%def name="vector_longhand(name, gecko_only=False, allow_empty=False, delegate_animate=False, space_separated_allowed=False, **kwargs)">
     <%call expr="longhand(name, **kwargs)">
         % if not gecko_only:
             use std::fmt;
             use values::HasViewportPercentage;
             use style_traits::ToCss;
 
             impl HasViewportPercentage for SpecifiedValue {
                 fn has_viewport_percentage(&self) -> bool {
@@ -81,16 +81,17 @@
             }
 
             pub mod single_value {
                 use cssparser::Parser;
                 use parser::{Parse, ParserContext, ParserContextExtraData};
                 use properties::{CSSWideKeyword, DeclaredValue, ShorthandId};
                 use values::computed::{Context, ToComputedValue};
                 use values::{computed, specified};
+                use values::{Auto, Either, None_, Normal};
                 ${caller.body()}
             }
 
             /// The definition of the computed value for ${name}.
             pub mod computed_value {
                 pub use super::single_value::computed_value as single_value;
                 pub use self::single_value::T as SingleComputedValue;
                 /// The computed value, effectively a list of single values.
@@ -161,26 +162,33 @@
                 % if allow_empty:
                     computed_value::T(vec![])
                 % else:
                     computed_value::T(vec![single_value::get_initial_value()])
                 % endif
             }
 
             pub fn parse(context: &ParserContext, input: &mut Parser) -> Result<SpecifiedValue, ()> {
+                use parser::parse_space_or_comma_separated;
+
+                <%
+                    parse_func = "Parser::parse_comma_separated"
+                    if space_separated_allowed:
+                        parse_func = "parse_space_or_comma_separated"
+                %>
                 % if allow_empty:
                     if input.try(|input| input.expect_ident_matching("none")).is_ok() {
                         Ok(SpecifiedValue(Vec::new()))
                     } else {
-                        input.parse_comma_separated(|parser| {
+                        ${parse_func}(input, |parser| {
                             single_value::parse(context, parser)
                         }).map(SpecifiedValue)
                     }
                 % else:
-                    input.parse_comma_separated(|parser| {
+                    ${parse_func}(input, |parser| {
                         single_value::parse(context, parser)
                     }).map(SpecifiedValue)
                 % endif
             }
 
             pub use self::single_value::SpecifiedValue as SingleSpecifiedValue;
 
             impl ToComputedValue for SpecifiedValue {
--- a/servo/components/style/properties/longhand/inherited_svg.mako.rs
+++ b/servo/components/style/properties/longhand/inherited_svg.mako.rs
@@ -88,16 +88,32 @@
                           needs_context=False,
                           animatable=False,
                           spec="https://www.w3.org/TR/SVG11/painting.html#StrokeMiterlimitProperty")}
 
 ${helpers.predefined_type("stroke-opacity", "Opacity", "1.0",
                           products="gecko", animatable=False,
                           spec="https://www.w3.org/TR/SVG11/painting.html#StrokeOpacityProperty")}
 
+${helpers.predefined_type("stroke-dasharray", "LoPOrNumber", "Either::Second(0.0)",
+                          "parse_non_negative",
+                          vector="True",
+                          products="gecko",
+                          animatable="False",
+                          space_separated_allowed="True",
+                          spec="https://www.w3.org/TR/SVG2/painting.html#StrokeDashing")}
+
+${helpers.predefined_type(
+    "stroke-dashoffset", "LengthOrPercentage",
+    "computed::LengthOrPercentage::zero()",
+    products="gecko",
+    animatable=True,
+    boxed=True,
+    spec="https://www.w3.org/TR/SVG2/painting.html#StrokeDashing")}
+
 // Section 14 - Clipping, Masking and Compositing
 ${helpers.single_keyword("clip-rule", "nonzero evenodd",
                          products="gecko",
                          gecko_enum_prefix="StyleFillRule",
                          gecko_inexhaustive=True,
                          animatable=False,
                          spec="https://www.w3.org/TR/SVG11/masking.html#ClipRuleProperty")}
 
--- a/servo/components/style/values/computed/mod.rs
+++ b/servo/components/style/values/computed/mod.rs
@@ -254,16 +254,18 @@ impl ToCss for SVGPaint {
         self.kind.to_css(dest)?;
         if let Some(ref fallback) = self.fallback {
             fallback.to_css(dest)?;
         }
         Ok(())
     }
 }
 
+/// <length> | <percentage> | <number>
+pub type LoPOrNumber = Either<LengthOrPercentage, Number>;
 
 #[derive(Clone, PartialEq, Eq, Copy, Debug)]
 #[cfg_attr(feature = "servo", derive(HeapSizeOf))]
 #[allow(missing_docs)]
 /// A computed cliprect for clip and image-region
 pub struct ClipRect {
     pub top: Au,
     pub right: Option<Au>,
--- a/servo/components/style/values/specified/mod.rs
+++ b/servo/components/style/values/specified/mod.rs
@@ -819,16 +819,31 @@ impl ToComputedValue for SVGPaintKind {
             }
             super::computed::SVGPaintKind::PaintServer(ref server) => {
                 SVGPaintKind::PaintServer(ToComputedValue::from_computed_value(server))
             }
         }
     }
 }
 
+/// <length> | <percentage> | <number>
+pub type LoPOrNumber = Either<LengthOrPercentage, Number>;
+
+impl LoPOrNumber {
+    /// parse a <length-percentage> | <number> enforcing that the contents aren't negative
+    pub fn parse_non_negative(_: &ParserContext, input: &mut Parser) -> Result<Self, ()> {
+        if let Ok(lop) = input.try(LengthOrPercentage::parse_non_negative) {
+            Ok(Either::First(lop))
+        } else if let Ok(num) = input.try(Number::parse_non_negative) {
+            Ok(Either::Second(num))
+        } else {
+            Err(())
+        }
+    }
+}
 
 impl HasViewportPercentage for ClipRect {
     fn has_viewport_percentage(&self) -> bool {
         self.top.has_viewport_percentage() ||
         self.right.as_ref().map_or(false, |x| x.has_viewport_percentage()) ||
         self.bottom.as_ref().map_or(false, |x| x.has_viewport_percentage()) ||
         self.left.has_viewport_percentage()
     }