Handle 3- and 4- valued <position>s in specified style

This commit is contained in:
Nazım Can Altınova 2016-08-25 16:21:43 +03:00
parent f84e01ecaf
commit 4eb93c85bc
3 changed files with 182 additions and 48 deletions

View file

@ -107,8 +107,10 @@ ${helpers.predefined_type("background-color", "CSSColor",
pub fn get_initial_specified_value() -> SpecifiedValue { pub fn get_initial_specified_value() -> SpecifiedValue {
use values::specified::Percentage; use values::specified::Percentage;
Position { Position {
horizontal: specified::LengthOrPercentage::Percentage(Percentage(0.0)), horiz_keyword: None,
vertical: specified::LengthOrPercentage::Percentage(Percentage(0.0)), horiz_position: Some(specified::LengthOrPercentage::Percentage(Percentage(0.0))),
vert_keyword: None,
vert_position: Some(specified::LengthOrPercentage::Percentage(Percentage(0.0))),
} }
} }

View file

@ -265,8 +265,10 @@ impl Circle {
} else { } else {
// Defaults to origin // Defaults to origin
Position { Position {
horizontal: LengthOrPercentage::Percentage(Percentage(0.5)), horiz_keyword: None,
vertical: LengthOrPercentage::Percentage(Percentage(0.5)), horiz_position: Some(LengthOrPercentage::Percentage(Percentage(0.5))),
vert_keyword: None,
vert_position: Some(LengthOrPercentage::Percentage(Percentage(0.5))),
} }
}; };
Ok(Circle { Ok(Circle {
@ -329,8 +331,10 @@ impl Ellipse {
} else { } else {
// Defaults to origin // Defaults to origin
Position { Position {
horizontal: LengthOrPercentage::Percentage(Percentage(0.5)), horiz_keyword: None,
vertical: LengthOrPercentage::Percentage(Percentage(0.5)), horiz_position: Some(LengthOrPercentage::Percentage(Percentage(0.5))),
vert_keyword: None,
vert_position: Some(LengthOrPercentage::Percentage(Percentage(0.5))),
} }
}; };
Ok(Ellipse { Ok(Ellipse {

View file

@ -17,28 +17,54 @@ use values::specified::{LengthOrPercentage, Percentage};
#[derive(Debug, Clone, PartialEq, Copy)] #[derive(Debug, Clone, PartialEq, Copy)]
#[cfg_attr(feature = "servo", derive(HeapSizeOf))] #[cfg_attr(feature = "servo", derive(HeapSizeOf))]
pub struct Position { pub struct Position {
pub horizontal: LengthOrPercentage, pub horiz_keyword: Option<Keyword>,
pub vertical: LengthOrPercentage, pub horiz_position: Option<LengthOrPercentage>,
pub vert_keyword: Option<Keyword>,
pub vert_position: Option<LengthOrPercentage>,
} }
impl ToCss for Position { impl ToCss for Position {
fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write { fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
try!(self.horizontal.to_css(dest)); // TODO: canaltinova: We should add keywords, probably?
if let Some(horiz_pos) = self.horiz_position {
if let Some(horiz_key) = self.horiz_keyword {
try!(horiz_key.to_css(dest));
};
try!(horiz_pos.to_css(dest));
};
try!(dest.write_str(" ")); try!(dest.write_str(" "));
try!(self.vertical.to_css(dest));
if let Some(vert_pos) = self.vert_position {
if let Some(vert_key) = self.vert_keyword {
try!(vert_key.to_css(dest));
};
try!(vert_pos.to_css(dest));
};
Ok(()) Ok(())
} }
} }
impl HasViewportPercentage for Position { impl HasViewportPercentage for Position {
fn has_viewport_percentage(&self) -> bool { fn has_viewport_percentage(&self) -> bool {
self.horizontal.has_viewport_percentage() || self.vertical.has_viewport_percentage() let horiz_viewport = if let Some(horiz_pos) = self.horiz_position {
horiz_pos.has_viewport_percentage()
} else {
false
};
let vert_viewport = if let Some(vert_pos) = self.vert_position {
vert_pos.has_viewport_percentage()
} else {
false
};
horiz_viewport || vert_viewport
} }
} }
// http://dev.w3.org/csswg/css2/colors.html#propdef-background-position
#[derive(Clone, PartialEq, Copy)] #[derive(Clone, PartialEq, Copy)]
pub enum PositionComponent { #[cfg_attr(feature = "servo", derive(HeapSizeOf))]
LengthOrPercentage(LengthOrPercentage), pub enum Keyword {
Center, Center,
Left, Left,
Right, Right,
@ -46,10 +72,49 @@ pub enum PositionComponent {
Bottom, Bottom,
} }
// http://dev.w3.org/csswg/css2/colors.html#propdef-background-position
#[derive(Clone, PartialEq, Copy)]
pub enum PositionComponent {
Length(LengthOrPercentage),
Keyword(Keyword),
}
impl Position { impl Position {
pub fn new(first: PositionComponent, second: PositionComponent) pub fn new(first_position: Option<PositionComponent>, second_position: Option<PositionComponent>,
first_keyword: Option<PositionComponent>, second_keyword: Option<PositionComponent>)
-> Result<Position, ()> { -> Result<Position, ()> {
let (horiz, vert) = match (category(first), category(second)) { // Check firts and second positions, this is more like for 2 value backgrounds.
let (mut horiz, mut vert) = if let Some(first_pos) = first_position {
if let Some(second_pos) = second_position {
match (category(first_pos), category(second_pos)) {
// Don't allow two vertical keywords or two horizontal keywords.
// also don't allow length/percentage values in the wrong position
(PositionCategory::HorizontalKeyword, PositionCategory::HorizontalKeyword) |
(PositionCategory::VerticalKeyword, PositionCategory::VerticalKeyword) |
(PositionCategory::LengthOrPercentage, PositionCategory::HorizontalKeyword) |
(PositionCategory::VerticalKeyword, PositionCategory::LengthOrPercentage) => return Err(()),
// Swap if both are keywords and vertical precedes horizontal.
(PositionCategory::VerticalKeyword, PositionCategory::HorizontalKeyword) |
(PositionCategory::VerticalKeyword, PositionCategory::OtherKeyword) |
(PositionCategory::OtherKeyword, PositionCategory::HorizontalKeyword) =>
(second_position, first_position),
// By default, horizontal is first.
_ => (first_position, second_position),
}
} else {
(first_position, second_position)
}
} else {
(first_position, second_position)
};
// Unwrap for checking if values are at right place.
let first_key = first_keyword.unwrap_or(PositionComponent::Keyword(Keyword::Left));
let second_key = second_keyword.unwrap_or(PositionComponent::Keyword(Keyword::Top));
// Check first and second keywords. This is for 4 value swapping.
let (horiz_keyword, vert_keyword) = match (category(first_key), category(second_key)) {
// Don't allow two vertical keywords or two horizontal keywords. // Don't allow two vertical keywords or two horizontal keywords.
// also don't allow length/percentage values in the wrong position // also don't allow length/percentage values in the wrong position
(PositionCategory::HorizontalKeyword, PositionCategory::HorizontalKeyword) | (PositionCategory::HorizontalKeyword, PositionCategory::HorizontalKeyword) |
@ -60,21 +125,80 @@ impl Position {
// Swap if both are keywords and vertical precedes horizontal. // Swap if both are keywords and vertical precedes horizontal.
(PositionCategory::VerticalKeyword, PositionCategory::HorizontalKeyword) | (PositionCategory::VerticalKeyword, PositionCategory::HorizontalKeyword) |
(PositionCategory::VerticalKeyword, PositionCategory::OtherKeyword) | (PositionCategory::VerticalKeyword, PositionCategory::OtherKeyword) |
(PositionCategory::OtherKeyword, PositionCategory::HorizontalKeyword) => (second, first), (PositionCategory::OtherKeyword, PositionCategory::HorizontalKeyword) => {
let tmp = horiz;
horiz = vert;
vert = tmp;
(second_keyword, first_keyword)
},
// By default, horizontal is first. // By default, horizontal is first.
_ => (first, second), _ => (first_keyword, second_keyword),
}; };
// Unwrap keywords from PositionComponent and wrap with Option.
let (wrapped_horiz_key, wrapped_vert_key) = if let Some(PositionComponent::Keyword(horiz_key)) = horiz_keyword {
if let Some(PositionComponent::Keyword(vert_key)) = vert_keyword {
(Some(horiz_key), Some(vert_key))
} else {
(Some(horiz_key), None)
}
} else {
(None, None)
};
Ok(Position { Ok(Position {
horizontal: horiz.to_length_or_percentage(), horiz_keyword: wrapped_horiz_key,
vertical: vert.to_length_or_percentage(), horiz_position: Some(horiz.unwrap().to_length_or_percentage()),
vert_keyword: wrapped_vert_key,
vert_position: Some(vert.unwrap().to_length_or_percentage()),
}) })
} }
pub fn parse(input: &mut Parser) -> Result<Position, ()> { pub fn parse(input: &mut Parser) -> Result<Position, ()> {
let first = try!(PositionComponent::parse(input)); let first = try!(PositionComponent::parse(input));
let second = input.try(PositionComponent::parse) let second = input.try(PositionComponent::parse)
.unwrap_or(PositionComponent::Center); .unwrap_or(PositionComponent::Keyword(Keyword::Center));
Position::new(first, second)
// Try to parse third and fourth values
if let Ok(third) = input.try(PositionComponent::parse) {
if let Ok(fourth) = input.try(PositionComponent::parse) {
// Handle 4 value background position
Position::new(Some(second), Some(fourth), Some(first), Some(third))
} else {
// Handle 3 value background position
if let PositionCategory::LengthOrPercentage = category(first) {
// "20px bottom 20%"
Position::new(Some(first), Some(third), None, Some(second))
} else {
if let PositionCategory::LengthOrPercentage = category(second) {
if let PositionCategory::HorizontalKeyword = category(third) {
// "bottom 10% right"
Position::new(Some(second), None, Some(first), Some(third))
} else {
// "right 10px 50%"
Position::new(Some(second), Some(third), Some(first), None)
}
} else {
// "right bottom 10px"
Position::new(None, Some(third), Some(first), Some(second))
}
}
}
} else {
// Handle 2 value background position
Position::new(Some(first), Some(second), None, None)
}
}
}
impl ToCss for Keyword {
fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
match *self {
Keyword::Right => try!(dest.write_str("right ")),
Keyword::Bottom => try!(dest.write_str("bottom ")),
_ => (),
};
} }
} }
@ -87,17 +211,19 @@ enum PositionCategory {
} }
fn category(p: PositionComponent) -> PositionCategory { fn category(p: PositionComponent) -> PositionCategory {
match p { if let PositionComponent::Keyword(keyword) = p {
PositionComponent::Left | match keyword {
PositionComponent::Right => Keyword::Left |
PositionCategory::HorizontalKeyword, Keyword::Right =>
PositionComponent::Top | PositionCategory::HorizontalKeyword,
PositionComponent::Bottom => Keyword::Top |
PositionCategory::VerticalKeyword, Keyword::Bottom =>
PositionComponent::Center => PositionCategory::VerticalKeyword,
PositionCategory::OtherKeyword, Keyword::Center =>
PositionComponent::LengthOrPercentage(_) => PositionCategory::OtherKeyword,
PositionCategory::LengthOrPercentage, }
} else {
PositionCategory::LengthOrPercentage
} }
} }
@ -107,8 +233,8 @@ impl ToComputedValue for Position {
#[inline] #[inline]
fn to_computed_value(&self, context: &Context) -> computed_position::Position { fn to_computed_value(&self, context: &Context) -> computed_position::Position {
computed_position::Position { computed_position::Position {
horizontal: self.horizontal.to_computed_value(context), horizontal: self.horiz_position.to_computed_value(context),
vertical: self.vertical.to_computed_value(context), vertical: self.vert_position.to_computed_value(context),
} }
} }
} }
@ -116,7 +242,7 @@ impl ToComputedValue for Position {
impl HasViewportPercentage for PositionComponent { impl HasViewportPercentage for PositionComponent {
fn has_viewport_percentage(&self) -> bool { fn has_viewport_percentage(&self) -> bool {
match *self { match *self {
PositionComponent::LengthOrPercentage(length) => length.has_viewport_percentage(), PositionComponent::Length(length) => length.has_viewport_percentage(),
_ => false _ => false
} }
} }
@ -125,16 +251,16 @@ impl HasViewportPercentage for PositionComponent {
impl PositionComponent { impl PositionComponent {
pub fn parse(input: &mut Parser) -> Result<PositionComponent, ()> { pub fn parse(input: &mut Parser) -> Result<PositionComponent, ()> {
input.try(LengthOrPercentage::parse) input.try(LengthOrPercentage::parse)
.map(PositionComponent::LengthOrPercentage) .map(PositionComponent::Length)
.or_else(|()| { .or_else(|()| {
match try!(input.next()) { match try!(input.next()) {
Token::Ident(value) => { Token::Ident(value) => {
match_ignore_ascii_case! { value, match_ignore_ascii_case! { value,
"center" => Ok(PositionComponent::Center), "center" => Ok(PositionComponent::Keyword(Keyword::Center)),
"left" => Ok(PositionComponent::Left), "left" => Ok(PositionComponent::Keyword(Keyword::Left)),
"right" => Ok(PositionComponent::Right), "right" => Ok(PositionComponent::Keyword(Keyword::Right)),
"top" => Ok(PositionComponent::Top), "top" => Ok(PositionComponent::Keyword(Keyword::Top)),
"bottom" => Ok(PositionComponent::Bottom), "bottom" => Ok(PositionComponent::Keyword(Keyword::Bottom)),
_ => Err(()) _ => Err(())
} }
}, },
@ -145,12 +271,14 @@ impl PositionComponent {
#[inline] #[inline]
pub fn to_length_or_percentage(self) -> LengthOrPercentage { pub fn to_length_or_percentage(self) -> LengthOrPercentage {
match self { match self {
PositionComponent::LengthOrPercentage(value) => value, PositionComponent::Length(value) => value,
PositionComponent::Center => LengthOrPercentage::Percentage(Percentage(0.5)), PositionComponent::Keyword(keyword) if keyword == Keyword::Center =>
PositionComponent::Left | LengthOrPercentage::Percentage(Percentage(0.5)),
PositionComponent::Top => LengthOrPercentage::Percentage(Percentage(0.0)), PositionComponent::Keyword(keyword) if keyword == Keyword::Left ||
PositionComponent::Right | keyword == Keyword::Top => LengthOrPercentage::Percentage(Percentage(0.0)),
PositionComponent::Bottom => LengthOrPercentage::Percentage(Percentage(1.0)), PositionComponent::Keyword(keyword) if keyword == Keyword::Right ||
keyword == Keyword::Bottom => LengthOrPercentage::Percentage(Percentage(1.0)),
PositionComponent::Keyword(_) => unimplemented!(), // TODO: All keywords are covered but rust forcing me to add this too?
} }
} }
} }