diff --git a/components/layout/fragment.rs b/components/layout/fragment.rs index ea2132a8169..4ff04389836 100644 --- a/components/layout/fragment.rs +++ b/components/layout/fragment.rs @@ -728,12 +728,15 @@ impl Fragment { }; debug!("(building display list) building background image"); + let image_width = Au::from_px(image.width as int); + let image_height = Au::from_px(image.height as int); + // Adjust bounds for `background-position` and `background-attachment`. let mut bounds = *absolute_bounds; let horizontal_position = model::specified(background.background_position.horizontal, - bounds.size.width); + bounds.size.width - image_width); let vertical_position = model::specified(background.background_position.vertical, - bounds.size.height); + bounds.size.height - image_height); // TODO: These are some situations below where it is possible // to determine that clipping is not necessary - this is an @@ -743,9 +746,6 @@ impl Fragment { children: DisplayList::new(), }); - let image_width = Au::from_px(image.width as int); - let image_height = Au::from_px(image.height as int); - match background.background_attachment { background_attachment::scroll => { bounds.origin.x = bounds.origin.x + horizontal_position; @@ -759,25 +759,21 @@ impl Fragment { } background_repeat::repeat_x => { bounds.size.height = Au::min(bounds.size.height - vertical_position, image_height); - if horizontal_position > Au(0) { - bounds.origin.x = bounds.origin.x - image_width; + if horizontal_position != Au(0) { bounds.size.width = bounds.size.width + image_width; } } background_repeat::repeat_y => { bounds.size.width = Au::min(bounds.size.width - horizontal_position, image_width); - if vertical_position > Au(0) { - bounds.origin.y = bounds.origin.y - image_height; + if vertical_position != Au(0) { bounds.size.height = bounds.size.height + image_height; } } background_repeat::repeat => { - if horizontal_position > Au(0) { - bounds.origin.x = bounds.origin.x - image_width; + if horizontal_position != Au(0) { bounds.size.width = bounds.size.width + image_width; } - if vertical_position > Au(0) { - bounds.origin.y = bounds.origin.y - image_height; + if vertical_position != Au(0) { bounds.size.height = bounds.size.height + image_height; } } diff --git a/components/style/properties/common_types.rs b/components/style/properties/common_types.rs index fc30a4036fe..77decfa6430 100644 --- a/components/style/properties/common_types.rs +++ b/components/style/properties/common_types.rs @@ -160,6 +160,47 @@ pub mod specified { LengthOrPercentageOrNone::parse_internal(input, /* negative_ok = */ false) } } + + // http://dev.w3.org/csswg/css2/colors.html#propdef-background-position + #[deriving(Clone)] + pub enum PositionComponent { + Pos_Length(Length), + Pos_Percentage(CSSFloat), // [0 .. 100%] maps to [0.0 .. 1.0] + Pos_Center, + Pos_Left, + Pos_Right, + Pos_Top, + Pos_Bottom, + } + impl PositionComponent { + pub fn parse(input: &ComponentValue) -> Result { + match input { + &Dimension(ref value, ref unit) => + Length::parse_dimension(value.value, unit.as_slice()).map(Pos_Length), + &ast::Percentage(ref value) => Ok(Pos_Percentage(value.value / 100.)), + &Number(ref value) if value.value == 0. => Ok(Pos_Length(Au_(Au(0)))), + &Ident(ref value) => { + if value.as_slice().eq_ignore_ascii_case("center") { Ok(Pos_Center) } + else if value.as_slice().eq_ignore_ascii_case("left") { Ok(Pos_Left) } + else if value.as_slice().eq_ignore_ascii_case("right") { Ok(Pos_Right) } + else if value.as_slice().eq_ignore_ascii_case("top") { Ok(Pos_Top) } + else if value.as_slice().eq_ignore_ascii_case("bottom") { Ok(Pos_Bottom) } + else { Err(()) } + } + _ => Err(()) + } + } + #[inline] + pub fn to_length_or_percentage(self) -> LengthOrPercentage { + match self { + Pos_Length(x) => LP_Length(x), + Pos_Percentage(x) => LP_Percentage(x), + Pos_Center => LP_Percentage(0.5), + Pos_Left | Pos_Top => LP_Percentage(0.0), + Pos_Right | Pos_Bottom => LP_Percentage(1.0), + } + } + } } pub mod computed { diff --git a/components/style/properties/mod.rs.mako b/components/style/properties/mod.rs.mako index e699c392a77..f65e9b35280 100644 --- a/components/style/properties/mod.rs.mako +++ b/components/style/properties/mod.rs.mako @@ -627,6 +627,46 @@ pub mod longhands { pub vertical: specified::LengthOrPercentage, } + impl SpecifiedValue { + fn new(first: specified::PositionComponent, second: specified::PositionComponent) + -> Result { + let (horiz, vert) = match (category(first), category(second)) { + // Don't allow two vertical keywords or two horizontal keywords. + (HorizontalKeyword, HorizontalKeyword) | + (VerticalKeyword, VerticalKeyword) => return Err(()), + + // Swap if both are keywords and vertical precedes horizontal. + (VerticalKeyword, HorizontalKeyword) | + (VerticalKeyword, OtherKeyword) | + (OtherKeyword, HorizontalKeyword) => (second, first), + + // By default, horizontal is first. + _ => (first, second), + }; + Ok(SpecifiedValue { + horizontal: horiz.to_length_or_percentage(), + vertical: vert.to_length_or_percentage(), + }) + } + } + + // Collapse `Position` into a few categories to simplify the above `match` expression. + enum PositionCategory { + HorizontalKeyword, + VerticalKeyword, + OtherKeyword, + LengthOrPercentage, + } + fn category(p: specified::PositionComponent) -> PositionCategory { + match p { + specified::Pos_Left | specified::Pos_Right => HorizontalKeyword, + specified::Pos_Top | specified::Pos_Bottom => VerticalKeyword, + specified::Pos_Center => OtherKeyword, + specified::Pos_Length(_) | + specified::Pos_Percentage(_) => LengthOrPercentage, + } + } + #[inline] pub fn to_computed_value(value: SpecifiedValue, context: &computed::Context) -> computed_value::T { @@ -644,29 +684,32 @@ pub mod longhands { } } - // FIXME(#1997, pcwalton): Support complete CSS2 syntax. - pub fn parse_horizontal_and_vertical(horiz: &ComponentValue, vert: &ComponentValue) - -> Result { - let horiz = try!(specified::LengthOrPercentage::parse_non_negative(horiz)); - let vert = try!(specified::LengthOrPercentage::parse_non_negative(vert)); + pub fn parse_one(first: &ComponentValue) -> Result { + let first = try!(specified::PositionComponent::parse(first)); + // If only one value is provided, use `center` for the second. + SpecifiedValue::new(first, specified::Pos_Center) + } - Ok(SpecifiedValue { - horizontal: horiz, - vertical: vert, - }) + pub fn parse_two(first: &ComponentValue, second: &ComponentValue) + -> Result { + let first = try!(specified::PositionComponent::parse(first)); + let second = try!(specified::PositionComponent::parse(second)); + SpecifiedValue::new(first, second) } pub fn parse(input: &[ComponentValue], _: &Url) -> Result { let mut input_iter = input.skip_whitespace(); - let horizontal = input_iter.next(); - let vertical = input_iter.next(); + let first = input_iter.next(); + let second = input_iter.next(); if input_iter.next().is_some() { return Err(()) } - - match (horizontal, vertical) { - (Some(horizontal), Some(vertical)) => { - parse_horizontal_and_vertical(horizontal, vertical) + match (first, second) { + (Some(first), Some(second)) => { + parse_two(first, second) + } + (Some(first), None) => { + parse_one(first) } _ => Err(()) } @@ -1097,10 +1140,40 @@ pub mod shorthands { let (mut color, mut image, mut position, mut repeat, mut attachment) = (None, None, None, None, None); - let mut last_component_value = None; + let mut unused_component_value = None; let mut any = false; for component_value in input.skip_whitespace() { + // Try `background-position` first because it might not use the value. + if position.is_none() { + match mem::replace(&mut unused_component_value, None) { + Some(saved_component_value) => { + // First try parsing a pair of values, then a single value. + match background_position::parse_two(saved_component_value, + component_value) { + Ok(v) => { + position = Some(v); + any = true; + continue + }, + Err(()) => { + match background_position::parse_one(saved_component_value) { + Ok(v) => { + position = Some(v); + any = true; + // We haven't used the current `component_value`; + // keep attempting to parse it below. + }, + // If we get here, parsing failed. + Err(()) => return Err(()) + } + } + } + } + None => () // Wait until we have a pair of potential values. + } + } + if color.is_none() { match background_color::from_component_value(component_value, base_url) { Ok(v) => { @@ -1146,32 +1219,27 @@ pub mod shorthands { } } - match mem::replace(&mut last_component_value, None) { - Some(saved_component_value) => { - if position.is_none() { - match background_position::parse_horizontal_and_vertical( - saved_component_value, - component_value) { - Ok(v) => { - position = Some(v); - any = true; - continue - }, - Err(()) => (), - } - } + // Save the component value. It may the first of a background-position pair. + unused_component_value = Some(component_value); + } - // If we get here, parsing failed. - return Err(()) - } - None => { - // Save the component value. - last_component_value = Some(component_value) + if position.is_none() { + // Check for a lone trailing background-position value. + match mem::replace(&mut unused_component_value, None) { + Some(saved_component_value) => { + match background_position::parse_one(saved_component_value) { + Ok(v) => { + position = Some(v); + any = true; + }, + Err(()) => return Err(()) + } } + None => () } } - if any && last_component_value.is_none() { + if any && unused_component_value.is_none() { Ok(Longhands { background_color: color, background_image: image, diff --git a/tests/ref/100x100_green.png b/tests/ref/100x100_green.png new file mode 100644 index 00000000000..b23bbba154e Binary files /dev/null and b/tests/ref/100x100_green.png differ diff --git a/tests/ref/background_position_keyword.html b/tests/ref/background_position_keyword.html new file mode 100644 index 00000000000..6e7962d0b46 --- /dev/null +++ b/tests/ref/background_position_keyword.html @@ -0,0 +1,20 @@ + + + +You see here a scroll labeled FOOBIE BLETCH. + + + +
+ + + diff --git a/tests/ref/background_position_percent.html b/tests/ref/background_position_percent.html new file mode 100644 index 00000000000..3b3ecc77ec0 --- /dev/null +++ b/tests/ref/background_position_percent.html @@ -0,0 +1,20 @@ + + + +You see here a scroll labeled FOOBIE BLETCH. + + + +
+ + + diff --git a/tests/ref/background_position_shorthand.html b/tests/ref/background_position_shorthand.html new file mode 100644 index 00000000000..aac75adeae3 --- /dev/null +++ b/tests/ref/background_position_shorthand.html @@ -0,0 +1,29 @@ + + + +background shorthand test + + + +
+
+
+
+
+
+ + + diff --git a/tests/ref/background_position_shorthand_ref.html b/tests/ref/background_position_shorthand_ref.html new file mode 100644 index 00000000000..ed748dffd88 --- /dev/null +++ b/tests/ref/background_position_shorthand_ref.html @@ -0,0 +1,31 @@ + + + +background shorthand test + + + +
+
+
+
+
+
+ + + diff --git a/tests/ref/basic.list b/tests/ref/basic.list index 8c2edc43c14..9e325df466d 100644 --- a/tests/ref/basic.list +++ b/tests/ref/basic.list @@ -67,6 +67,9 @@ == min_max_height_a.html min_max_height_b.html == minimum_line_height_a.html minimum_line_height_b.html == background_position_a.html background_position_b.html +== background_position_keyword.html background_position_b.html +== background_position_percent.html background_position_b.html +== background_position_shorthand.html background_position_shorthand_ref.html == background_repeat_x_a.html background_repeat_x_b.html == background_repeat_y_a.html background_repeat_y_b.html == background_repeat_none_a.html background_repeat_none_b.html