diff --git a/servo/components/style/values/specified/basic_shape.rs b/servo/components/style/values/specified/basic_shape.rs index e683b30e7264..11da6b80598f 100644 --- a/servo/components/style/values/specified/basic_shape.rs +++ b/servo/components/style/values/specified/basic_shape.rs @@ -16,7 +16,7 @@ use values::computed::basic_shape as computed_basic_shape; use values::computed::{Context, ToComputedValue, ComputedValueAsSpecified}; use values::specified::UrlExtraData; use values::specified::position::{Keyword, Position}; -use values::specified::{BorderRadiusSize, LengthOrPercentage}; +use values::specified::{BorderRadiusSize, LengthOrPercentage, Percentage}; /// A shape source, for some reference box /// @@ -241,6 +241,89 @@ impl ToComputedValue for InsetRect { } } +/// https://drafts.csswg.org/css-shapes/#basic-shape-serialization +/// +/// Positions get serialized differently with basic shapes. Keywords +/// are converted to percentages where possible. Only the two or four +/// value forms are used. In case of two keyword-percentage pairs, +/// the keywords are folded into the percentages +fn serialize_basicshape_position(position: &Position, dest: &mut W) + -> fmt::Result where W: fmt::Write { + use values::specified::Length; + use values::specified::position::Keyword; + + // keyword-percentage pairs can be folded into a single percentage + fn fold_keyword(keyword: Option, length: Option) + -> Option { + let pc = match length.map(replace_with_percent) { + None => Percentage(0.0), // unspecified length = 0% + Some(LengthOrPercentage::Percentage(pc)) => pc, + _ => return None + }; + let percent = match keyword { + Some(Keyword::Center) => { + // center cannot pair with lengths + assert!(length.is_none()); + Percentage(0.5) + }, + Some(Keyword::Left) | Some(Keyword::Top) | None => pc, + Some(Keyword::Right) | Some(Keyword::Bottom) => Percentage(1.0 - pc.0), + }; + Some(LengthOrPercentage::Percentage(percent)) + } + + // 0 length should be replaced with 0% + fn replace_with_percent(input: LengthOrPercentage) -> LengthOrPercentage { + match input { + LengthOrPercentage::Length(Length::Absolute(au)) if au.0 == 0 => { + LengthOrPercentage::Percentage(Percentage(0.0)) + } + _ => { + input + } + } + } + + fn serialize_position_pair(x: LengthOrPercentage, y: LengthOrPercentage, + dest: &mut W) -> fmt::Result where W: fmt::Write { + try!(replace_with_percent(x).to_css(dest)); + try!(dest.write_str(" ")); + replace_with_percent(y).to_css(dest) + } + + match (position.horiz_keyword, position.horiz_position, + position.vert_keyword, position.vert_position) { + (Some(hk), None, Some(vk), None) => { + // two keywords: serialize as two lengths + serialize_position_pair(hk.to_length_or_percentage(), + vk.to_length_or_percentage(), + dest) + } + (None, Some(hp), None, Some(vp)) => { + // two lengths: just serialize regularly + serialize_position_pair(hp, vp, dest) + } + (hk, hp, vk, vp) => { + // only fold if both fold; the three-value form isn't + // allowed here. + if let (Some(x), Some(y)) = (fold_keyword(hk, hp), fold_keyword(vk, vp)) { + serialize_position_pair(x, y, dest) + } else { + // We failed to reduce it to a two-value form, + // so we expand it to 4-value + let zero = LengthOrPercentage::Percentage(Percentage(0.0)); + try!(hk.unwrap_or(Keyword::Left).to_css(dest)); + try!(dest.write_str(" ")); + try!(replace_with_percent(hp.unwrap_or(zero)).to_css(dest)); + try!(dest.write_str(" ")); + try!(vk.unwrap_or(Keyword::Top).to_css(dest)); + try!(dest.write_str(" ")); + replace_with_percent(vp.unwrap_or(zero)).to_css(dest) + } + } + } +} + #[derive(Clone, PartialEq, Copy, Debug)] #[cfg_attr(feature = "servo", derive(HeapSizeOf))] /// https://drafts.csswg.org/css-shapes/#funcdef-circle @@ -286,7 +369,7 @@ impl ToCss for Circle { try!(dest.write_str(" ")); } try!(dest.write_str("at ")); - try!(self.position.to_css(dest)); + try!(serialize_basicshape_position(&self.position, dest)); dest.write_str(")") } } @@ -355,7 +438,7 @@ impl ToCss for Ellipse { try!(dest.write_str(" ")); } try!(dest.write_str("at ")); - try!(self.position.to_css(dest)); + try!(serialize_basicshape_position(&self.position, dest)); dest.write_str(")") } } diff --git a/servo/tests/unit/style/parsing/basic_shape.rs b/servo/tests/unit/style/parsing/basic_shape.rs index d62e6ee4de66..da829e7c29d0 100644 --- a/servo/tests/unit/style/parsing/basic_shape.rs +++ b/servo/tests/unit/style/parsing/basic_shape.rs @@ -77,7 +77,6 @@ fn test_border_radius() { #[test] fn test_circle() { - /* assert_roundtrip_basicshape!(Circle::parse, "circle(at center)", "circle(at 50% 50%)"); assert_roundtrip_basicshape!(Circle::parse, "circle()", "circle(at 50% 50%)"); assert_roundtrip_basicshape!(Circle::parse, "circle(at left bottom)", "circle(at 0% 100%)"); @@ -97,14 +96,24 @@ fn test_circle() { assert_roundtrip_basicshape!(Circle::parse, "circle(calc(1px + 50%) at center)", "circle(calc(1px + 50%) at 50% 50%)"); - assert!(parse(Circle::parse, "circle(at top 40%)").is_err()); - */ + assert_roundtrip_basicshape!(Circle::parse, "circle(at right 5px bottom 10px)", + "circle(at right 5px bottom 10px)"); + assert_roundtrip_basicshape!(Circle::parse, "circle(at bottom 5px right 10px)", + "circle(at right 10px bottom 5px)"); + assert_roundtrip_basicshape!(Circle::parse, "circle(at right 5% top 0px)", + "circle(at 95% 0%)"); + assert_roundtrip_basicshape!(Circle::parse, "circle(at right 5% bottom 0px)", + "circle(at 95% 100%)"); + assert_roundtrip_basicshape!(Circle::parse, "circle(at right 5% bottom 1px)", + "circle(at right 5% bottom 1px)"); + assert_roundtrip_basicshape!(Circle::parse, "circle(at 5% bottom 1px)", + "circle(at left 5% bottom 1px)"); + assert!(parse(Circle::parse, "circle(at top 40%)").is_err()); } #[test] fn test_ellipse() { - /* assert_roundtrip_basicshape!(Ellipse::parse, "ellipse(at center)", "ellipse(at 50% 50%)"); assert_roundtrip_basicshape!(Ellipse::parse, "ellipse()", "ellipse(at 50% 50%)"); assert_roundtrip_basicshape!(Ellipse::parse, "ellipse(at left bottom)", "ellipse(at 0% 100%)"); @@ -118,7 +127,6 @@ fn test_ellipse() { assert_roundtrip_basicshape!(Ellipse::parse, "ellipse(20px 10% at center)", "ellipse(20px 10% at 50% 50%)"); assert_roundtrip_basicshape!(Ellipse::parse, "ellipse(calc(1px + 50%) 10px at center)", "ellipse(calc(1px + 50%) 10px at 50% 50%)"); - */ } #[test]