mirror of
https://github.com/servo/servo.git
synced 2025-07-23 15:23:42 +01:00
Implement background-position keyword values
Also fixes calculation of background-position percentages: Rather than multiplying the container size by a percent and aligning the top left of the image at the resulting width, we also need to subtract a corresponding percent of the image size, per http://dev.w3.org/csswg/css2/colors.html#propdef-background-position "A value pair of '100% 100%' places the lower right corner of the image in the lower right corner of the padding box. With a value pair of '14% 84%', the point 14% across and 84% down the image is to be placed at the point 14% across and 84% down the padding box."
This commit is contained in:
parent
66a7de750c
commit
6c9524b687
9 changed files with 258 additions and 50 deletions
|
@ -728,12 +728,15 @@ impl Fragment {
|
||||||
};
|
};
|
||||||
debug!("(building display list) building background image");
|
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`.
|
// Adjust bounds for `background-position` and `background-attachment`.
|
||||||
let mut bounds = *absolute_bounds;
|
let mut bounds = *absolute_bounds;
|
||||||
let horizontal_position = model::specified(background.background_position.horizontal,
|
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,
|
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
|
// TODO: These are some situations below where it is possible
|
||||||
// to determine that clipping is not necessary - this is an
|
// to determine that clipping is not necessary - this is an
|
||||||
|
@ -743,9 +746,6 @@ impl Fragment {
|
||||||
children: DisplayList::new(),
|
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 {
|
match background.background_attachment {
|
||||||
background_attachment::scroll => {
|
background_attachment::scroll => {
|
||||||
bounds.origin.x = bounds.origin.x + horizontal_position;
|
bounds.origin.x = bounds.origin.x + horizontal_position;
|
||||||
|
@ -759,25 +759,21 @@ impl Fragment {
|
||||||
}
|
}
|
||||||
background_repeat::repeat_x => {
|
background_repeat::repeat_x => {
|
||||||
bounds.size.height = Au::min(bounds.size.height - vertical_position, image_height);
|
bounds.size.height = Au::min(bounds.size.height - vertical_position, image_height);
|
||||||
if horizontal_position > Au(0) {
|
if horizontal_position != Au(0) {
|
||||||
bounds.origin.x = bounds.origin.x - image_width;
|
|
||||||
bounds.size.width = bounds.size.width + image_width;
|
bounds.size.width = bounds.size.width + image_width;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
background_repeat::repeat_y => {
|
background_repeat::repeat_y => {
|
||||||
bounds.size.width = Au::min(bounds.size.width - horizontal_position, image_width);
|
bounds.size.width = Au::min(bounds.size.width - horizontal_position, image_width);
|
||||||
if vertical_position > Au(0) {
|
if vertical_position != Au(0) {
|
||||||
bounds.origin.y = bounds.origin.y - image_height;
|
|
||||||
bounds.size.height = bounds.size.height + image_height;
|
bounds.size.height = bounds.size.height + image_height;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
background_repeat::repeat => {
|
background_repeat::repeat => {
|
||||||
if horizontal_position > Au(0) {
|
if horizontal_position != Au(0) {
|
||||||
bounds.origin.x = bounds.origin.x - image_width;
|
|
||||||
bounds.size.width = bounds.size.width + image_width;
|
bounds.size.width = bounds.size.width + image_width;
|
||||||
}
|
}
|
||||||
if vertical_position > Au(0) {
|
if vertical_position != Au(0) {
|
||||||
bounds.origin.y = bounds.origin.y - image_height;
|
|
||||||
bounds.size.height = bounds.size.height + image_height;
|
bounds.size.height = bounds.size.height + image_height;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -160,6 +160,47 @@ pub mod specified {
|
||||||
LengthOrPercentageOrNone::parse_internal(input, /* negative_ok = */ false)
|
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<PositionComponent, ()> {
|
||||||
|
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 {
|
pub mod computed {
|
||||||
|
|
|
@ -627,6 +627,46 @@ pub mod longhands {
|
||||||
pub vertical: specified::LengthOrPercentage,
|
pub vertical: specified::LengthOrPercentage,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl SpecifiedValue {
|
||||||
|
fn new(first: specified::PositionComponent, second: specified::PositionComponent)
|
||||||
|
-> Result<SpecifiedValue,()> {
|
||||||
|
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]
|
#[inline]
|
||||||
pub fn to_computed_value(value: SpecifiedValue, context: &computed::Context)
|
pub fn to_computed_value(value: SpecifiedValue, context: &computed::Context)
|
||||||
-> computed_value::T {
|
-> computed_value::T {
|
||||||
|
@ -644,29 +684,32 @@ pub mod longhands {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// FIXME(#1997, pcwalton): Support complete CSS2 syntax.
|
pub fn parse_one(first: &ComponentValue) -> Result<SpecifiedValue, ()> {
|
||||||
pub fn parse_horizontal_and_vertical(horiz: &ComponentValue, vert: &ComponentValue)
|
let first = try!(specified::PositionComponent::parse(first));
|
||||||
-> Result<SpecifiedValue, ()> {
|
// If only one value is provided, use `center` for the second.
|
||||||
let horiz = try!(specified::LengthOrPercentage::parse_non_negative(horiz));
|
SpecifiedValue::new(first, specified::Pos_Center)
|
||||||
let vert = try!(specified::LengthOrPercentage::parse_non_negative(vert));
|
}
|
||||||
|
|
||||||
Ok(SpecifiedValue {
|
pub fn parse_two(first: &ComponentValue, second: &ComponentValue)
|
||||||
horizontal: horiz,
|
-> Result<SpecifiedValue, ()> {
|
||||||
vertical: vert,
|
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<SpecifiedValue, ()> {
|
pub fn parse(input: &[ComponentValue], _: &Url) -> Result<SpecifiedValue, ()> {
|
||||||
let mut input_iter = input.skip_whitespace();
|
let mut input_iter = input.skip_whitespace();
|
||||||
let horizontal = input_iter.next();
|
let first = input_iter.next();
|
||||||
let vertical = input_iter.next();
|
let second = input_iter.next();
|
||||||
if input_iter.next().is_some() {
|
if input_iter.next().is_some() {
|
||||||
return Err(())
|
return Err(())
|
||||||
}
|
}
|
||||||
|
match (first, second) {
|
||||||
match (horizontal, vertical) {
|
(Some(first), Some(second)) => {
|
||||||
(Some(horizontal), Some(vertical)) => {
|
parse_two(first, second)
|
||||||
parse_horizontal_and_vertical(horizontal, vertical)
|
}
|
||||||
|
(Some(first), None) => {
|
||||||
|
parse_one(first)
|
||||||
}
|
}
|
||||||
_ => Err(())
|
_ => Err(())
|
||||||
}
|
}
|
||||||
|
@ -1097,10 +1140,40 @@ pub mod shorthands {
|
||||||
|
|
||||||
let (mut color, mut image, mut position, mut repeat, mut attachment) =
|
let (mut color, mut image, mut position, mut repeat, mut attachment) =
|
||||||
(None, None, None, None, None);
|
(None, None, None, None, None);
|
||||||
let mut last_component_value = None;
|
let mut unused_component_value = None;
|
||||||
let mut any = false;
|
let mut any = false;
|
||||||
|
|
||||||
for component_value in input.skip_whitespace() {
|
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() {
|
if color.is_none() {
|
||||||
match background_color::from_component_value(component_value, base_url) {
|
match background_color::from_component_value(component_value, base_url) {
|
||||||
Ok(v) => {
|
Ok(v) => {
|
||||||
|
@ -1146,32 +1219,27 @@ pub mod shorthands {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
match mem::replace(&mut last_component_value, None) {
|
// Save the component value. It may the first of a background-position pair.
|
||||||
Some(saved_component_value) => {
|
unused_component_value = Some(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(()) => (),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// If we get here, parsing failed.
|
if position.is_none() {
|
||||||
return Err(())
|
// Check for a lone trailing background-position value.
|
||||||
}
|
match mem::replace(&mut unused_component_value, None) {
|
||||||
None => {
|
Some(saved_component_value) => {
|
||||||
// Save the component value.
|
match background_position::parse_one(saved_component_value) {
|
||||||
last_component_value = Some(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 {
|
Ok(Longhands {
|
||||||
background_color: color,
|
background_color: color,
|
||||||
background_image: image,
|
background_image: image,
|
||||||
|
|
BIN
tests/ref/100x100_green.png
Normal file
BIN
tests/ref/100x100_green.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 215 B |
20
tests/ref/background_position_keyword.html
Normal file
20
tests/ref/background_position_keyword.html
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>You see here a scroll labeled FOOBIE BLETCH.</title>
|
||||||
|
<style>
|
||||||
|
#foo {
|
||||||
|
background: url(400x400_green.png);
|
||||||
|
background-position: right top;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
width: 528px;
|
||||||
|
height: 400px;
|
||||||
|
margin-left: 0;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id=foo></div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
20
tests/ref/background_position_percent.html
Normal file
20
tests/ref/background_position_percent.html
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>You see here a scroll labeled FOOBIE BLETCH.</title>
|
||||||
|
<style>
|
||||||
|
#foo {
|
||||||
|
background: url(400x400_green.png);
|
||||||
|
background-position: 100% 0%;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
width: 528px;
|
||||||
|
height: 400px;
|
||||||
|
margin-left: 0;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id=foo></div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
29
tests/ref/background_position_shorthand.html
Normal file
29
tests/ref/background_position_shorthand.html
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>background shorthand test</title>
|
||||||
|
<style>
|
||||||
|
div {
|
||||||
|
float: left; /* So all the divs are visible in the test viewport */
|
||||||
|
width: 200px;
|
||||||
|
height: 200px;
|
||||||
|
}
|
||||||
|
/* test different orders for background position */
|
||||||
|
#a { background: red url(100x100_green.png) no-repeat right center; }
|
||||||
|
#b { background: red url(100x100_green.png) no-repeat center right; }
|
||||||
|
#c { background: right center red url(100x100_green.png) no-repeat; }
|
||||||
|
#d { background: red url(100x100_green.png) right center no-repeat; }
|
||||||
|
#e { background: red url(100x100_green.png) no-repeat right; }
|
||||||
|
#f { background: right red url(100x100_green.png) no-repeat; }
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="a"></div>
|
||||||
|
<div id="b"></div>
|
||||||
|
<div id="c"></div>
|
||||||
|
<div id="d"></div>
|
||||||
|
<div id="e"></div>
|
||||||
|
<div id="f"></div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
31
tests/ref/background_position_shorthand_ref.html
Normal file
31
tests/ref/background_position_shorthand_ref.html
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>background shorthand test</title>
|
||||||
|
<style>
|
||||||
|
.outer {
|
||||||
|
float: left;
|
||||||
|
background-color: red;
|
||||||
|
width: 200px;
|
||||||
|
height: 200px;
|
||||||
|
}
|
||||||
|
.inner {
|
||||||
|
background-image: url(100x100_green.png);
|
||||||
|
width: 100px;
|
||||||
|
height: 100px;
|
||||||
|
position: relative;
|
||||||
|
top: 50px;
|
||||||
|
left: 100px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="outer"><div class="inner"></div></div>
|
||||||
|
<div class="outer"><div class="inner"></div></div>
|
||||||
|
<div class="outer"><div class="inner"></div></div>
|
||||||
|
<div class="outer"><div class="inner"></div></div>
|
||||||
|
<div class="outer"><div class="inner"></div></div>
|
||||||
|
<div class="outer"><div class="inner"></div></div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
|
@ -67,6 +67,9 @@
|
||||||
== min_max_height_a.html min_max_height_b.html
|
== min_max_height_a.html min_max_height_b.html
|
||||||
== minimum_line_height_a.html minimum_line_height_b.html
|
== minimum_line_height_a.html minimum_line_height_b.html
|
||||||
== background_position_a.html background_position_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_x_a.html background_repeat_x_b.html
|
||||||
== background_repeat_y_a.html background_repeat_y_b.html
|
== background_repeat_y_a.html background_repeat_y_b.html
|
||||||
== background_repeat_none_a.html background_repeat_none_b.html
|
== background_repeat_none_a.html background_repeat_none_b.html
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue