Refactor Position

A specified position is now a struct made of two values of different types,
the first one being PositionComponent<X>, and the second one PositionComponent<Y>.

A position component is represented by the new enum PositionComponent<Side>,
with the three values Center, Length(LengthOrPercentage), and
Side(Side, Option<LengthOrPercentage>).

Side keywords are represented by the X and Y enums, which don't include a value
for the center keyword anymore. They are accompanied by the Side trait, which
allows us to determine whether a side keyword is "left" or "top".

This refactor simplified the parsing and serialisation code and exposed bugs in it,
where it would reject valid <position> values followed by arbitrary tokens,
and where it would fail to prefer "left" to "right" when serialising positions
in basic shapes.
This commit is contained in:
Anthony Ramine 2017-05-08 03:09:26 +02:00
parent 0040160b38
commit 70ec61cf01
22 changed files with 484 additions and 887 deletions

View file

@ -1008,9 +1008,9 @@ impl FragmentDisplayListBuilding for Fragment {
let horiz_position = *get_cyclic(&background.background_position_x.0, index); let horiz_position = *get_cyclic(&background.background_position_x.0, index);
let vert_position = *get_cyclic(&background.background_position_y.0, index); let vert_position = *get_cyclic(&background.background_position_y.0, index);
// Use `background-position` to get the offset. // Use `background-position` to get the offset.
let horizontal_position = model::specified(horiz_position.0, let horizontal_position = model::specified(horiz_position,
bounds.size.width - image_size.width); bounds.size.width - image_size.width);
let vertical_position = model::specified(vert_position.0, let vertical_position = model::specified(vert_position,
bounds.size.height - image_size.height); bounds.size.height - image_size.height);
// The anchor position for this background, based on both the background-attachment // The anchor position for this background, based on both the background-attachment
@ -1167,8 +1167,8 @@ impl FragmentDisplayListBuilding for Fragment {
repeating: bool, repeating: bool,
style: &ServoComputedValues) style: &ServoComputedValues)
-> display_list::RadialGradient { -> display_list::RadialGradient {
let center = Point2D::new(specified(center.horizontal.0, bounds.size.width), let center = Point2D::new(specified(center.horizontal, bounds.size.width),
specified(center.vertical.0, bounds.size.height)); specified(center.vertical, bounds.size.height));
let radius = match *shape { let radius = match *shape {
EndingShape::Circle(LengthOrKeyword::Length(length)) EndingShape::Circle(LengthOrKeyword::Length(length))
=> Size2D::new(length, length), => Size2D::new(length, length),

View file

@ -307,8 +307,8 @@ impl nsStyleImage {
}, },
} }
unsafe { unsafe {
(*gecko_gradient).mBgPosX.set(position.horizontal.0); (*gecko_gradient).mBgPosX.set(position.horizontal);
(*gecko_gradient).mBgPosY.set(position.vertical.0); (*gecko_gradient).mBgPosY.set(position.vertical);
} }
gecko_gradient gecko_gradient
@ -372,7 +372,6 @@ pub mod basic_shape {
use values::computed::position; use values::computed::position;
use values::generics::BorderRadiusSize as GenericBorderRadiusSize; use values::generics::BorderRadiusSize as GenericBorderRadiusSize;
use values::generics::basic_shape::FillRule; use values::generics::basic_shape::FillRule;
use values::generics::position::{HorizontalPosition, VerticalPosition};
// using Borrow so that we can have a non-moving .into() // using Borrow so that we can have a non-moving .into()
impl<T: Borrow<StyleBasicShape>> From<T> for BasicShape { impl<T: Borrow<StyleBasicShape>> From<T> for BasicShape {
@ -483,8 +482,8 @@ pub mod basic_shape {
impl From<position::Position> for structs::Position { impl From<position::Position> for structs::Position {
fn from(other: position::Position) -> Self { fn from(other: position::Position) -> Self {
structs::Position { structs::Position {
mXPosition: other.horizontal.0.into(), mXPosition: other.horizontal.into(),
mYPosition: other.vertical.0.into() mYPosition: other.vertical.into()
} }
} }
} }
@ -501,8 +500,8 @@ pub mod basic_shape {
fn from(other: T) -> Self { fn from(other: T) -> Self {
let other = other.borrow(); let other = other.borrow();
position::Position { position::Position {
horizontal: HorizontalPosition(other.mXPosition.into()), horizontal: other.mXPosition.into(),
vertical: VerticalPosition(other.mYPosition.into()), vertical: other.mYPosition.into(),
} }
} }
} }

View file

@ -367,17 +367,16 @@ fn color_to_nscolor_zero_currentcolor(color: Color) -> structs::nscolor {
<%def name="impl_position(ident, gecko_ffi_name, need_clone=False)"> <%def name="impl_position(ident, gecko_ffi_name, need_clone=False)">
#[allow(non_snake_case)] #[allow(non_snake_case)]
pub fn set_${ident}(&mut self, v: longhands::${ident}::computed_value::T) { pub fn set_${ident}(&mut self, v: longhands::${ident}::computed_value::T) {
${set_gecko_property("%s.mXPosition" % gecko_ffi_name, "v.horizontal.0.into()")} ${set_gecko_property("%s.mXPosition" % gecko_ffi_name, "v.horizontal.into()")}
${set_gecko_property("%s.mYPosition" % gecko_ffi_name, "v.vertical.0.into()")} ${set_gecko_property("%s.mYPosition" % gecko_ffi_name, "v.vertical.into()")}
} }
<%call expr="impl_simple_copy(ident, gecko_ffi_name)"></%call> <%call expr="impl_simple_copy(ident, gecko_ffi_name)"></%call>
% if need_clone: % if need_clone:
#[allow(non_snake_case)] #[allow(non_snake_case)]
pub fn clone_${ident}(&self) -> longhands::${ident}::computed_value::T { pub fn clone_${ident}(&self) -> longhands::${ident}::computed_value::T {
use values::generics::position::{HorizontalPosition, Position, VerticalPosition}; longhands::${ident}::computed_value::T {
Position { horizontal: self.gecko.${gecko_ffi_name}.mXPosition.into(),
horizontal: HorizontalPosition(self.gecko.${gecko_ffi_name}.mXPosition.into()), vertical: self.gecko.${gecko_ffi_name}.mYPosition.into(),
vertical: VerticalPosition(self.gecko.${gecko_ffi_name}.mYPosition.into()),
} }
} }
% endif % endif
@ -2040,8 +2039,8 @@ fn static_assert() {
for (gecko, servo) in self.gecko.mScrollSnapCoordinate for (gecko, servo) in self.gecko.mScrollSnapCoordinate
.iter_mut() .iter_mut()
.zip(v) { .zip(v) {
gecko.mXPosition = servo.horizontal.0.into(); gecko.mXPosition = servo.horizontal.into();
gecko.mYPosition = servo.vertical.0.into(); gecko.mYPosition = servo.vertical.into();
} }
} }
@ -2726,12 +2725,12 @@ fn static_assert() {
} }
</%self:simple_image_array_property> </%self:simple_image_array_property>
% for orientation in [("x", "Horizontal"), ("y", "Vertical")]: % for orientation in ["x", "y"]:
pub fn copy_${shorthand}_position_${orientation[0]}_from(&mut self, other: &Self) { pub fn copy_${shorthand}_position_${orientation}_from(&mut self, other: &Self) {
use gecko_bindings::structs::nsStyleImageLayers_LayerType as LayerType; use gecko_bindings::structs::nsStyleImageLayers_LayerType as LayerType;
self.gecko.${image_layers_field}.mPosition${orientation[0].upper()}Count self.gecko.${image_layers_field}.mPosition${orientation.upper()}Count
= cmp::min(1, other.gecko.${image_layers_field}.mPosition${orientation[0].upper()}Count); = cmp::min(1, other.gecko.${image_layers_field}.mPosition${orientation.upper()}Count);
self.gecko.${image_layers_field}.mLayers.mFirstElement.mPosition = self.gecko.${image_layers_field}.mLayers.mFirstElement.mPosition =
other.gecko.${image_layers_field}.mLayers.mFirstElement.mPosition; other.gecko.${image_layers_field}.mLayers.mFirstElement.mPosition;
unsafe { unsafe {
@ -2742,20 +2741,19 @@ fn static_assert() {
for (layer, other) in self.gecko.${image_layers_field}.mLayers.iter_mut() for (layer, other) in self.gecko.${image_layers_field}.mLayers.iter_mut()
.zip(other.gecko.${image_layers_field}.mLayers.iter()) { .zip(other.gecko.${image_layers_field}.mLayers.iter()) {
layer.mPosition.m${orientation[0].upper()}Position layer.mPosition.m${orientation.upper()}Position
= other.mPosition.m${orientation[0].upper()}Position; = other.mPosition.m${orientation.upper()}Position;
} }
self.gecko.${image_layers_field}.mPosition${orientation[0].upper()}Count self.gecko.${image_layers_field}.mPosition${orientation.upper()}Count
= other.gecko.${image_layers_field}.mPosition${orientation[0].upper()}Count; = other.gecko.${image_layers_field}.mPosition${orientation.upper()}Count;
} }
pub fn clone_${shorthand}_position_${orientation[0]}(&self) pub fn clone_${shorthand}_position_${orientation}(&self)
-> longhands::${shorthand}_position_${orientation[0]}::computed_value::T { -> longhands::${shorthand}_position_${orientation}::computed_value::T {
use values::generics::position::${orientation[1]}Position; longhands::${shorthand}_position_${orientation}::computed_value::T(
longhands::${shorthand}_position_${orientation[0]}::computed_value::T(
self.gecko.${image_layers_field}.mLayers.iter() self.gecko.${image_layers_field}.mLayers.iter()
.take(self.gecko.${image_layers_field}.mPosition${orientation[0].upper()}Count as usize) .take(self.gecko.${image_layers_field}.mPosition${orientation.upper()}Count as usize)
.map(|position| ${orientation[1]}Position(position.mPosition.m${orientation[0].upper()}Position.into())) .map(|position| position.mPosition.m${orientation.upper()}Position.into())
.collect() .collect()
) )
} }
@ -2778,7 +2776,7 @@ fn static_assert() {
self.gecko.${image_layers_field}.mPosition${orientation[0].upper()}Count = v.len() as u32; self.gecko.${image_layers_field}.mPosition${orientation[0].upper()}Count = v.len() as u32;
for (servo, geckolayer) in v.zip(self.gecko.${image_layers_field} for (servo, geckolayer) in v.zip(self.gecko.${image_layers_field}
.mLayers.iter_mut()) { .mLayers.iter_mut()) {
geckolayer.mPosition.m${orientation[0].upper()}Position = servo.0.into(); geckolayer.mPosition.m${orientation[0].upper()}Position = servo.into();
} }
} }
% endfor % endfor

View file

@ -41,7 +41,6 @@ use values::computed::{Angle, LengthOrPercentageOrAuto, LengthOrPercentageOrNone
use values::computed::{BorderRadiusSize, ClipRect}; use values::computed::{BorderRadiusSize, ClipRect};
use values::computed::{CalcLengthOrPercentage, Context, LengthOrPercentage}; use values::computed::{CalcLengthOrPercentage, Context, LengthOrPercentage};
use values::computed::{MaxLength, MinLength}; use values::computed::{MaxLength, MinLength};
use values::computed::position::{HorizontalPosition, VerticalPosition};
use values::computed::ToComputedValue; use values::computed::ToComputedValue;
use values::generics::position as generic_position; use values::generics::position as generic_position;
@ -655,6 +654,7 @@ pub trait Animatable: Sized {
/// https://drafts.csswg.org/css-transitions/#animtype-repeatable-list /// https://drafts.csswg.org/css-transitions/#animtype-repeatable-list
pub trait RepeatableListAnimatable: Animatable {} pub trait RepeatableListAnimatable: Animatable {}
impl RepeatableListAnimatable for LengthOrPercentage {}
impl RepeatableListAnimatable for Either<f32, LengthOrPercentage> {} impl RepeatableListAnimatable for Either<f32, LengthOrPercentage> {}
impl<T: RepeatableListAnimatable> Animatable for SmallVec<[T; 1]> { impl<T: RepeatableListAnimatable> Animatable for SmallVec<[T; 1]> {
@ -1336,46 +1336,6 @@ impl<H: Animatable, V: Animatable> Animatable for generic_position::Position<H,
impl<H, V> RepeatableListAnimatable for generic_position::Position<H, V> impl<H, V> RepeatableListAnimatable for generic_position::Position<H, V>
where H: RepeatableListAnimatable, V: RepeatableListAnimatable {} where H: RepeatableListAnimatable, V: RepeatableListAnimatable {}
/// https://drafts.csswg.org/css-transitions/#animtype-simple-list
impl Animatable for HorizontalPosition {
#[inline]
fn interpolate(&self, other: &Self, progress: f64) -> Result<Self, ()> {
self.0.interpolate(&other.0, progress).map(generic_position::HorizontalPosition)
}
#[inline]
fn compute_distance(&self, other: &Self) -> Result<f64, ()> {
self.0.compute_distance(&other.0)
}
#[inline]
fn compute_squared_distance(&self, other: &Self) -> Result<f64, ()> {
self.0.compute_squared_distance(&other.0)
}
}
impl RepeatableListAnimatable for HorizontalPosition {}
/// https://drafts.csswg.org/css-transitions/#animtype-simple-list
impl Animatable for VerticalPosition {
#[inline]
fn interpolate(&self, other: &Self, progress: f64) -> Result<Self, ()> {
self.0.interpolate(&other.0, progress).map(generic_position::VerticalPosition)
}
#[inline]
fn compute_distance(&self, other: &Self) -> Result<f64, ()> {
self.0.compute_distance(&other.0)
}
#[inline]
fn compute_squared_distance(&self, other: &Self) -> Result<f64, ()> {
self.0.compute_squared_distance(&other.0)
}
}
impl RepeatableListAnimatable for VerticalPosition {}
/// https://drafts.csswg.org/css-transitions/#animtype-rect /// https://drafts.csswg.org/css-transitions/#animtype-rect
impl Animatable for ClipRect { impl Animatable for ClipRect {
#[inline] #[inline]

View file

@ -20,38 +20,13 @@ ${helpers.predefined_type("background-image", "LayerImage",
animation_value_type="none", animation_value_type="none",
has_uncacheable_values="True" if product == "gecko" else "False")} has_uncacheable_values="True" if product == "gecko" else "False")}
<%helpers:predefined_type name="background-position-x" type="position::HorizontalPosition" % for (axis, direction, initial) in [("x", "Horizontal", "left"), ("y", "Vertical", "top")]:
initial_value="computed::position::HorizontalPosition::zero()" ${helpers.predefined_type("background-position-" + axis, "position::" + direction + "Position",
initial_specified_value="specified::position::HorizontalPosition::left()" initial_value="computed::LengthOrPercentage::zero()",
spec="https://drafts.csswg.org/css-backgrounds-4/#propdef-background-position-x" initial_specified_value="SpecifiedValue::initial_specified_value()",
animation_value_type="ComputedValue" vector="True" delegate_animate="True"> spec="https://drafts.csswg.org/css-backgrounds-4/#propdef-background-position-" + axis,
#[inline] animation_value_type="ComputedValue", vector=True, delegate_animate=True)}
/// Get the initial value for horizontal position. % endfor
pub fn get_initial_position_value() -> SpecifiedValue {
use values::generics::position::{HorizontalPosition, PositionValue};
use values::specified::{LengthOrPercentage, Percentage};
HorizontalPosition(PositionValue {
keyword: None,
position: Some(LengthOrPercentage::Percentage(Percentage(0.0))),
})
}
</%helpers:predefined_type>
<%helpers:predefined_type name="background-position-y" type="position::VerticalPosition"
initial_value="computed::position::VerticalPosition::zero()"
initial_specified_value="specified::position::VerticalPosition::top()"
spec="https://drafts.csswg.org/css-backgrounds-4/#propdef-background-position-y"
animation_value_type="ComputedValue" vector="True" delegate_animate="True">
/// Get the initial value for vertical position.
pub fn get_initial_position_value() -> SpecifiedValue {
use values::generics::position::{VerticalPosition, PositionValue};
use values::specified::{LengthOrPercentage, Percentage};
VerticalPosition(PositionValue {
keyword: None,
position: Some(LengthOrPercentage::Percentage(Percentage(0.0))),
})
}
</%helpers:predefined_type>
<%helpers:vector_longhand name="background-repeat" animation_value_type="none" <%helpers:vector_longhand name="background-repeat" animation_value_type="none"
spec="https://drafts.csswg.org/css-backgrounds/#the-background-repeat"> spec="https://drafts.csswg.org/css-backgrounds/#the-background-repeat">

View file

@ -2143,11 +2143,11 @@ ${helpers.predefined_type("perspective",
animation_value_type="ComputedValue")} animation_value_type="ComputedValue")}
${helpers.predefined_type("perspective-origin", ${helpers.predefined_type("perspective-origin",
"position::OriginPosition", "position::Position",
"computed::position::OriginPosition::center()", "computed::position::Position::center()",
boxed="True", boxed="True",
extra_prefixes="moz webkit", extra_prefixes="moz webkit",
spec="https://drafts.csswg.org/css-transforms/#perspective-origin-property", spec="https://drafts.csswg.org/css-transforms-2/#perspective-origin-property",
animation_value_type="ComputedValue")} animation_value_type="ComputedValue")}
${helpers.single_keyword("backface-visibility", ${helpers.single_keyword("backface-visibility",

View file

@ -90,49 +90,14 @@ ${helpers.single_keyword("mask-mode",
} }
</%helpers:vector_longhand> </%helpers:vector_longhand>
<%helpers:vector_longhand name="mask-position-x" products="gecko" % for (axis, direction) in [("x", "Horizontal"), ("y", "Vertical")]:
animation_value_type="ComputedValue" extra_prefixes="webkit" ${helpers.predefined_type("mask-position-" + axis, "position::" + direction + "Position",
spec="https://drafts.fxtf.org/css-masking/#propdef-mask-position"> products="gecko", extra_prefixes="webkit",
pub use properties::longhands::background_position_x::single_value::get_initial_value; initial_value="computed::LengthOrPercentage::zero()",
pub use properties::longhands::background_position_x::single_value::get_initial_position_value; initial_specified_value="specified::PositionComponent::Center",
pub use properties::longhands::background_position_x::single_value::get_initial_specified_value; spec="https://drafts.fxtf.org/css-masking/#propdef-mask-position",
pub use properties::longhands::background_position_x::single_value::parse; animation_value_type="ComputedValue", vector=True, delegate_animate=True)}
pub use properties::longhands::background_position_x::single_value::SpecifiedValue; % endfor
pub use properties::longhands::background_position_x::single_value::computed_value;
use properties::animated_properties::{Animatable, RepeatableListAnimatable};
use properties::longhands::mask_position_x::computed_value::T as MaskPositionX;
impl Animatable for MaskPositionX {
#[inline]
fn interpolate(&self, other: &Self, progress: f64) -> Result<Self, ()> {
Ok(MaskPositionX(try!(self.0.interpolate(&other.0, progress))))
}
}
impl RepeatableListAnimatable for MaskPositionX {}
</%helpers:vector_longhand>
<%helpers:vector_longhand name="mask-position-y" products="gecko"
animation_value_type="ComputedValue" extra_prefixes="webkit"
spec="https://drafts.fxtf.org/css-masking/#propdef-mask-position">
pub use properties::longhands::background_position_y::single_value::get_initial_value;
pub use properties::longhands::background_position_y::single_value::get_initial_position_value;
pub use properties::longhands::background_position_y::single_value::get_initial_specified_value;
pub use properties::longhands::background_position_y::single_value::parse;
pub use properties::longhands::background_position_y::single_value::SpecifiedValue;
pub use properties::longhands::background_position_y::single_value::computed_value;
use properties::animated_properties::{Animatable, RepeatableListAnimatable};
use properties::longhands::mask_position_y::computed_value::T as MaskPositionY;
impl Animatable for MaskPositionY {
#[inline]
fn interpolate(&self, other: &Self, progress: f64) -> Result<Self, ()> {
Ok(MaskPositionY(try!(self.0.interpolate(&other.0, progress))))
}
}
impl RepeatableListAnimatable for MaskPositionY {}
</%helpers:vector_longhand>
${helpers.single_keyword("mask-clip", ${helpers.single_keyword("mask-clip",
"border-box content-box padding-box", "border-box content-box padding-box",

View file

@ -15,7 +15,7 @@
use properties::longhands::background_clip; use properties::longhands::background_clip;
use properties::longhands::background_clip::single_value::computed_value::T as Clip; use properties::longhands::background_clip::single_value::computed_value::T as Clip;
use properties::longhands::background_origin::single_value::computed_value::T as Origin; use properties::longhands::background_origin::single_value::computed_value::T as Origin;
use values::specified::position::Position; use values::specified::{Position, PositionComponent};
use parser::Parse; use parser::Parse;
impl From<background_origin::single_value::SpecifiedValue> for background_clip::single_value::SpecifiedValue { impl From<background_origin::single_value::SpecifiedValue> for background_clip::single_value::SpecifiedValue {
@ -39,7 +39,7 @@
let mut background_${name} = background_${name}::SpecifiedValue(Vec::new()); let mut background_${name} = background_${name}::SpecifiedValue(Vec::new());
% endfor % endfor
try!(input.parse_comma_separated(|input| { try!(input.parse_comma_separated(|input| {
% for name in "image position_x position_y repeat size attachment origin clip".split(): % for name in "image position repeat size attachment origin clip".split():
let mut ${name} = None; let mut ${name} = None;
% endfor % endfor
loop { loop {
@ -52,10 +52,9 @@
return Err(()) return Err(())
} }
} }
if position_x.is_none() && position_y.is_none() { if position.is_none() {
if let Ok(value) = input.try(|input| Position::parse(context, input)) { if let Ok(value) = input.try(|input| Position::parse(context, input)) {
position_x = Some(value.horizontal); position = Some(value);
position_y = Some(value.vertical);
// Parse background size, if applicable. // Parse background size, if applicable.
size = input.try(|input| { size = input.try(|input| {
@ -83,22 +82,17 @@
} }
} }
let mut any = false; let mut any = false;
% for name in "image position_x position_y repeat size attachment origin clip".split(): % for name in "image position repeat size attachment origin clip".split():
any = any || ${name}.is_some(); any = any || ${name}.is_some();
% endfor % endfor
any = any || background_color.is_some(); any = any || background_color.is_some();
if any { if any {
if position_x.is_some() || position_y.is_some() { if let Some(position) = position {
% for name in "position_x position_y".split(): background_position_x.0.push(position.horizontal);
if let Some(bg_${name}) = ${name} { background_position_y.0.push(position.vertical);
background_${name}.0.push(bg_${name});
}
% endfor
} else { } else {
% for name in "position_x position_y".split(): background_position_x.0.push(PositionComponent::zero());
background_${name}.0.push(background_${name}::single_value background_position_y.0.push(PositionComponent::zero());
::get_initial_position_value());
% endfor
} }
% for name in "image repeat size attachment origin clip".split(): % for name in "image repeat size attachment origin clip".split():
if let Some(bg_${name}) = ${name} { if let Some(bg_${name}) = ${name} {
@ -193,7 +187,7 @@
<%helpers:shorthand name="background-position" <%helpers:shorthand name="background-position"
sub_properties="background-position-x background-position-y" sub_properties="background-position-x background-position-y"
spec="https://drafts.csswg.org/css-backgrounds-4/#the-background-position"> spec="https://drafts.csswg.org/css-backgrounds-4/#the-background-position">
use properties::longhands::{background_position_x,background_position_y}; use properties::longhands::{background_position_x, background_position_y};
use values::specified::AllowQuirks; use values::specified::AllowQuirks;
use values::specified::position::Position; use values::specified::position::Position;
@ -202,18 +196,13 @@
let mut position_y = background_position_y::SpecifiedValue(Vec::new()); let mut position_y = background_position_y::SpecifiedValue(Vec::new());
let mut any = false; let mut any = false;
try!(input.parse_comma_separated(|input| { input.parse_comma_separated(|input| {
loop { let value = Position::parse_quirky(context, input, AllowQuirks::Yes)?;
if let Ok(value) = input.try(|input| Position::parse_quirky(context, input, AllowQuirks::Yes)) { position_x.0.push(value.horizontal);
position_x.0.push(value.horizontal); position_y.0.push(value.vertical);
position_y.0.push(value.vertical); any = true;
any = true;
continue
}
break
}
Ok(()) Ok(())
})); })?;
if !any { if !any {
return Err(()); return Err(());
} }

View file

@ -11,7 +11,7 @@
use properties::longhands::{mask_mode, mask_repeat, mask_clip, mask_origin, mask_composite, mask_position_x, use properties::longhands::{mask_mode, mask_repeat, mask_clip, mask_origin, mask_composite, mask_position_x,
mask_position_y}; mask_position_y};
use properties::longhands::{mask_size, mask_image}; use properties::longhands::{mask_size, mask_image};
use values::specified::position::Position; use values::specified::{Position, PositionComponent};
use parser::Parse; use parser::Parse;
impl From<mask_origin::single_value::SpecifiedValue> for mask_clip::single_value::SpecifiedValue { impl From<mask_origin::single_value::SpecifiedValue> for mask_clip::single_value::SpecifiedValue {
@ -41,7 +41,7 @@
% endfor % endfor
try!(input.parse_comma_separated(|input| { try!(input.parse_comma_separated(|input| {
% for name in "image mode position_x position_y size repeat origin clip composite".split(): % for name in "image mode position size repeat origin clip composite".split():
let mut ${name} = None; let mut ${name} = None;
% endfor % endfor
loop { loop {
@ -52,10 +52,9 @@
continue continue
} }
} }
if position_x.is_none() && position_y.is_none() { if position.is_none() {
if let Ok(value) = input.try(|input| Position::parse(context, input)) { if let Ok(value) = input.try(|input| Position::parse(context, input)) {
position_x = Some(value.horizontal); position = Some(value);
position_y = Some(value.vertical);
// Parse mask size, if applicable. // Parse mask size, if applicable.
size = input.try(|input| { size = input.try(|input| {
@ -83,21 +82,16 @@
} }
} }
let mut any = false; let mut any = false;
% for name in "image mode position_x position_y size repeat origin clip composite".split(): % for name in "image mode position size repeat origin clip composite".split():
any = any || ${name}.is_some(); any = any || ${name}.is_some();
% endfor % endfor
if any { if any {
if position_x.is_some() || position_y.is_some() { if let Some(position) = position {
% for name in "position_x position_y".split(): mask_position_x.0.push(position.horizontal);
if let Some(bg_${name}) = ${name} { mask_position_y.0.push(position.vertical);
mask_${name}.0.push(bg_${name});
}
% endfor
} else { } else {
% for name in "position_x position_y".split(): mask_position_x.0.push(PositionComponent::zero());
mask_${name}.0.push(mask_${name}::single_value mask_position_y.0.push(PositionComponent::zero());
::get_initial_position_value());
% endfor
} }
% for name in "image mode size repeat origin clip composite".split(): % for name in "image mode size repeat origin clip composite".split():
if let Some(m_${name}) = ${name} { if let Some(m_${name}) = ${name} {
@ -191,18 +185,13 @@
let mut position_y = mask_position_y::SpecifiedValue(Vec::new()); let mut position_y = mask_position_y::SpecifiedValue(Vec::new());
let mut any = false; let mut any = false;
try!(input.parse_comma_separated(|input| { input.parse_comma_separated(|input| {
loop { let value = Position::parse(context, input)?;
if let Ok(value) = input.try(|input| Position::parse(context, input)) { position_x.0.push(value.horizontal);
position_x.0.push(value.horizontal); position_y.0.push(value.vertical);
position_y.0.push(value.vertical); any = true;
any = true;
continue
}
break
}
Ok(()) Ok(())
})); })?;
if any == false { if any == false {
return Err(()); return Err(());
} }

View file

@ -10,39 +10,28 @@
use std::fmt; use std::fmt;
use style_traits::ToCss; use style_traits::ToCss;
use values::computed::LengthOrPercentage; use values::computed::LengthOrPercentage;
use values::generics::position::{Position as GenericPosition, PositionWithKeyword}; use values::generics::position::Position as GenericPosition;
use values::generics::position::HorizontalPosition as GenericHorizontalPosition;
use values::generics::position::VerticalPosition as GenericVerticalPosition;
/// The computed value of a CSS `<position>` /// The computed value of a CSS `<position>`
pub type Position = PositionWithKeyword<LengthOrPercentage>; pub type Position = GenericPosition<HorizontalPosition, VerticalPosition>;
impl Copy for Position {} /// The computed value of a CSS horizontal position.
pub type HorizontalPosition = LengthOrPercentage;
/// The computed value for `<position>` values without a keyword. /// The computed value of a CSS vertical position.
pub type OriginPosition = GenericPosition<LengthOrPercentage, LengthOrPercentage>; pub type VerticalPosition = LengthOrPercentage;
impl Copy for OriginPosition {}
impl OriginPosition {
#[inline]
/// The initial value for `perspective-origin`
pub fn center() -> OriginPosition {
GenericPosition {
horizontal: LengthOrPercentage::Percentage(0.5),
vertical: LengthOrPercentage::Percentage(0.5),
}
}
}
impl Position { impl Position {
/// `50% 50%`
#[inline]
pub fn center() -> Self {
Self::new(LengthOrPercentage::Percentage(0.5), LengthOrPercentage::Percentage(0.5))
}
/// `0% 0%`
#[inline] #[inline]
/// Construct a position at (0, 0)
pub fn zero() -> Self { pub fn zero() -> Self {
Position { Self::new(LengthOrPercentage::zero(), LengthOrPercentage::zero())
horizontal: GenericHorizontalPosition(LengthOrPercentage::zero()),
vertical: GenericVerticalPosition(LengthOrPercentage::zero()),
}
} }
} }
@ -53,29 +42,3 @@ impl ToCss for Position {
self.vertical.to_css(dest) self.vertical.to_css(dest)
} }
} }
/// The computed value of a horizontal `<position>`
pub type HorizontalPosition = GenericHorizontalPosition<LengthOrPercentage>;
impl Copy for HorizontalPosition {}
impl HorizontalPosition {
#[inline]
/// Create a zero position value.
pub fn zero() -> HorizontalPosition {
GenericHorizontalPosition(LengthOrPercentage::Percentage(0.0))
}
}
/// The computed value of a vertical `<position>`
pub type VerticalPosition = GenericVerticalPosition<LengthOrPercentage>;
impl Copy for VerticalPosition {}
impl VerticalPosition {
#[inline]
/// Create a zero position value.
pub fn zero() -> VerticalPosition {
GenericVerticalPosition(LengthOrPercentage::Percentage(0.0))
}
}

View file

@ -5,231 +5,12 @@
//! Generic types for CSS handling of specified and computed values of //! Generic types for CSS handling of specified and computed values of
//! [`position`](https://drafts.csswg.org/css-backgrounds-3/#position) //! [`position`](https://drafts.csswg.org/css-backgrounds-3/#position)
use cssparser::Parser;
use parser::{Parse, ParserContext};
use std::fmt;
use style_traits::ToCss;
use values::HasViewportPercentage; use values::HasViewportPercentage;
use values::computed::{ComputedValueAsSpecified, Context, ToComputedValue}; use values::computed::{Context, ToComputedValue};
use values::specified::{LengthOrPercentage, Percentage};
define_css_keyword_enum!{ Keyword: #[derive(Clone, Copy, Debug, PartialEq)]
"center" => Center,
"left" => Left,
"right" => Right,
"top" => Top,
"bottom" => Bottom,
"x-start" => XStart,
"x-end" => XEnd,
"y-start" => YStart,
"y-end" => YEnd
}
add_impls_for_keyword_enum!(Keyword);
impl Keyword {
#[inline]
/// The defaults for position keywords are `left` and `top` (`x-start` and `y-start` for logical).
/// This method checks whether this keyword indicates their opposite sides. See the
/// `ToComputedValue` impl on `HorizontalPosition` and `VerticalPosition` for its use case.
pub fn is_other_side(&self) -> bool {
if self.is_horizontal() || self.is_logical_x() {
matches!(*self, Keyword::Right | Keyword::XEnd)
} else {
matches!(*self, Keyword::Bottom | Keyword::YEnd)
}
}
#[inline]
/// Check whether this is a keyword for horizontal position.
pub fn is_horizontal(&self) -> bool {
matches!(*self, Keyword::Left | Keyword::Right)
}
#[inline]
/// Check whether this is a keyword for vertical position.
pub fn is_vertical(&self) -> bool {
matches!(*self, Keyword::Top | Keyword::Bottom)
}
#[inline]
/// Check whether this is a horizontal logical keyword.
pub fn is_logical_x(&self) -> bool {
matches!(*self, Keyword::XStart | Keyword::XEnd)
}
#[inline]
/// Check whether this is a vertical logical keyword.
pub fn is_logical_y(&self) -> bool {
matches!(*self, Keyword::YStart | Keyword::YEnd)
}
#[inline]
/// Check whether this is a logical keyword.
pub fn is_logical(&self) -> bool {
self.is_logical_x() || self.is_logical_y()
}
}
impl From<Keyword> for LengthOrPercentage {
fn from(val: Keyword) -> LengthOrPercentage {
match val {
Keyword::Center => LengthOrPercentage::Percentage(Percentage(0.5)),
Keyword::Left | Keyword::Top => LengthOrPercentage::Percentage(Percentage(0.0)),
Keyword::Right | Keyword::Bottom => LengthOrPercentage::Percentage(Percentage(1.0)),
// FIXME(canaltinova): Support logical keywords
Keyword::XStart | Keyword::YStart => LengthOrPercentage::Percentage(Percentage(0.0)),
Keyword::XEnd | Keyword::YEnd => LengthOrPercentage::Percentage(Percentage(1.0)),
}
}
}
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "servo", derive(HeapSizeOf))]
/// A generic type for representing horizontal or vertical `<position>` value.
pub struct PositionValue<L> {
/// Even though this is generic, it's always a `<length-percentage>` value.
pub position: Option<L>,
/// A position keyword.
pub keyword: Option<Keyword>,
}
impl<L: HasViewportPercentage> HasViewportPercentage for PositionValue<L> {
#[inline]
fn has_viewport_percentage(&self) -> bool {
self.position.as_ref().map_or(false, |pos| pos.has_viewport_percentage())
}
}
impl<L: Parse> PositionValue<L> {
/// Internal parsing function which (after parsing) checks the keyword with the
/// given function.
pub fn parse_internal<F>(context: &ParserContext, input: &mut Parser,
mut is_allowed_keyword: F) -> Result<PositionValue<L>, ()>
where F: FnMut(Keyword) -> bool
{
let (mut pos, mut keyword) = (None, None);
for _ in 0..2 {
if let Ok(l) = input.try(|i| L::parse(context, i)) {
if pos.is_some() {
return Err(())
}
pos = Some(l);
}
if let Ok(k) = input.try(Keyword::parse) {
if keyword.is_some() || !is_allowed_keyword(k) {
return Err(())
}
keyword = Some(k);
}
}
if pos.is_some() {
if let Some(Keyword::Center) = keyword {
return Err(()) // "center" and <length> is not allowed
}
} else if keyword.is_none() {
return Err(()) // at least one value is necessary
}
Ok(PositionValue {
position: pos,
keyword: keyword,
})
}
}
impl<L: ToCss> ToCss for PositionValue<L> {
fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
if let Some(keyword) = self.keyword {
keyword.to_css(dest)?;
}
if let Some(ref position) = self.position {
if self.keyword.is_some() {
dest.write_str(" ")?;
}
position.to_css(dest)?;
}
Ok(())
}
}
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "servo", derive(HeapSizeOf))]
/// A generic type for representing horizontal `<position>`
pub struct HorizontalPosition<L>(pub L);
impl<L: ToCss> ToCss for HorizontalPosition<L> {
#[inline]
fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
self.0.to_css(dest)
}
}
impl<L: HasViewportPercentage> HasViewportPercentage for HorizontalPosition<L> {
#[inline]
fn has_viewport_percentage(&self) -> bool {
self.0.has_viewport_percentage()
}
}
impl<L: Parse> Parse for HorizontalPosition<PositionValue<L>> {
#[inline]
fn parse(context: &ParserContext, input: &mut Parser) -> Result<Self, ()> {
PositionValue::parse_internal(context, input, |keyword| {
matches!{ keyword,
Keyword::Left | Keyword::Right | Keyword::Center |
Keyword::XStart | Keyword::XEnd
}
}).map(HorizontalPosition)
}
}
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "servo", derive(HeapSizeOf))]
/// A generic type for representing vertical `<position>`
pub struct VerticalPosition<L>(pub L);
impl<L: ToCss> ToCss for VerticalPosition<L> {
#[inline]
fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
self.0.to_css(dest)
}
}
impl<L: HasViewportPercentage> HasViewportPercentage for VerticalPosition<L> {
#[inline]
fn has_viewport_percentage(&self) -> bool {
self.0.has_viewport_percentage()
}
}
impl<L: Parse> Parse for VerticalPosition<PositionValue<L>> {
#[inline]
fn parse(context: &ParserContext, input: &mut Parser) -> Result<Self, ()> {
PositionValue::parse_internal(context, input, |keyword| {
matches!{ keyword,
Keyword::Top | Keyword::Bottom | Keyword::Center |
Keyword::YStart | Keyword::YEnd
}
}).map(VerticalPosition)
}
}
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "servo", derive(HeapSizeOf))] #[cfg_attr(feature = "servo", derive(HeapSizeOf))]
/// A generic type for representing a CSS [position](https://drafts.csswg.org/css-values/#position). /// A generic type for representing a CSS [position](https://drafts.csswg.org/css-values/#position).
///
/// Note that the horizontal and vertical positions aren't really different types.
/// They're just unit struct wrappers over `LengthOrPercentage`. They should be different
/// because they allow different keywords (for e.g., vertical position doesn't allow
/// `right` or `left` keywords and vice versa).
pub struct Position<H, V> { pub struct Position<H, V> {
/// The horizontal component of position. /// The horizontal component of position.
pub horizontal: H, pub horizontal: H,
@ -237,8 +18,15 @@ pub struct Position<H, V> {
pub vertical: V, pub vertical: V,
} }
/// A generic type for representing positions with keywords. impl<H, V> Position<H, V> {
pub type PositionWithKeyword<L> = Position<HorizontalPosition<L>, VerticalPosition<L>>; /// Returns a new position.
pub fn new(horizontal: H, vertical: V) -> Self {
Self {
horizontal: horizontal,
vertical: vertical,
}
}
}
impl<H: HasViewportPercentage, V: HasViewportPercentage> HasViewportPercentage for Position<H, V> { impl<H: HasViewportPercentage, V: HasViewportPercentage> HasViewportPercentage for Position<H, V> {
#[inline] #[inline]
@ -247,42 +35,9 @@ impl<H: HasViewportPercentage, V: HasViewportPercentage> HasViewportPercentage f
} }
} }
impl<L: ToCss> ToCss for Position<L, L> {
fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
self.horizontal.to_css(dest)?;
dest.write_str(" ")?;
self.vertical.to_css(dest)
}
}
impl<L: ToCss> ToCss for PositionWithKeyword<PositionValue<L>> {
fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
macro_rules! to_css_with_keyword {
($pos:expr, $default:expr) => {
$pos.keyword.unwrap_or($default).to_css(dest)?;
if let Some(ref position) = $pos.position {
dest.write_str(" ")?;
position.to_css(dest)?;
}
}
}
if (self.horizontal.0.keyword.is_some() && self.horizontal.0.position.is_some()) ||
(self.vertical.0.keyword.is_some() && self.vertical.0.position.is_some()) {
to_css_with_keyword!(self.horizontal.0, Keyword::Left);
dest.write_str(" ")?;
to_css_with_keyword!(self.vertical.0, Keyword::Top);
return Ok(())
}
self.horizontal.to_css(dest)?;
dest.write_str(" ")?;
self.vertical.to_css(dest)
}
}
impl<H: ToComputedValue, V: ToComputedValue> ToComputedValue for Position<H, V> { impl<H: ToComputedValue, V: ToComputedValue> ToComputedValue for Position<H, V> {
type ComputedValue = Position<H::ComputedValue, V::ComputedValue>; type ComputedValue = Position<<H as ToComputedValue>::ComputedValue,
<V as ToComputedValue>::ComputedValue>;
#[inline] #[inline]
fn to_computed_value(&self, context: &Context) -> Self::ComputedValue { fn to_computed_value(&self, context: &Context) -> Self::ComputedValue {
@ -294,7 +49,7 @@ impl<H: ToComputedValue, V: ToComputedValue> ToComputedValue for Position<H, V>
#[inline] #[inline]
fn from_computed_value(computed: &Self::ComputedValue) -> Self { fn from_computed_value(computed: &Self::ComputedValue) -> Self {
Position { Self {
horizontal: ToComputedValue::from_computed_value(&computed.horizontal), horizontal: ToComputedValue::from_computed_value(&computed.horizontal),
vertical: ToComputedValue::from_computed_value(&computed.vertical), vertical: ToComputedValue::from_computed_value(&computed.vertical),
} }

View file

@ -11,6 +11,7 @@ use cssparser::Parser;
use parser::{Parse, ParserContext}; use parser::{Parse, ParserContext};
use properties::shorthands::parse_four_sides; use properties::shorthands::parse_four_sides;
use std::ascii::AsciiExt; use std::ascii::AsciiExt;
use std::borrow::Cow;
use std::fmt; use std::fmt;
use style_traits::ToCss; use style_traits::ToCss;
use values::HasViewportPercentage; use values::HasViewportPercentage;
@ -19,8 +20,8 @@ use values::computed::basic_shape as computed_basic_shape;
use values::generics::BorderRadiusSize; use values::generics::BorderRadiusSize;
use values::generics::basic_shape::{BorderRadius as GenericBorderRadius, ShapeRadius as GenericShapeRadius}; use values::generics::basic_shape::{BorderRadius as GenericBorderRadius, ShapeRadius as GenericShapeRadius};
use values::generics::basic_shape::{InsetRect as GenericInsetRect, Polygon as GenericPolygon, ShapeSource}; use values::generics::basic_shape::{InsetRect as GenericInsetRect, Polygon as GenericPolygon, ShapeSource};
use values::specified::{LengthOrPercentage, Percentage}; use values::specified::{LengthOrPercentage, Percentage, Position, PositionComponent};
use values::specified::position::{Keyword, Position}; use values::specified::position::Side;
/// The specified value used by `clip-path` /// The specified value used by `clip-path`
pub type ShapeWithGeometryBox = ShapeSource<BasicShape, GeometryBox>; pub type ShapeWithGeometryBox = ShapeSource<BasicShape, GeometryBox>;
@ -138,75 +139,62 @@ impl Parse for InsetRect {
fn serialize_basicshape_position<W>(position: &Position, dest: &mut W) -> fmt::Result fn serialize_basicshape_position<W>(position: &Position, dest: &mut W) -> fmt::Result
where W: fmt::Write where W: fmt::Write
{ {
// 0 length should be replaced with 0% fn to_keyword_and_lop<S>(component: &PositionComponent<S>) -> (S, Cow<LengthOrPercentage>)
fn replace_with_percent(input: LengthOrPercentage) -> LengthOrPercentage { where S: Copy + Side
match input { {
LengthOrPercentage::Length(ref l) if l.is_zero() => match *component {
LengthOrPercentage::Percentage(Percentage(0.0)), PositionComponent::Center => {
_ => input (S::start(), Cow::Owned(LengthOrPercentage::Percentage(Percentage(0.5))))
}
}
// keyword-percentage pairs can be folded into a single percentage
fn fold_keyword(keyword: Option<Keyword>,
length: Option<LengthOrPercentage>) -> Option<LengthOrPercentage> {
let is_length_none = length.is_none();
let pc = match length.map(replace_with_percent) {
Some(LengthOrPercentage::Percentage(pc)) => pc,
None => Percentage(0.0), // unspecified length = 0%
_ => return None
};
let percent = match keyword {
Some(Keyword::Center) => {
assert!(is_length_none); // center cannot pair with lengths
Percentage(0.5)
}, },
Some(Keyword::Left) | Some(Keyword::Top) | None => pc, PositionComponent::Side(keyword, None) => {
Some(Keyword::Right) | Some(Keyword::Bottom) => Percentage(1.0 - pc.0), // left | top => 0%
_ => return None, // right | bottom => 100%
}; let p = if keyword.is_start() { 0. } else { 1. };
(S::start(), Cow::Owned(LengthOrPercentage::Percentage(Percentage(p))))
Some(LengthOrPercentage::Percentage(percent)) },
PositionComponent::Side(keyword, Some(ref lop)) if !keyword.is_start() => {
if let LengthOrPercentage::Percentage(p) = *to_non_zero_length(lop) {
(S::start(), Cow::Owned(LengthOrPercentage::Percentage(Percentage(1. - p.0))))
} else {
(keyword, Cow::Borrowed(lop))
}
},
PositionComponent::Length(ref lop) |
PositionComponent::Side(_, Some(ref lop)) => {
(S::start(), to_non_zero_length(lop))
},
}
} }
fn serialize_position_pair<W>(x: LengthOrPercentage, y: LengthOrPercentage, fn to_non_zero_length(lop: &LengthOrPercentage) -> Cow<LengthOrPercentage> {
dest: &mut W) -> fmt::Result where W: fmt::Write { match *lop {
replace_with_percent(x).to_css(dest)?; LengthOrPercentage::Length(ref l) if l.is_zero() => {
dest.write_str(" ")?; Cow::Owned(LengthOrPercentage::Percentage(Percentage(0.)))
replace_with_percent(y).to_css(dest) },
} _ => {
Cow::Borrowed(lop)
match (position.horizontal.0.keyword, position.horizontal.0.position.clone(),
position.vertical.0.keyword, position.vertical.0.position.clone()) {
(Some(hk), None, Some(vk), None) => {
// two keywords: serialize as two lengths
serialize_position_pair(hk.into(), vk.into(), 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.clone()),
fold_keyword(vk, vp.clone())) {
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));
hk.unwrap_or(Keyword::Left).to_css(dest)?;
dest.write_str(" ")?;
replace_with_percent(hp.unwrap_or(zero.clone())).to_css(dest)?;
dest.write_str(" ")?;
vk.unwrap_or(Keyword::Top).to_css(dest)?;
dest.write_str(" ")?;
replace_with_percent(vp.unwrap_or(zero)).to_css(dest)
} }
} }
} }
fn write_pair<A, B, W>(a: &A, b: &B, dest: &mut W) -> fmt::Result
where A: ToCss, B: ToCss, W: fmt::Write
{
a.to_css(dest)?;
dest.write_str(" ")?;
b.to_css(dest)
}
let (x_pos, x_lop) = to_keyword_and_lop(&position.horizontal);
let (y_pos, y_lop) = to_keyword_and_lop(&position.vertical);
if x_pos.is_start() && y_pos.is_start() {
return write_pair(&*x_lop, &*y_lop, dest);
}
write_pair(&x_pos, &*x_lop, dest)?;
dest.write_str(" ")?;
write_pair(&y_pos, &*y_lop, dest)
} }
#[derive(Clone, PartialEq, Debug)] #[derive(Clone, PartialEq, Debug)]

View file

@ -36,7 +36,7 @@ pub use self::length::{FontRelativeLength, ViewportPercentageLength, CharacterWi
pub use self::length::{Percentage, LengthOrNone, LengthOrNumber, LengthOrPercentage, LengthOrPercentageOrAuto}; pub use self::length::{Percentage, LengthOrNone, LengthOrNumber, LengthOrPercentage, LengthOrPercentageOrAuto};
pub use self::length::{LengthOrPercentageOrNone, LengthOrPercentageOrAutoOrContent, NoCalcLength}; pub use self::length::{LengthOrPercentageOrNone, LengthOrPercentageOrAutoOrContent, NoCalcLength};
pub use self::length::{MaxLength, MinLength}; pub use self::length::{MaxLength, MinLength};
pub use self::position::{HorizontalPosition, Position, VerticalPosition}; pub use self::position::{Position, PositionComponent};
#[cfg(feature = "gecko")] #[cfg(feature = "gecko")]
pub mod align; pub mod align;

View file

@ -7,278 +7,297 @@
//! //!
//! [position]: https://drafts.csswg.org/css-backgrounds-3/#position //! [position]: https://drafts.csswg.org/css-backgrounds-3/#position
use app_units::Au;
use cssparser::Parser; use cssparser::Parser;
use parser::{Parse, ParserContext}; use parser::{Parse, ParserContext};
use properties::longhands::parse_origin; use std::fmt;
use std::mem; use style_traits::ToCss;
use values::Either; use values::HasViewportPercentage;
use values::computed::{CalcLengthOrPercentage, Context}; use values::computed::{CalcLengthOrPercentage, ComputedValueAsSpecified, Context};
use values::computed::{LengthOrPercentage as ComputedLengthOrPercentage, ToComputedValue}; use values::computed::{LengthOrPercentage as ComputedLengthOrPercentage, ToComputedValue};
use values::computed::position as computed_position; use values::generics::position::Position as GenericPosition;
use values::generics::position::{Position as GenericPosition, PositionValue, PositionWithKeyword};
use values::generics::position::HorizontalPosition as GenericHorizontalPosition;
use values::generics::position::VerticalPosition as GenericVerticalPosition;
use values::specified::{AllowQuirks, LengthOrPercentage, Percentage}; use values::specified::{AllowQuirks, LengthOrPercentage, Percentage};
pub use values::generics::position::Keyword;
/// The specified value of a CSS `<position>` /// The specified value of a CSS `<position>`
pub type Position = PositionWithKeyword<PositionValue<LengthOrPercentage>>; pub type Position = GenericPosition<HorizontalPosition, VerticalPosition>;
/// The specified value for `<position>` values without a keyword. /// The specified value of a horizontal position.
pub type OriginPosition = GenericPosition<LengthOrPercentage, LengthOrPercentage>; pub type HorizontalPosition = PositionComponent<X>;
impl Parse for OriginPosition { /// The specified value of a vertical position.
fn parse(context: &ParserContext, input: &mut Parser) -> Result<Self, ()> { pub type VerticalPosition = PositionComponent<Y>;
let result = parse_origin(context, input)?;
match result.depth { /// The specified value of a component of a CSS `<position>`.
Some(_) => Err(()), #[cfg_attr(feature = "servo", derive(HeapSizeOf))]
None => Ok(GenericPosition { #[derive(Clone, Debug, PartialEq)]
horizontal: result.horizontal.unwrap_or(LengthOrPercentage::Percentage(Percentage(0.5))), pub enum PositionComponent<S> {
vertical: result.vertical.unwrap_or(LengthOrPercentage::Percentage(Percentage(0.5))), /// `center`
}) Center,
} /// `<lop>`
} Length(LengthOrPercentage),
/// `<side> <lop>?`
Side(S, Option<LengthOrPercentage>),
} }
type PositionComponent = Either<LengthOrPercentage, Keyword>; define_css_keyword_enum! { X:
"left" => Left,
impl Position { "right" => Right,
/// Create a new position value from either a length or a keyword.
pub fn from_components(mut first_position: Option<PositionComponent>,
mut second_position: Option<PositionComponent>,
first_keyword: Option<PositionComponent>,
second_keyword: Option<PositionComponent>) -> Result<Position, ()> {
// Unwrap for checking if values are at right place.
let first_key = first_keyword.clone().unwrap_or(Either::Second(Keyword::Left));
let second_key = second_keyword.clone().unwrap_or(Either::Second(Keyword::Top));
let (horiz_keyword, vert_keyword) = match (&first_key, &second_key) {
// Check if a position is specified after center keyword.
(&Either::Second(Keyword::Center), _) if first_position.is_some() => return Err(()),
(_, &Either::Second(Keyword::Center)) if second_position.is_some() => return Err(()),
// Check first and second keywords for both 2 and 4 value positions.
// FIXME(canaltinova): Allow logical keywords for Position. They are not in current spec yet.
(&Either::Second(k), _) if k.is_logical() => return Err(()),
(_, &Either::Second(k)) if k.is_logical() => return Err(()),
// Don't allow two vertical keywords or two horizontal keywords.
(&Either::Second(k1), &Either::Second(k2))
if (k1.is_horizontal() && k2.is_horizontal()) || (k1.is_vertical() && k2.is_vertical()) =>
return Err(()),
// Also don't allow <length-percentage> values in the wrong position
(&Either::First(_), &Either::Second(k)) if k.is_horizontal() => return Err(()),
(&Either::Second(k), &Either::First(_)) if k.is_vertical() => return Err(()),
// Swap if both are keywords and vertical precedes horizontal.
(&Either::Second(k1), &Either::Second(k2))
if (k1.is_vertical() && k2.is_horizontal()) || (k1.is_vertical() && k2 == Keyword::Center) ||
(k1 == Keyword::Center && k2.is_horizontal()) => {
mem::swap(&mut first_position, &mut second_position);
(second_keyword, first_keyword)
},
// By default, horizontal is first.
_ => (first_keyword, second_keyword),
};
let (mut h_pos, mut h_key, mut v_pos, mut v_key) = (None, None, None, None);
if let Some(Either::First(l)) = first_position {
h_pos = Some(l);
}
if let Some(Either::First(l)) = second_position {
v_pos = Some(l);
}
if let Some(Either::Second(k)) = horiz_keyword {
h_key = Some(k);
}
if let Some(Either::Second(k)) = vert_keyword {
v_key = Some(k);
}
Ok(Position {
horizontal: GenericHorizontalPosition(PositionValue {
keyword: h_key,
position: h_pos,
}),
vertical: GenericVerticalPosition(PositionValue {
keyword: v_key,
position: v_pos,
}),
})
}
/// Returns a "centered" position, as in "center center".
pub fn center() -> Position {
Position {
horizontal: GenericHorizontalPosition(PositionValue {
keyword: Some(Keyword::Center),
position: None,
}),
vertical: GenericVerticalPosition(PositionValue {
keyword: Some(Keyword::Center),
position: None,
}),
}
}
} }
add_impls_for_keyword_enum!(X);
define_css_keyword_enum! { Y:
"top" => Top,
"bottom" => Bottom,
}
add_impls_for_keyword_enum!(Y);
impl Parse for Position { impl Parse for Position {
fn parse(context: &ParserContext, input: &mut Parser) -> Result<Self, ()> { fn parse(context: &ParserContext, input: &mut Parser) -> Result<Self, ()> {
Position::parse_quirky(context, input, AllowQuirks::No) Self::parse_quirky(context, input, AllowQuirks::No)
} }
} }
impl Position { impl Position {
/// Parses, with quirks. /// Parses a `<position>`, with quirks.
pub fn parse_quirky(context: &ParserContext, pub fn parse_quirky(context: &ParserContext,
input: &mut Parser, input: &mut Parser,
allow_quirks: AllowQuirks) allow_quirks: AllowQuirks)
-> Result<Self, ()> { -> Result<Self, ()> {
let first = input.try(|i| PositionComponent::parse_quirky(context, i, allow_quirks))?; match input.try(|i| PositionComponent::parse_quirky(context, i, allow_quirks)) {
let second = input.try(|i| PositionComponent::parse_quirky(context, i, allow_quirks)) Ok(x_pos @ PositionComponent::Center) => {
.unwrap_or(Either::Second(Keyword::Center)); if let Ok(y_pos) = input.try(|i| PositionComponent::parse_quirky(context, i, allow_quirks)) {
return Ok(Self::new(x_pos, y_pos));
if let Ok(third) = input.try(|i| PositionComponent::parse_quirky(context, i, allow_quirks)) {
// There's a 3rd value.
if let Ok(fourth) = input.try(|i| PositionComponent::parse_quirky(context, i, allow_quirks)) {
// There's a 4th value.
Position::from_components(Some(second), Some(fourth), Some(first), Some(third))
} else {
// For 3 value background position, there are several options.
if let Either::First(_) = first {
return Err(()) // <length-percentage> must be preceded by <keyword>
} }
let x_pos = input
// only 3 values. .try(|i| PositionComponent::parse_quirky(context, i, allow_quirks))
match (&second, &third) { .unwrap_or(x_pos);
(&Either::First(_), &Either::First(_)) => Err(()), let y_pos = PositionComponent::Center;
// "keyword length keyword" return Ok(Self::new(x_pos, y_pos));
(&Either::First(_), _) => Position::from_components(Some(second), None, },
Some(first), Some(third)), Ok(PositionComponent::Side(x_keyword, lop)) => {
// "keyword keyword length" if input.try(|i| i.expect_ident_matching("center")).is_ok() {
_ => Position::from_components(None, Some(third), Some(first), Some(second)), let x_pos = PositionComponent::Side(x_keyword, lop);
} let y_pos = PositionComponent::Center;
} return Ok(Self::new(x_pos, y_pos));
} else { }
// only 2 values. if let Ok(y_keyword) = input.try(Y::parse) {
match (&first, &second) { let y_lop = input.try(|i| LengthOrPercentage::parse_quirky(context, i, allow_quirks)).ok();
(&Either::First(_), &Either::First(_)) => let x_pos = PositionComponent::Side(x_keyword, lop);
Position::from_components(Some(first), Some(second), None, None), let y_pos = PositionComponent::Side(y_keyword, y_lop);
(&Either::First(_), &Either::Second(_)) => return Ok(Self::new(x_pos, y_pos));
Position::from_components(Some(first), None, None, Some(second)), }
(&Either::Second(_), &Either::First(_)) => let x_pos = PositionComponent::Side(x_keyword, None);
Position::from_components(None, Some(second), Some(first), None), let y_pos = lop.map_or(PositionComponent::Center, PositionComponent::Length);
(&Either::Second(_), &Either::Second(_)) => return Ok(Self::new(x_pos, y_pos));
Position::from_components(None, None, Some(first), Some(second)), },
} Ok(x_pos @ PositionComponent::Length(_)) => {
} if let Ok(y_keyword) = input.try(Y::parse) {
} let y_pos = PositionComponent::Side(y_keyword, None);
} return Ok(Self::new(x_pos, y_pos));
}
impl PositionComponent { if let Ok(y_lop) = input.try(|i| LengthOrPercentage::parse_quirky(context, i, allow_quirks)) {
/// Parses, with quirks. let y_pos = PositionComponent::Length(y_lop);
fn parse_quirky(context: &ParserContext, return Ok(Self::new(x_pos, y_pos));
input: &mut Parser, }
allow_quirks: AllowQuirks) -> Result<Self, ()> { let y_pos = PositionComponent::Center;
input.try(|i| LengthOrPercentage::parse_quirky(context, i, allow_quirks)) let _ = input.try(|i| i.expect_ident_matching("center"));
.map(Either::First) return Ok(Self::new(x_pos, y_pos));
.or_else(|()| input.try(Keyword::parse).map(Either::Second)) },
} Err(_) => {},
} }
let y_keyword = Y::parse(input)?;
impl PositionValue<LengthOrPercentage> { let lop_and_x_pos: Result<_, ()> = input.try(|i| {
/// Generic function for the computed value of a position. let y_lop = i.try(|i| LengthOrPercentage::parse_quirky(context, i, allow_quirks)).ok();
fn computed_value(&self, context: &Context) -> ComputedLengthOrPercentage { if let Ok(x_keyword) = i.try(X::parse) {
match self.keyword { let x_lop = i.try(|i| LengthOrPercentage::parse_quirky(context, i, allow_quirks)).ok();
Some(Keyword::Center) => ComputedLengthOrPercentage::Percentage(0.5), let x_pos = PositionComponent::Side(x_keyword, x_lop);
Some(k) if k.is_other_side() => match self.position { return Ok((y_lop, x_pos));
Some(ref x) => { }
let (length, percentage) = match *x { i.expect_ident_matching("center")?;
LengthOrPercentage::Percentage(Percentage(y)) => (Au(0), Some(1.0 - y)), let x_pos = PositionComponent::Center;
LengthOrPercentage::Length(ref y) => (-y.to_computed_value(context), Some(1.0)), Ok((y_lop, x_pos))
_ => (Au(0), None), });
}; if let Ok((y_lop, x_pos)) = lop_and_x_pos {
let y_pos = PositionComponent::Side(y_keyword, y_lop);
ComputedLengthOrPercentage::Calc(CalcLengthOrPercentage { return Ok(Self::new(x_pos, y_pos));
length: length, }
percentage: percentage let x_pos = PositionComponent::Center;
}) let y_pos = PositionComponent::Side(y_keyword, None);
}, Ok(Self::new(x_pos, y_pos))
None => ComputedLengthOrPercentage::Percentage(1.0), }
/// `center center`
#[inline]
pub fn center() -> Self {
Self::new(PositionComponent::Center, PositionComponent::Center)
}
}
impl ToCss for Position {
fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
match (&self.horizontal, &self.vertical) {
(x_pos @ &PositionComponent::Side(_, Some(_)), &PositionComponent::Length(ref y_lop)) => {
x_pos.to_css(dest)?;
dest.write_str(" top ")?;
y_lop.to_css(dest)
},
(&PositionComponent::Length(ref x_lop), y_pos @ &PositionComponent::Side(_, Some(_))) => {
dest.write_str("left ")?;
x_lop.to_css(dest)?;
dest.write_str(" ")?;
y_pos.to_css(dest)
},
(x_pos, y_pos) => {
x_pos.to_css(dest)?;
dest.write_str(" ")?;
y_pos.to_css(dest)
}, },
_ => self.position.as_ref().map(|l| l.to_computed_value(context))
.unwrap_or(ComputedLengthOrPercentage::Percentage(0.0)),
} }
} }
} }
/// The specified value of horizontal `<position>` impl<S> HasViewportPercentage for PositionComponent<S> {
pub type HorizontalPosition = GenericHorizontalPosition<PositionValue<LengthOrPercentage>>; fn has_viewport_percentage(&self) -> bool {
match *self {
impl ToComputedValue for HorizontalPosition { PositionComponent::Length(ref lop) |
type ComputedValue = computed_position::HorizontalPosition; PositionComponent::Side(_, Some(ref lop)) => {
lop.has_viewport_percentage()
#[inline] },
fn to_computed_value(&self, context: &Context) -> computed_position::HorizontalPosition { _ => false,
GenericHorizontalPosition(self.0.computed_value(context)) }
}
#[inline]
fn from_computed_value(computed: &computed_position::HorizontalPosition) -> HorizontalPosition {
GenericHorizontalPosition(PositionValue {
keyword: None,
position: Some(ToComputedValue::from_computed_value(&computed.0)),
})
} }
} }
impl HorizontalPosition { impl<S: Parse> Parse for PositionComponent<S> {
#[inline] fn parse(context: &ParserContext, input: &mut Parser) -> Result<Self, ()> {
/// Initial specified value for vertical position (`top` keyword). Self::parse_quirky(context, input, AllowQuirks::No)
pub fn left() -> HorizontalPosition {
GenericHorizontalPosition(PositionValue {
keyword: Some(Keyword::Left),
position: None,
})
} }
} }
impl<S: Parse> PositionComponent<S> {
/// The specified value of vertical `<position>` /// Parses a component of a CSS position, with quirks.
pub type VerticalPosition = GenericVerticalPosition<PositionValue<LengthOrPercentage>>; pub fn parse_quirky(context: &ParserContext,
input: &mut Parser,
impl ToComputedValue for VerticalPosition { allow_quirks: AllowQuirks)
type ComputedValue = computed_position::VerticalPosition; -> Result<Self, ()> {
if input.try(|i| i.expect_ident_matching("center")).is_ok() {
#[inline] return Ok(PositionComponent::Center);
fn to_computed_value(&self, context: &Context) -> computed_position::VerticalPosition { }
GenericVerticalPosition(self.0.computed_value(context)) if let Ok(lop) = input.try(|i| LengthOrPercentage::parse_quirky(context, i, allow_quirks)) {
} return Ok(PositionComponent::Length(lop));
}
#[inline] let keyword = S::parse(context, input)?;
fn from_computed_value(computed: &computed_position::VerticalPosition) -> VerticalPosition { let lop = input.try(|i| LengthOrPercentage::parse_quirky(context, i, allow_quirks)).ok();
GenericVerticalPosition(PositionValue { Ok(PositionComponent::Side(keyword, lop))
keyword: None,
position: Some(ToComputedValue::from_computed_value(&computed.0)),
})
} }
} }
impl VerticalPosition { impl<S> PositionComponent<S> {
#[inline] /// `0%`
/// Initial specified value for vertical position (`top` keyword). pub fn zero() -> Self {
pub fn top() -> VerticalPosition { PositionComponent::Length(LengthOrPercentage::Percentage(Percentage(0.)))
GenericVerticalPosition(PositionValue { }
keyword: Some(Keyword::Top), }
position: None,
}) impl<S: ToCss> ToCss for PositionComponent<S> {
fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
match *self {
PositionComponent::Center => {
dest.write_str("center")
},
PositionComponent::Length(ref lop) => {
lop.to_css(dest)
},
PositionComponent::Side(ref keyword, ref lop) => {
keyword.to_css(dest)?;
if let Some(ref lop) = *lop {
dest.write_str(" ")?;
lop.to_css(dest)?;
}
Ok(())
},
}
}
}
impl<S: Side> ToComputedValue for PositionComponent<S> {
type ComputedValue = ComputedLengthOrPercentage;
fn to_computed_value(&self, context: &Context) -> Self::ComputedValue {
match *self {
PositionComponent::Center => {
ComputedLengthOrPercentage::Percentage(0.5)
},
PositionComponent::Side(ref keyword, None) => {
let p = if keyword.is_start() { 0. } else { 1. };
ComputedLengthOrPercentage::Percentage(p)
},
PositionComponent::Side(ref keyword, Some(ref length)) if !keyword.is_start() => {
match length.to_computed_value(context) {
ComputedLengthOrPercentage::Length(length) => {
ComputedLengthOrPercentage::Calc(CalcLengthOrPercentage {
length: -length,
percentage: Some(1.0),
})
},
ComputedLengthOrPercentage::Percentage(p) => {
ComputedLengthOrPercentage::Percentage(1.0 - p)
},
ComputedLengthOrPercentage::Calc(calc) => {
ComputedLengthOrPercentage::Calc(CalcLengthOrPercentage {
length: -calc.length,
percentage: Some(1.0 - calc.percentage.unwrap_or(0.)),
})
},
}
},
PositionComponent::Side(_, Some(ref length)) |
PositionComponent::Length(ref length) => {
length.to_computed_value(context)
},
}
}
fn from_computed_value(computed: &Self::ComputedValue) -> Self {
PositionComponent::Length(ToComputedValue::from_computed_value(computed))
}
}
impl<S: Side> PositionComponent<S> {
/// The initial specified value of a position component, i.e. the start side.
pub fn initial_specified_value() -> Self {
PositionComponent::Side(S::start(), None)
}
}
/// Represents a side, either horizontal or vertical, of a CSS position.
pub trait Side {
/// Returns the start side.
fn start() -> Self;
/// Returns whether this side is the start side.
fn is_start(&self) -> bool;
}
impl Side for X {
#[inline]
fn start() -> Self {
X::Left
}
#[inline]
fn is_start(&self) -> bool {
*self == X::Left
}
}
impl Side for Y {
#[inline]
fn start() -> Self {
Y::Top
}
#[inline]
fn is_start(&self) -> bool {
*self == Y::Top
} }
} }

View file

@ -124,7 +124,7 @@ fn test_circle() {
assert_roundtrip_basicshape!(Circle::parse, "circle(at right 5% bottom 0px)", assert_roundtrip_basicshape!(Circle::parse, "circle(at right 5% bottom 0px)",
"circle(at 95% 100%)"); "circle(at 95% 100%)");
assert_roundtrip_basicshape!(Circle::parse, "circle(at right 5% bottom 1px)", assert_roundtrip_basicshape!(Circle::parse, "circle(at right 5% bottom 1px)",
"circle(at right 5% bottom 1px)"); "circle(at left 95% bottom 1px)");
assert!(parse(Circle::parse, "circle(at 5% bottom 1px)").is_err()); assert!(parse(Circle::parse, "circle(at 5% bottom 1px)").is_err());
assert!(parse(Circle::parse, "circle(at top 40%)").is_err()); assert!(parse(Circle::parse, "circle(at top 40%)").is_err());

View file

@ -20,6 +20,10 @@ fn parse<T, F: Fn(&ParserContext, &mut Parser) -> Result<T, ()>>(f: F, s: &str)
f(&context, &mut parser) f(&context, &mut parser)
} }
fn parse_entirely<T, F: Fn(&ParserContext, &mut Parser) -> Result<T, ()>>(f: F, s: &str) -> Result<T, ()> {
parse(|context, parser| parser.parse_entirely(|p| f(context, p)), s)
}
// This is a macro so that the file/line information // This is a macro so that the file/line information
// is preserved in the panic // is preserved in the panic
macro_rules! assert_roundtrip_with_context { macro_rules! assert_roundtrip_with_context {

View file

@ -2,7 +2,7 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this * License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
use parsing::parse; use parsing::{parse, parse_entirely};
use style::parser::Parse; use style::parser::Parse;
use style::values::specified::position::*; use style::values::specified::position::*;
use style_traits::ToCss; use style_traits::ToCss;
@ -28,8 +28,8 @@ fn test_position() {
assert_roundtrip_with_context!(Position::parse, "right 10%", "right 10%"); assert_roundtrip_with_context!(Position::parse, "right 10%", "right 10%");
// Only keywords can be reordered // Only keywords can be reordered
assert!(parse(Position::parse, "top 40%").is_err()); assert!(parse_entirely(Position::parse, "top 40%").is_err());
assert!(parse(Position::parse, "40% left").is_err()); assert!(parse_entirely(Position::parse, "40% left").is_err());
// 3 and 4 value serialization // 3 and 4 value serialization
assert_roundtrip_with_context!(Position::parse, "left 10px top 15px", "left 10px top 15px"); assert_roundtrip_with_context!(Position::parse, "left 10px top 15px", "left 10px top 15px");
@ -46,31 +46,31 @@ fn test_position() {
assert_roundtrip_with_context!(Position::parse, "center bottom 10px", "center bottom 10px"); assert_roundtrip_with_context!(Position::parse, "center bottom 10px", "center bottom 10px");
// Invalid 3 value positions // Invalid 3 value positions
assert!(parse(Position::parse, "20px 30px 20px").is_err()); assert!(parse_entirely(Position::parse, "20px 30px 20px").is_err());
assert!(parse(Position::parse, "top 30px 20px").is_err()); assert!(parse_entirely(Position::parse, "top 30px 20px").is_err());
assert!(parse(Position::parse, "50% bottom 20%").is_err()); assert!(parse_entirely(Position::parse, "50% bottom 20%").is_err());
// Only horizontal and vertical keywords can have positions // Only horizontal and vertical keywords can have positions
assert!(parse(Position::parse, "center 10px left 15px").is_err()); assert!(parse_entirely(Position::parse, "center 10px left 15px").is_err());
assert!(parse(Position::parse, "center 10px 15px").is_err()); assert!(parse_entirely(Position::parse, "center 10px 15px").is_err());
assert!(parse(Position::parse, "center 10px bottom").is_err()); assert!(parse_entirely(Position::parse, "center 10px bottom").is_err());
// "Horizontal Horizontal" or "Vertical Vertical" positions cause error // "Horizontal Horizontal" or "Vertical Vertical" positions cause error
assert!(parse(Position::parse, "left right").is_err()); assert!(parse_entirely(Position::parse, "left right").is_err());
assert!(parse(Position::parse, "left 10px right").is_err()); assert!(parse_entirely(Position::parse, "left 10px right").is_err());
assert!(parse(Position::parse, "left 10px right 15%").is_err()); assert!(parse_entirely(Position::parse, "left 10px right 15%").is_err());
assert!(parse(Position::parse, "top bottom").is_err()); assert!(parse_entirely(Position::parse, "top bottom").is_err());
assert!(parse(Position::parse, "top 10px bottom").is_err()); assert!(parse_entirely(Position::parse, "top 10px bottom").is_err());
assert!(parse(Position::parse, "top 10px bottom 15%").is_err()); assert!(parse_entirely(Position::parse, "top 10px bottom 15%").is_err());
// Logical keywords are not supported in Position yet // Logical keywords are not supported in Position yet.
assert!(parse(Position::parse, "x-start").is_err()); assert!(parse(Position::parse, "x-start").is_err());
assert!(parse(Position::parse, "y-end").is_err()); assert!(parse(Position::parse, "y-end").is_err());
assert!(parse(Position::parse, "x-start y-end").is_err()); assert!(parse(Position::parse, "x-start y-end").is_err());
assert!(parse(Position::parse, "x-end 10px").is_err()); assert!(parse(Position::parse, "x-end 10px").is_err());
assert!(parse(Position::parse, "y-start 20px").is_err()); assert!(parse(Position::parse, "y-start 20px").is_err());
assert!(parse(Position::parse, "x-start bottom 10%").is_err()); assert!(parse(Position::parse, "x-start bottom 10%").is_err());
assert!(parse(Position::parse, "left y-start 10%").is_err()); assert!(parse_entirely(Position::parse, "left y-start 10%").is_err());
assert!(parse(Position::parse, "x-start 20px y-end 10%").is_err()); assert!(parse(Position::parse, "x-start 20px y-end 10%").is_err());
} }
@ -82,29 +82,31 @@ fn test_horizontal_position() {
assert_roundtrip_with_context!(HorizontalPosition::parse, "center", "center"); assert_roundtrip_with_context!(HorizontalPosition::parse, "center", "center");
assert_roundtrip_with_context!(HorizontalPosition::parse, "left", "left"); assert_roundtrip_with_context!(HorizontalPosition::parse, "left", "left");
assert_roundtrip_with_context!(HorizontalPosition::parse, "right", "right"); assert_roundtrip_with_context!(HorizontalPosition::parse, "right", "right");
assert_roundtrip_with_context!(HorizontalPosition::parse, "x-start", "x-start");
assert_roundtrip_with_context!(HorizontalPosition::parse, "x-end", "x-end");
// Two value serializations. // Two value serializations.
assert_roundtrip_with_context!(HorizontalPosition::parse, "right 10px", "right 10px"); assert_roundtrip_with_context!(HorizontalPosition::parse, "right 10px", "right 10px");
assert_roundtrip_with_context!(HorizontalPosition::parse, "10px left", "left 10px");
assert_roundtrip_with_context!(HorizontalPosition::parse, "x-end 20%", "x-end 20%");
assert_roundtrip_with_context!(HorizontalPosition::parse, "20px x-start", "x-start 20px");
// Invalid horizontal positions. // Invalid horizontal positions.
assert!(parse(HorizontalPosition::parse, "top").is_err()); assert!(parse(HorizontalPosition::parse, "top").is_err());
assert!(parse(HorizontalPosition::parse, "bottom").is_err()); assert!(parse(HorizontalPosition::parse, "bottom").is_err());
assert!(parse(HorizontalPosition::parse, "y-start").is_err()); assert!(parse(HorizontalPosition::parse, "y-start").is_err());
assert!(parse(HorizontalPosition::parse, "y-end").is_err()); assert!(parse(HorizontalPosition::parse, "y-end").is_err());
assert!(parse(HorizontalPosition::parse, "20px y-end").is_err());
assert!(parse(HorizontalPosition::parse, "y-end 20px ").is_err()); assert!(parse(HorizontalPosition::parse, "y-end 20px ").is_err());
assert!(parse(HorizontalPosition::parse, "bottom 20px").is_err()); assert!(parse(HorizontalPosition::parse, "bottom 20px").is_err());
assert!(parse(HorizontalPosition::parse, "20px top").is_err());
assert!(parse(HorizontalPosition::parse, "left center").is_err());
assert!(parse(HorizontalPosition::parse, "bottom top").is_err()); assert!(parse(HorizontalPosition::parse, "bottom top").is_err());
assert!(parse(HorizontalPosition::parse, "left top").is_err()); assert!(parse_entirely(HorizontalPosition::parse, "20px y-end").is_err());
assert!(parse(HorizontalPosition::parse, "left right").is_err()); assert!(parse_entirely(HorizontalPosition::parse, "20px top").is_err());
assert!(parse(HorizontalPosition::parse, "20px 30px").is_err()); assert!(parse_entirely(HorizontalPosition::parse, "left center").is_err());
assert!(parse_entirely(HorizontalPosition::parse, "left top").is_err());
assert!(parse_entirely(HorizontalPosition::parse, "left right").is_err());
assert!(parse_entirely(HorizontalPosition::parse, "20px 30px").is_err());
assert!(parse_entirely(HorizontalPosition::parse, "10px left").is_err());
assert!(parse_entirely(HorizontalPosition::parse, "x-end 20%").is_err());
assert!(parse_entirely(HorizontalPosition::parse, "20px x-start").is_err());
// Logical keywords are not supported in Position yet.
assert!(parse(HorizontalPosition::parse, "x-start").is_err());
assert!(parse(HorizontalPosition::parse, "x-end").is_err());
} }
#[test] #[test]
@ -115,29 +117,31 @@ fn test_vertical_position() {
assert_roundtrip_with_context!(VerticalPosition::parse, "center", "center"); assert_roundtrip_with_context!(VerticalPosition::parse, "center", "center");
assert_roundtrip_with_context!(VerticalPosition::parse, "top", "top"); assert_roundtrip_with_context!(VerticalPosition::parse, "top", "top");
assert_roundtrip_with_context!(VerticalPosition::parse, "bottom", "bottom"); assert_roundtrip_with_context!(VerticalPosition::parse, "bottom", "bottom");
assert_roundtrip_with_context!(VerticalPosition::parse, "y-start", "y-start");
assert_roundtrip_with_context!(VerticalPosition::parse, "y-end", "y-end");
// Two value serializations. // Two value serializations.
assert_roundtrip_with_context!(VerticalPosition::parse, "bottom 10px", "bottom 10px"); assert_roundtrip_with_context!(VerticalPosition::parse, "bottom 10px", "bottom 10px");
assert_roundtrip_with_context!(VerticalPosition::parse, "10px top", "top 10px");
assert_roundtrip_with_context!(VerticalPosition::parse, "y-end 20%", "y-end 20%");
assert_roundtrip_with_context!(VerticalPosition::parse, "20px y-start", "y-start 20px");
// Invalid vertical positions. // Invalid vertical positions.
assert!(parse(VerticalPosition::parse, "left").is_err()); assert!(parse(VerticalPosition::parse, "left").is_err());
assert!(parse(VerticalPosition::parse, "right").is_err()); assert!(parse(VerticalPosition::parse, "right").is_err());
assert!(parse(VerticalPosition::parse, "x-start").is_err()); assert!(parse(VerticalPosition::parse, "x-start").is_err());
assert!(parse(VerticalPosition::parse, "x-end").is_err()); assert!(parse(VerticalPosition::parse, "x-end").is_err());
assert!(parse(VerticalPosition::parse, "20px x-end").is_err()); assert!(parse(VerticalPosition::parse, "x-end 20px").is_err());
assert!(parse(VerticalPosition::parse, "x-end 20px ").is_err());
assert!(parse(VerticalPosition::parse, "left 20px").is_err()); assert!(parse(VerticalPosition::parse, "left 20px").is_err());
assert!(parse(VerticalPosition::parse, "20px right").is_err());
assert!(parse(VerticalPosition::parse, "left center").is_err()); assert!(parse(VerticalPosition::parse, "left center").is_err());
assert!(parse(VerticalPosition::parse, "bottom top").is_err());
assert!(parse(VerticalPosition::parse, "left top").is_err()); assert!(parse(VerticalPosition::parse, "left top").is_err());
assert!(parse(VerticalPosition::parse, "left right").is_err()); assert!(parse(VerticalPosition::parse, "left right").is_err());
assert!(parse(VerticalPosition::parse, "20px 30px").is_err()); assert!(parse_entirely(VerticalPosition::parse, "20px x-end").is_err());
assert!(parse_entirely(VerticalPosition::parse, "20px right").is_err());
assert!(parse_entirely(VerticalPosition::parse, "bottom top").is_err());
assert!(parse_entirely(VerticalPosition::parse, "20px 30px").is_err());
assert!(parse_entirely(VerticalPosition::parse, "10px top").is_err());
assert!(parse_entirely(VerticalPosition::parse, "y-end 20%").is_err());
assert!(parse_entirely(VerticalPosition::parse, "20px y-start").is_err());
// Logical keywords are not supported in Position yet.
assert!(parse(VerticalPosition::parse, "y-start").is_err());
assert!(parse(VerticalPosition::parse, "y-end").is_err());
} }
#[test] #[test]

View file

@ -3,7 +3,6 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
use app_units::Au; use app_units::Au;
use parsing::parse;
use style::values::HasViewportPercentage; use style::values::HasViewportPercentage;
use style::values::specified::{AbsoluteLength, NoCalcLength, ViewportPercentageLength}; use style::values::specified::{AbsoluteLength, NoCalcLength, ViewportPercentageLength};

View file

@ -9,8 +9,10 @@ use style::properties::longhands::outline_color::computed_value::T as ComputedCo
use style::properties::parse_property_declaration_list; use style::properties::parse_property_declaration_list;
use style::values::{RGBA, Auto}; use style::values::{RGBA, Auto};
use style::values::CustomIdent; use style::values::CustomIdent;
use style::values::specified::{BorderStyle, BorderWidth, CSSColor, Length, NoCalcLength}; use style::values::specified::{BorderStyle, BorderWidth, CSSColor, Length, LengthOrPercentage};
use style::values::specified::{LengthOrPercentage, LengthOrPercentageOrAuto, LengthOrPercentageOrAutoOrContent}; use style::values::specified::{LengthOrPercentageOrAuto, LengthOrPercentageOrAutoOrContent};
use style::values::specified::{NoCalcLength, PositionComponent};
use style::values::specified::position::Y;
use style::values::specified::url::SpecifiedUrl; use style::values::specified::url::SpecifiedUrl;
use style_traits::ToCss; use style_traits::ToCss;
use stylesheets::block_from; use stylesheets::block_from;
@ -796,7 +798,6 @@ mod shorthand_serialization {
use style::properties::longhands::mask_position_y as position_y; use style::properties::longhands::mask_position_y as position_y;
use style::properties::longhands::mask_repeat as repeat; use style::properties::longhands::mask_repeat as repeat;
use style::properties::longhands::mask_size as size; use style::properties::longhands::mask_size as size;
use style::values::generics::position::{HorizontalPosition, Keyword, PositionValue, VerticalPosition};
use style::values::specified::Image; use style::values::specified::Image;
use super::*; use super::*;
@ -833,16 +834,13 @@ mod shorthand_serialization {
let mode = single_vec_keyword_value!(mode, luminance); let mode = single_vec_keyword_value!(mode, luminance);
let position_x = single_vec_value_typedef!(position_x, let position_x = single_vec_value_typedef!(position_x,
HorizontalPosition(PositionValue { PositionComponent::Length(LengthOrPercentage::Length(NoCalcLength::from_px(7f32)))
keyword: None,
position: Some(LengthOrPercentage::Length(NoCalcLength::from_px(7f32))),
})
); );
let position_y = single_vec_value_typedef!(position_y, let position_y = single_vec_value_typedef!(position_y,
VerticalPosition(PositionValue { PositionComponent::Side(
keyword: Some(Keyword::Bottom), Y::Bottom,
position: Some(LengthOrPercentage::Length(NoCalcLength::from_px(4f32))), Some(LengthOrPercentage::Length(NoCalcLength::from_px(4f32))),
}) )
); );
let size = single_vec_variant_value!(size, let size = single_vec_variant_value!(size,
@ -888,17 +886,11 @@ mod shorthand_serialization {
let mode = single_vec_keyword_value!(mode, luminance); let mode = single_vec_keyword_value!(mode, luminance);
let position_x = single_vec_value_typedef!(position_x, let position_x = single_vec_value_typedef!(position_x,
HorizontalPosition(PositionValue { PositionComponent::Length(LengthOrPercentage::Length(NoCalcLength::from_px(7f32)))
keyword: None,
position: Some(LengthOrPercentage::Length(NoCalcLength::from_px(7f32))),
})
); );
let position_y = single_vec_value_typedef!(position_y, let position_y = single_vec_value_typedef!(position_y,
VerticalPosition(PositionValue { PositionComponent::Length(LengthOrPercentage::Length(NoCalcLength::from_px(4f32)))
keyword: None,
position: Some(LengthOrPercentage::Length(NoCalcLength::from_px(4f32))),
})
); );
let size = single_vec_variant_value!(size, let size = single_vec_variant_value!(size,

View file

@ -25,7 +25,7 @@ use style::stylearc::Arc;
use style::stylesheets::{Origin, Namespaces}; use style::stylesheets::{Origin, Namespaces};
use style::stylesheets::{Stylesheet, NamespaceRule, CssRule, CssRules, StyleRule, KeyframesRule}; use style::stylesheets::{Stylesheet, NamespaceRule, CssRule, CssRules, StyleRule, KeyframesRule};
use style::values::{KeyframesName, CustomIdent}; use style::values::{KeyframesName, CustomIdent};
use style::values::specified::{LengthOrPercentageOrAuto, Percentage}; use style::values::specified::{LengthOrPercentageOrAuto, Percentage, PositionComponent};
pub fn block_from<I>(iterable: I) -> PropertyDeclarationBlock pub fn block_from<I>(iterable: I) -> PropertyDeclarationBlock
where I: IntoIterator<Item=(PropertyDeclaration, Importance)> { where I: IntoIterator<Item=(PropertyDeclaration, Importance)> {
@ -183,13 +183,11 @@ fn test_parse_stylesheet() {
Importance::Normal), Importance::Normal),
(PropertyDeclaration::BackgroundPositionX( (PropertyDeclaration::BackgroundPositionX(
longhands::background_position_x::SpecifiedValue( longhands::background_position_x::SpecifiedValue(
vec![longhands::background_position_x::single_value vec![PositionComponent::zero()])),
::get_initial_position_value()])), Importance::Normal),
Importance::Normal),
(PropertyDeclaration::BackgroundPositionY( (PropertyDeclaration::BackgroundPositionY(
longhands::background_position_y::SpecifiedValue( longhands::background_position_y::SpecifiedValue(
vec![longhands::background_position_y::single_value vec![PositionComponent::zero()])),
::get_initial_position_value()])),
Importance::Normal), Importance::Normal),
(PropertyDeclaration::BackgroundRepeat( (PropertyDeclaration::BackgroundRepeat(
longhands::background_repeat::SpecifiedValue( longhands::background_repeat::SpecifiedValue(

View file

@ -25371,7 +25371,7 @@
"testharness" "testharness"
], ],
"mozilla/calc.html": [ "mozilla/calc.html": [
"028fc71bdc9a99d552ba552036d38fb4eef11bc1", "47507adabc0d3642154b3ed4b1ab64d726fa682d",
"testharness" "testharness"
], ],
"mozilla/canvas.initial.reset.2dstate.html": [ "mozilla/canvas.initial.reset.2dstate.html": [

View file

@ -142,7 +142,7 @@ var otherProperties = [
['border-width', 'calc(1px)', 'calc(1px)'], ['border-width', 'calc(1px)', 'calc(1px)'],
['border-spacing', 'calc(1px)', 'calc(1px)'], ['border-spacing', 'calc(1px)', 'calc(1px)'],
['transform-origin', 'calc(1px + 0%)', 'calc(1px + 0%) 50% 0px'], ['transform-origin', 'calc(1px + 0%)', 'calc(1px + 0%) 50% 0px'],
['perspective-origin', 'calc(1px + 0%)', 'calc(1px + 0%) 50%'], ['perspective-origin', 'calc(1px + 0%)', 'calc(1px + 0%) center'],
['background-size', 'calc(1px + 0%)', 'calc(1px + 0%) auto'], ['background-size', 'calc(1px + 0%)', 'calc(1px + 0%) auto'],
['background-position', 'calc(1px + 0%) calc(2px + 0%)', 'calc(1px + 0%) calc(2px + 0%)'], ['background-position', 'calc(1px + 0%) calc(2px + 0%)', 'calc(1px + 0%) calc(2px + 0%)'],
['border-top-left-radius', 'calc(1px + 0%)', 'calc(1px + 0%) calc(1px + 0%)'], ['border-top-left-radius', 'calc(1px + 0%)', 'calc(1px + 0%) calc(1px + 0%)'],