mirror of
https://github.com/servo/servo.git
synced 2025-07-22 23:03:42 +01:00
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:
parent
0040160b38
commit
70ec61cf01
22 changed files with 484 additions and 887 deletions
|
@ -1008,9 +1008,9 @@ impl FragmentDisplayListBuilding for Fragment {
|
|||
let horiz_position = *get_cyclic(&background.background_position_x.0, index);
|
||||
let vert_position = *get_cyclic(&background.background_position_y.0, index);
|
||||
// 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);
|
||||
let vertical_position = model::specified(vert_position.0,
|
||||
let vertical_position = model::specified(vert_position,
|
||||
bounds.size.height - image_size.height);
|
||||
|
||||
// The anchor position for this background, based on both the background-attachment
|
||||
|
@ -1167,8 +1167,8 @@ impl FragmentDisplayListBuilding for Fragment {
|
|||
repeating: bool,
|
||||
style: &ServoComputedValues)
|
||||
-> display_list::RadialGradient {
|
||||
let center = Point2D::new(specified(center.horizontal.0, bounds.size.width),
|
||||
specified(center.vertical.0, bounds.size.height));
|
||||
let center = Point2D::new(specified(center.horizontal, bounds.size.width),
|
||||
specified(center.vertical, bounds.size.height));
|
||||
let radius = match *shape {
|
||||
EndingShape::Circle(LengthOrKeyword::Length(length))
|
||||
=> Size2D::new(length, length),
|
||||
|
|
|
@ -307,8 +307,8 @@ impl nsStyleImage {
|
|||
},
|
||||
}
|
||||
unsafe {
|
||||
(*gecko_gradient).mBgPosX.set(position.horizontal.0);
|
||||
(*gecko_gradient).mBgPosY.set(position.vertical.0);
|
||||
(*gecko_gradient).mBgPosX.set(position.horizontal);
|
||||
(*gecko_gradient).mBgPosY.set(position.vertical);
|
||||
}
|
||||
|
||||
gecko_gradient
|
||||
|
@ -372,7 +372,6 @@ pub mod basic_shape {
|
|||
use values::computed::position;
|
||||
use values::generics::BorderRadiusSize as GenericBorderRadiusSize;
|
||||
use values::generics::basic_shape::FillRule;
|
||||
use values::generics::position::{HorizontalPosition, VerticalPosition};
|
||||
|
||||
// using Borrow so that we can have a non-moving .into()
|
||||
impl<T: Borrow<StyleBasicShape>> From<T> for BasicShape {
|
||||
|
@ -483,8 +482,8 @@ pub mod basic_shape {
|
|||
impl From<position::Position> for structs::Position {
|
||||
fn from(other: position::Position) -> Self {
|
||||
structs::Position {
|
||||
mXPosition: other.horizontal.0.into(),
|
||||
mYPosition: other.vertical.0.into()
|
||||
mXPosition: other.horizontal.into(),
|
||||
mYPosition: other.vertical.into()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -501,8 +500,8 @@ pub mod basic_shape {
|
|||
fn from(other: T) -> Self {
|
||||
let other = other.borrow();
|
||||
position::Position {
|
||||
horizontal: HorizontalPosition(other.mXPosition.into()),
|
||||
vertical: VerticalPosition(other.mYPosition.into()),
|
||||
horizontal: other.mXPosition.into(),
|
||||
vertical: other.mYPosition.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)">
|
||||
#[allow(non_snake_case)]
|
||||
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.mYPosition" % gecko_ffi_name, "v.vertical.0.into()")}
|
||||
${set_gecko_property("%s.mXPosition" % gecko_ffi_name, "v.horizontal.into()")}
|
||||
${set_gecko_property("%s.mYPosition" % gecko_ffi_name, "v.vertical.into()")}
|
||||
}
|
||||
<%call expr="impl_simple_copy(ident, gecko_ffi_name)"></%call>
|
||||
% if need_clone:
|
||||
#[allow(non_snake_case)]
|
||||
pub fn clone_${ident}(&self) -> longhands::${ident}::computed_value::T {
|
||||
use values::generics::position::{HorizontalPosition, Position, VerticalPosition};
|
||||
Position {
|
||||
horizontal: HorizontalPosition(self.gecko.${gecko_ffi_name}.mXPosition.into()),
|
||||
vertical: VerticalPosition(self.gecko.${gecko_ffi_name}.mYPosition.into()),
|
||||
longhands::${ident}::computed_value::T {
|
||||
horizontal: self.gecko.${gecko_ffi_name}.mXPosition.into(),
|
||||
vertical: self.gecko.${gecko_ffi_name}.mYPosition.into(),
|
||||
}
|
||||
}
|
||||
% endif
|
||||
|
@ -2040,8 +2039,8 @@ fn static_assert() {
|
|||
for (gecko, servo) in self.gecko.mScrollSnapCoordinate
|
||||
.iter_mut()
|
||||
.zip(v) {
|
||||
gecko.mXPosition = servo.horizontal.0.into();
|
||||
gecko.mYPosition = servo.vertical.0.into();
|
||||
gecko.mXPosition = servo.horizontal.into();
|
||||
gecko.mYPosition = servo.vertical.into();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2726,12 +2725,12 @@ fn static_assert() {
|
|||
}
|
||||
</%self:simple_image_array_property>
|
||||
|
||||
% for orientation in [("x", "Horizontal"), ("y", "Vertical")]:
|
||||
pub fn copy_${shorthand}_position_${orientation[0]}_from(&mut self, other: &Self) {
|
||||
% for orientation in ["x", "y"]:
|
||||
pub fn copy_${shorthand}_position_${orientation}_from(&mut self, other: &Self) {
|
||||
use gecko_bindings::structs::nsStyleImageLayers_LayerType as LayerType;
|
||||
|
||||
self.gecko.${image_layers_field}.mPosition${orientation[0].upper()}Count
|
||||
= cmp::min(1, other.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.upper()}Count);
|
||||
self.gecko.${image_layers_field}.mLayers.mFirstElement.mPosition =
|
||||
other.gecko.${image_layers_field}.mLayers.mFirstElement.mPosition;
|
||||
unsafe {
|
||||
|
@ -2742,20 +2741,19 @@ fn static_assert() {
|
|||
|
||||
for (layer, other) in self.gecko.${image_layers_field}.mLayers.iter_mut()
|
||||
.zip(other.gecko.${image_layers_field}.mLayers.iter()) {
|
||||
layer.mPosition.m${orientation[0].upper()}Position
|
||||
= other.mPosition.m${orientation[0].upper()}Position;
|
||||
layer.mPosition.m${orientation.upper()}Position
|
||||
= other.mPosition.m${orientation.upper()}Position;
|
||||
}
|
||||
self.gecko.${image_layers_field}.mPosition${orientation[0].upper()}Count
|
||||
= other.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.upper()}Count;
|
||||
}
|
||||
|
||||
pub fn clone_${shorthand}_position_${orientation[0]}(&self)
|
||||
-> longhands::${shorthand}_position_${orientation[0]}::computed_value::T {
|
||||
use values::generics::position::${orientation[1]}Position;
|
||||
longhands::${shorthand}_position_${orientation[0]}::computed_value::T(
|
||||
pub fn clone_${shorthand}_position_${orientation}(&self)
|
||||
-> longhands::${shorthand}_position_${orientation}::computed_value::T {
|
||||
longhands::${shorthand}_position_${orientation}::computed_value::T(
|
||||
self.gecko.${image_layers_field}.mLayers.iter()
|
||||
.take(self.gecko.${image_layers_field}.mPosition${orientation[0].upper()}Count as usize)
|
||||
.map(|position| ${orientation[1]}Position(position.mPosition.m${orientation[0].upper()}Position.into()))
|
||||
.take(self.gecko.${image_layers_field}.mPosition${orientation.upper()}Count as usize)
|
||||
.map(|position| position.mPosition.m${orientation.upper()}Position.into())
|
||||
.collect()
|
||||
)
|
||||
}
|
||||
|
@ -2778,7 +2776,7 @@ fn static_assert() {
|
|||
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}
|
||||
.mLayers.iter_mut()) {
|
||||
geckolayer.mPosition.m${orientation[0].upper()}Position = servo.0.into();
|
||||
geckolayer.mPosition.m${orientation[0].upper()}Position = servo.into();
|
||||
}
|
||||
}
|
||||
% endfor
|
||||
|
|
|
@ -41,7 +41,6 @@ use values::computed::{Angle, LengthOrPercentageOrAuto, LengthOrPercentageOrNone
|
|||
use values::computed::{BorderRadiusSize, ClipRect};
|
||||
use values::computed::{CalcLengthOrPercentage, Context, LengthOrPercentage};
|
||||
use values::computed::{MaxLength, MinLength};
|
||||
use values::computed::position::{HorizontalPosition, VerticalPosition};
|
||||
use values::computed::ToComputedValue;
|
||||
use values::generics::position as generic_position;
|
||||
|
||||
|
@ -655,6 +654,7 @@ pub trait Animatable: Sized {
|
|||
/// https://drafts.csswg.org/css-transitions/#animtype-repeatable-list
|
||||
pub trait RepeatableListAnimatable: Animatable {}
|
||||
|
||||
impl RepeatableListAnimatable for LengthOrPercentage {}
|
||||
impl RepeatableListAnimatable for Either<f32, LengthOrPercentage> {}
|
||||
|
||||
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>
|
||||
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
|
||||
impl Animatable for ClipRect {
|
||||
#[inline]
|
||||
|
|
|
@ -20,38 +20,13 @@ ${helpers.predefined_type("background-image", "LayerImage",
|
|||
animation_value_type="none",
|
||||
has_uncacheable_values="True" if product == "gecko" else "False")}
|
||||
|
||||
<%helpers:predefined_type name="background-position-x" type="position::HorizontalPosition"
|
||||
initial_value="computed::position::HorizontalPosition::zero()"
|
||||
initial_specified_value="specified::position::HorizontalPosition::left()"
|
||||
spec="https://drafts.csswg.org/css-backgrounds-4/#propdef-background-position-x"
|
||||
animation_value_type="ComputedValue" vector="True" delegate_animate="True">
|
||||
#[inline]
|
||||
/// Get the initial value for horizontal position.
|
||||
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>
|
||||
% for (axis, direction, initial) in [("x", "Horizontal", "left"), ("y", "Vertical", "top")]:
|
||||
${helpers.predefined_type("background-position-" + axis, "position::" + direction + "Position",
|
||||
initial_value="computed::LengthOrPercentage::zero()",
|
||||
initial_specified_value="SpecifiedValue::initial_specified_value()",
|
||||
spec="https://drafts.csswg.org/css-backgrounds-4/#propdef-background-position-" + axis,
|
||||
animation_value_type="ComputedValue", vector=True, delegate_animate=True)}
|
||||
% endfor
|
||||
|
||||
<%helpers:vector_longhand name="background-repeat" animation_value_type="none"
|
||||
spec="https://drafts.csswg.org/css-backgrounds/#the-background-repeat">
|
||||
|
|
|
@ -2143,11 +2143,11 @@ ${helpers.predefined_type("perspective",
|
|||
animation_value_type="ComputedValue")}
|
||||
|
||||
${helpers.predefined_type("perspective-origin",
|
||||
"position::OriginPosition",
|
||||
"computed::position::OriginPosition::center()",
|
||||
"position::Position",
|
||||
"computed::position::Position::center()",
|
||||
boxed="True",
|
||||
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")}
|
||||
|
||||
${helpers.single_keyword("backface-visibility",
|
||||
|
|
|
@ -90,49 +90,14 @@ ${helpers.single_keyword("mask-mode",
|
|||
}
|
||||
</%helpers:vector_longhand>
|
||||
|
||||
<%helpers:vector_longhand name="mask-position-x" 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_x::single_value::get_initial_value;
|
||||
pub use properties::longhands::background_position_x::single_value::get_initial_position_value;
|
||||
pub use properties::longhands::background_position_x::single_value::get_initial_specified_value;
|
||||
pub use properties::longhands::background_position_x::single_value::parse;
|
||||
pub use properties::longhands::background_position_x::single_value::SpecifiedValue;
|
||||
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>
|
||||
% for (axis, direction) in [("x", "Horizontal"), ("y", "Vertical")]:
|
||||
${helpers.predefined_type("mask-position-" + axis, "position::" + direction + "Position",
|
||||
products="gecko", extra_prefixes="webkit",
|
||||
initial_value="computed::LengthOrPercentage::zero()",
|
||||
initial_specified_value="specified::PositionComponent::Center",
|
||||
spec="https://drafts.fxtf.org/css-masking/#propdef-mask-position",
|
||||
animation_value_type="ComputedValue", vector=True, delegate_animate=True)}
|
||||
% endfor
|
||||
|
||||
${helpers.single_keyword("mask-clip",
|
||||
"border-box content-box padding-box",
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
use properties::longhands::background_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 values::specified::position::Position;
|
||||
use values::specified::{Position, PositionComponent};
|
||||
use parser::Parse;
|
||||
|
||||
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());
|
||||
% endfor
|
||||
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;
|
||||
% endfor
|
||||
loop {
|
||||
|
@ -52,10 +52,9 @@
|
|||
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)) {
|
||||
position_x = Some(value.horizontal);
|
||||
position_y = Some(value.vertical);
|
||||
position = Some(value);
|
||||
|
||||
// Parse background size, if applicable.
|
||||
size = input.try(|input| {
|
||||
|
@ -83,22 +82,17 @@
|
|||
}
|
||||
}
|
||||
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();
|
||||
% endfor
|
||||
any = any || background_color.is_some();
|
||||
if any {
|
||||
if position_x.is_some() || position_y.is_some() {
|
||||
% for name in "position_x position_y".split():
|
||||
if let Some(bg_${name}) = ${name} {
|
||||
background_${name}.0.push(bg_${name});
|
||||
}
|
||||
% endfor
|
||||
if let Some(position) = position {
|
||||
background_position_x.0.push(position.horizontal);
|
||||
background_position_y.0.push(position.vertical);
|
||||
} else {
|
||||
% for name in "position_x position_y".split():
|
||||
background_${name}.0.push(background_${name}::single_value
|
||||
::get_initial_position_value());
|
||||
% endfor
|
||||
background_position_x.0.push(PositionComponent::zero());
|
||||
background_position_y.0.push(PositionComponent::zero());
|
||||
}
|
||||
% for name in "image repeat size attachment origin clip".split():
|
||||
if let Some(bg_${name}) = ${name} {
|
||||
|
@ -193,7 +187,7 @@
|
|||
<%helpers:shorthand name="background-position"
|
||||
sub_properties="background-position-x background-position-y"
|
||||
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::position::Position;
|
||||
|
||||
|
@ -202,18 +196,13 @@
|
|||
let mut position_y = background_position_y::SpecifiedValue(Vec::new());
|
||||
let mut any = false;
|
||||
|
||||
try!(input.parse_comma_separated(|input| {
|
||||
loop {
|
||||
if let Ok(value) = input.try(|input| Position::parse_quirky(context, input, AllowQuirks::Yes)) {
|
||||
position_x.0.push(value.horizontal);
|
||||
position_y.0.push(value.vertical);
|
||||
any = true;
|
||||
continue
|
||||
}
|
||||
break
|
||||
}
|
||||
input.parse_comma_separated(|input| {
|
||||
let value = Position::parse_quirky(context, input, AllowQuirks::Yes)?;
|
||||
position_x.0.push(value.horizontal);
|
||||
position_y.0.push(value.vertical);
|
||||
any = true;
|
||||
Ok(())
|
||||
}));
|
||||
})?;
|
||||
if !any {
|
||||
return Err(());
|
||||
}
|
||||
|
|
|
@ -11,7 +11,7 @@
|
|||
use properties::longhands::{mask_mode, mask_repeat, mask_clip, mask_origin, mask_composite, mask_position_x,
|
||||
mask_position_y};
|
||||
use properties::longhands::{mask_size, mask_image};
|
||||
use values::specified::position::Position;
|
||||
use values::specified::{Position, PositionComponent};
|
||||
use parser::Parse;
|
||||
|
||||
impl From<mask_origin::single_value::SpecifiedValue> for mask_clip::single_value::SpecifiedValue {
|
||||
|
@ -41,7 +41,7 @@
|
|||
% endfor
|
||||
|
||||
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;
|
||||
% endfor
|
||||
loop {
|
||||
|
@ -52,10 +52,9 @@
|
|||
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)) {
|
||||
position_x = Some(value.horizontal);
|
||||
position_y = Some(value.vertical);
|
||||
position = Some(value);
|
||||
|
||||
// Parse mask size, if applicable.
|
||||
size = input.try(|input| {
|
||||
|
@ -83,21 +82,16 @@
|
|||
}
|
||||
}
|
||||
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();
|
||||
% endfor
|
||||
if any {
|
||||
if position_x.is_some() || position_y.is_some() {
|
||||
% for name in "position_x position_y".split():
|
||||
if let Some(bg_${name}) = ${name} {
|
||||
mask_${name}.0.push(bg_${name});
|
||||
}
|
||||
% endfor
|
||||
if let Some(position) = position {
|
||||
mask_position_x.0.push(position.horizontal);
|
||||
mask_position_y.0.push(position.vertical);
|
||||
} else {
|
||||
% for name in "position_x position_y".split():
|
||||
mask_${name}.0.push(mask_${name}::single_value
|
||||
::get_initial_position_value());
|
||||
% endfor
|
||||
mask_position_x.0.push(PositionComponent::zero());
|
||||
mask_position_y.0.push(PositionComponent::zero());
|
||||
}
|
||||
% for name in "image mode size repeat origin clip composite".split():
|
||||
if let Some(m_${name}) = ${name} {
|
||||
|
@ -191,18 +185,13 @@
|
|||
let mut position_y = mask_position_y::SpecifiedValue(Vec::new());
|
||||
let mut any = false;
|
||||
|
||||
try!(input.parse_comma_separated(|input| {
|
||||
loop {
|
||||
if let Ok(value) = input.try(|input| Position::parse(context, input)) {
|
||||
position_x.0.push(value.horizontal);
|
||||
position_y.0.push(value.vertical);
|
||||
any = true;
|
||||
continue
|
||||
}
|
||||
break
|
||||
}
|
||||
input.parse_comma_separated(|input| {
|
||||
let value = Position::parse(context, input)?;
|
||||
position_x.0.push(value.horizontal);
|
||||
position_y.0.push(value.vertical);
|
||||
any = true;
|
||||
Ok(())
|
||||
}));
|
||||
})?;
|
||||
if any == false {
|
||||
return Err(());
|
||||
}
|
||||
|
|
|
@ -10,39 +10,28 @@
|
|||
use std::fmt;
|
||||
use style_traits::ToCss;
|
||||
use values::computed::LengthOrPercentage;
|
||||
use values::generics::position::{Position as GenericPosition, PositionWithKeyword};
|
||||
use values::generics::position::HorizontalPosition as GenericHorizontalPosition;
|
||||
use values::generics::position::VerticalPosition as GenericVerticalPosition;
|
||||
use values::generics::position::Position as GenericPosition;
|
||||
|
||||
/// 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.
|
||||
pub type OriginPosition = GenericPosition<LengthOrPercentage, 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),
|
||||
}
|
||||
}
|
||||
}
|
||||
/// The computed value of a CSS vertical position.
|
||||
pub type VerticalPosition = LengthOrPercentage;
|
||||
|
||||
impl Position {
|
||||
/// `50% 50%`
|
||||
#[inline]
|
||||
pub fn center() -> Self {
|
||||
Self::new(LengthOrPercentage::Percentage(0.5), LengthOrPercentage::Percentage(0.5))
|
||||
}
|
||||
|
||||
/// `0% 0%`
|
||||
#[inline]
|
||||
/// Construct a position at (0, 0)
|
||||
pub fn zero() -> Self {
|
||||
Position {
|
||||
horizontal: GenericHorizontalPosition(LengthOrPercentage::zero()),
|
||||
vertical: GenericVerticalPosition(LengthOrPercentage::zero()),
|
||||
}
|
||||
Self::new(LengthOrPercentage::zero(), LengthOrPercentage::zero())
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -53,29 +42,3 @@ impl ToCss for Position {
|
|||
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))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,231 +5,12 @@
|
|||
//! Generic types for CSS handling of specified and computed values of
|
||||
//! [`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::computed::{ComputedValueAsSpecified, Context, ToComputedValue};
|
||||
use values::specified::{LengthOrPercentage, Percentage};
|
||||
use values::computed::{Context, ToComputedValue};
|
||||
|
||||
define_css_keyword_enum!{ Keyword:
|
||||
"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)]
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
#[cfg_attr(feature = "servo", derive(HeapSizeOf))]
|
||||
/// 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> {
|
||||
/// The horizontal component of position.
|
||||
pub horizontal: H,
|
||||
|
@ -237,8 +18,15 @@ pub struct Position<H, V> {
|
|||
pub vertical: V,
|
||||
}
|
||||
|
||||
/// A generic type for representing positions with keywords.
|
||||
pub type PositionWithKeyword<L> = Position<HorizontalPosition<L>, VerticalPosition<L>>;
|
||||
impl<H, V> Position<H, V> {
|
||||
/// 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> {
|
||||
#[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> {
|
||||
type ComputedValue = Position<H::ComputedValue, V::ComputedValue>;
|
||||
type ComputedValue = Position<<H as ToComputedValue>::ComputedValue,
|
||||
<V as ToComputedValue>::ComputedValue>;
|
||||
|
||||
#[inline]
|
||||
fn to_computed_value(&self, context: &Context) -> Self::ComputedValue {
|
||||
|
@ -294,7 +49,7 @@ impl<H: ToComputedValue, V: ToComputedValue> ToComputedValue for Position<H, V>
|
|||
|
||||
#[inline]
|
||||
fn from_computed_value(computed: &Self::ComputedValue) -> Self {
|
||||
Position {
|
||||
Self {
|
||||
horizontal: ToComputedValue::from_computed_value(&computed.horizontal),
|
||||
vertical: ToComputedValue::from_computed_value(&computed.vertical),
|
||||
}
|
||||
|
|
|
@ -11,6 +11,7 @@ use cssparser::Parser;
|
|||
use parser::{Parse, ParserContext};
|
||||
use properties::shorthands::parse_four_sides;
|
||||
use std::ascii::AsciiExt;
|
||||
use std::borrow::Cow;
|
||||
use std::fmt;
|
||||
use style_traits::ToCss;
|
||||
use values::HasViewportPercentage;
|
||||
|
@ -19,8 +20,8 @@ use values::computed::basic_shape as computed_basic_shape;
|
|||
use values::generics::BorderRadiusSize;
|
||||
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::specified::{LengthOrPercentage, Percentage};
|
||||
use values::specified::position::{Keyword, Position};
|
||||
use values::specified::{LengthOrPercentage, Percentage, Position, PositionComponent};
|
||||
use values::specified::position::Side;
|
||||
|
||||
/// The specified value used by `clip-path`
|
||||
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
|
||||
where W: fmt::Write
|
||||
{
|
||||
// 0 length should be replaced with 0%
|
||||
fn replace_with_percent(input: LengthOrPercentage) -> LengthOrPercentage {
|
||||
match input {
|
||||
LengthOrPercentage::Length(ref l) if l.is_zero() =>
|
||||
LengthOrPercentage::Percentage(Percentage(0.0)),
|
||||
_ => input
|
||||
}
|
||||
}
|
||||
|
||||
// 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)
|
||||
fn to_keyword_and_lop<S>(component: &PositionComponent<S>) -> (S, Cow<LengthOrPercentage>)
|
||||
where S: Copy + Side
|
||||
{
|
||||
match *component {
|
||||
PositionComponent::Center => {
|
||||
(S::start(), Cow::Owned(LengthOrPercentage::Percentage(Percentage(0.5))))
|
||||
},
|
||||
Some(Keyword::Left) | Some(Keyword::Top) | None => pc,
|
||||
Some(Keyword::Right) | Some(Keyword::Bottom) => Percentage(1.0 - pc.0),
|
||||
_ => return None,
|
||||
};
|
||||
|
||||
Some(LengthOrPercentage::Percentage(percent))
|
||||
PositionComponent::Side(keyword, None) => {
|
||||
// left | top => 0%
|
||||
// right | bottom => 100%
|
||||
let p = if keyword.is_start() { 0. } else { 1. };
|
||||
(S::start(), Cow::Owned(LengthOrPercentage::Percentage(Percentage(p))))
|
||||
},
|
||||
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,
|
||||
dest: &mut W) -> fmt::Result where W: fmt::Write {
|
||||
replace_with_percent(x).to_css(dest)?;
|
||||
dest.write_str(" ")?;
|
||||
replace_with_percent(y).to_css(dest)
|
||||
}
|
||||
|
||||
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 to_non_zero_length(lop: &LengthOrPercentage) -> Cow<LengthOrPercentage> {
|
||||
match *lop {
|
||||
LengthOrPercentage::Length(ref l) if l.is_zero() => {
|
||||
Cow::Owned(LengthOrPercentage::Percentage(Percentage(0.)))
|
||||
},
|
||||
_ => {
|
||||
Cow::Borrowed(lop)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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)]
|
||||
|
|
|
@ -36,7 +36,7 @@ pub use self::length::{FontRelativeLength, ViewportPercentageLength, CharacterWi
|
|||
pub use self::length::{Percentage, LengthOrNone, LengthOrNumber, LengthOrPercentage, LengthOrPercentageOrAuto};
|
||||
pub use self::length::{LengthOrPercentageOrNone, LengthOrPercentageOrAutoOrContent, NoCalcLength};
|
||||
pub use self::length::{MaxLength, MinLength};
|
||||
pub use self::position::{HorizontalPosition, Position, VerticalPosition};
|
||||
pub use self::position::{Position, PositionComponent};
|
||||
|
||||
#[cfg(feature = "gecko")]
|
||||
pub mod align;
|
||||
|
|
|
@ -7,278 +7,297 @@
|
|||
//!
|
||||
//! [position]: https://drafts.csswg.org/css-backgrounds-3/#position
|
||||
|
||||
use app_units::Au;
|
||||
use cssparser::Parser;
|
||||
use parser::{Parse, ParserContext};
|
||||
use properties::longhands::parse_origin;
|
||||
use std::mem;
|
||||
use values::Either;
|
||||
use values::computed::{CalcLengthOrPercentage, Context};
|
||||
use std::fmt;
|
||||
use style_traits::ToCss;
|
||||
use values::HasViewportPercentage;
|
||||
use values::computed::{CalcLengthOrPercentage, ComputedValueAsSpecified, Context};
|
||||
use values::computed::{LengthOrPercentage as ComputedLengthOrPercentage, ToComputedValue};
|
||||
use values::computed::position as computed_position;
|
||||
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::generics::position::Position as GenericPosition;
|
||||
use values::specified::{AllowQuirks, LengthOrPercentage, Percentage};
|
||||
|
||||
pub use values::generics::position::Keyword;
|
||||
|
||||
/// 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.
|
||||
pub type OriginPosition = GenericPosition<LengthOrPercentage, LengthOrPercentage>;
|
||||
/// The specified value of a horizontal position.
|
||||
pub type HorizontalPosition = PositionComponent<X>;
|
||||
|
||||
impl Parse for OriginPosition {
|
||||
fn parse(context: &ParserContext, input: &mut Parser) -> Result<Self, ()> {
|
||||
let result = parse_origin(context, input)?;
|
||||
match result.depth {
|
||||
Some(_) => Err(()),
|
||||
None => Ok(GenericPosition {
|
||||
horizontal: result.horizontal.unwrap_or(LengthOrPercentage::Percentage(Percentage(0.5))),
|
||||
vertical: result.vertical.unwrap_or(LengthOrPercentage::Percentage(Percentage(0.5))),
|
||||
})
|
||||
}
|
||||
}
|
||||
/// The specified value of a vertical position.
|
||||
pub type VerticalPosition = PositionComponent<Y>;
|
||||
|
||||
/// The specified value of a component of a CSS `<position>`.
|
||||
#[cfg_attr(feature = "servo", derive(HeapSizeOf))]
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub enum PositionComponent<S> {
|
||||
/// `center`
|
||||
Center,
|
||||
/// `<lop>`
|
||||
Length(LengthOrPercentage),
|
||||
/// `<side> <lop>?`
|
||||
Side(S, Option<LengthOrPercentage>),
|
||||
}
|
||||
|
||||
type PositionComponent = Either<LengthOrPercentage, Keyword>;
|
||||
|
||||
impl Position {
|
||||
/// 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,
|
||||
}),
|
||||
}
|
||||
}
|
||||
define_css_keyword_enum! { X:
|
||||
"left" => Left,
|
||||
"right" => Right,
|
||||
}
|
||||
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 {
|
||||
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 {
|
||||
/// Parses, with quirks.
|
||||
/// Parses a `<position>`, with quirks.
|
||||
pub fn parse_quirky(context: &ParserContext,
|
||||
input: &mut Parser,
|
||||
allow_quirks: AllowQuirks)
|
||||
-> Result<Self, ()> {
|
||||
let first = input.try(|i| PositionComponent::parse_quirky(context, i, allow_quirks))?;
|
||||
let second = input.try(|i| PositionComponent::parse_quirky(context, i, allow_quirks))
|
||||
.unwrap_or(Either::Second(Keyword::Center));
|
||||
|
||||
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>
|
||||
match input.try(|i| PositionComponent::parse_quirky(context, i, allow_quirks)) {
|
||||
Ok(x_pos @ PositionComponent::Center) => {
|
||||
if let Ok(y_pos) = input.try(|i| PositionComponent::parse_quirky(context, i, allow_quirks)) {
|
||||
return Ok(Self::new(x_pos, y_pos));
|
||||
}
|
||||
|
||||
// only 3 values.
|
||||
match (&second, &third) {
|
||||
(&Either::First(_), &Either::First(_)) => Err(()),
|
||||
// "keyword length keyword"
|
||||
(&Either::First(_), _) => Position::from_components(Some(second), None,
|
||||
Some(first), Some(third)),
|
||||
// "keyword keyword length"
|
||||
_ => Position::from_components(None, Some(third), Some(first), Some(second)),
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// only 2 values.
|
||||
match (&first, &second) {
|
||||
(&Either::First(_), &Either::First(_)) =>
|
||||
Position::from_components(Some(first), Some(second), None, None),
|
||||
(&Either::First(_), &Either::Second(_)) =>
|
||||
Position::from_components(Some(first), None, None, Some(second)),
|
||||
(&Either::Second(_), &Either::First(_)) =>
|
||||
Position::from_components(None, Some(second), Some(first), None),
|
||||
(&Either::Second(_), &Either::Second(_)) =>
|
||||
Position::from_components(None, None, Some(first), Some(second)),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PositionComponent {
|
||||
/// Parses, with quirks.
|
||||
fn parse_quirky(context: &ParserContext,
|
||||
input: &mut Parser,
|
||||
allow_quirks: AllowQuirks) -> Result<Self, ()> {
|
||||
input.try(|i| LengthOrPercentage::parse_quirky(context, i, allow_quirks))
|
||||
.map(Either::First)
|
||||
.or_else(|()| input.try(Keyword::parse).map(Either::Second))
|
||||
}
|
||||
}
|
||||
|
||||
impl PositionValue<LengthOrPercentage> {
|
||||
/// Generic function for the computed value of a position.
|
||||
fn computed_value(&self, context: &Context) -> ComputedLengthOrPercentage {
|
||||
match self.keyword {
|
||||
Some(Keyword::Center) => ComputedLengthOrPercentage::Percentage(0.5),
|
||||
Some(k) if k.is_other_side() => match self.position {
|
||||
Some(ref x) => {
|
||||
let (length, percentage) = match *x {
|
||||
LengthOrPercentage::Percentage(Percentage(y)) => (Au(0), Some(1.0 - y)),
|
||||
LengthOrPercentage::Length(ref y) => (-y.to_computed_value(context), Some(1.0)),
|
||||
_ => (Au(0), None),
|
||||
};
|
||||
|
||||
ComputedLengthOrPercentage::Calc(CalcLengthOrPercentage {
|
||||
length: length,
|
||||
percentage: percentage
|
||||
})
|
||||
},
|
||||
None => ComputedLengthOrPercentage::Percentage(1.0),
|
||||
let x_pos = input
|
||||
.try(|i| PositionComponent::parse_quirky(context, i, allow_quirks))
|
||||
.unwrap_or(x_pos);
|
||||
let y_pos = PositionComponent::Center;
|
||||
return Ok(Self::new(x_pos, y_pos));
|
||||
},
|
||||
Ok(PositionComponent::Side(x_keyword, lop)) => {
|
||||
if input.try(|i| i.expect_ident_matching("center")).is_ok() {
|
||||
let x_pos = PositionComponent::Side(x_keyword, lop);
|
||||
let y_pos = PositionComponent::Center;
|
||||
return Ok(Self::new(x_pos, y_pos));
|
||||
}
|
||||
if let Ok(y_keyword) = input.try(Y::parse) {
|
||||
let y_lop = input.try(|i| LengthOrPercentage::parse_quirky(context, i, allow_quirks)).ok();
|
||||
let x_pos = PositionComponent::Side(x_keyword, lop);
|
||||
let y_pos = PositionComponent::Side(y_keyword, y_lop);
|
||||
return Ok(Self::new(x_pos, y_pos));
|
||||
}
|
||||
let x_pos = PositionComponent::Side(x_keyword, None);
|
||||
let y_pos = lop.map_or(PositionComponent::Center, PositionComponent::Length);
|
||||
return Ok(Self::new(x_pos, y_pos));
|
||||
},
|
||||
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));
|
||||
}
|
||||
if let Ok(y_lop) = input.try(|i| LengthOrPercentage::parse_quirky(context, i, allow_quirks)) {
|
||||
let y_pos = PositionComponent::Length(y_lop);
|
||||
return Ok(Self::new(x_pos, y_pos));
|
||||
}
|
||||
let y_pos = PositionComponent::Center;
|
||||
let _ = input.try(|i| i.expect_ident_matching("center"));
|
||||
return Ok(Self::new(x_pos, y_pos));
|
||||
},
|
||||
Err(_) => {},
|
||||
}
|
||||
let y_keyword = Y::parse(input)?;
|
||||
let lop_and_x_pos: Result<_, ()> = input.try(|i| {
|
||||
let y_lop = i.try(|i| LengthOrPercentage::parse_quirky(context, i, allow_quirks)).ok();
|
||||
if let Ok(x_keyword) = i.try(X::parse) {
|
||||
let x_lop = i.try(|i| LengthOrPercentage::parse_quirky(context, i, allow_quirks)).ok();
|
||||
let x_pos = PositionComponent::Side(x_keyword, x_lop);
|
||||
return Ok((y_lop, x_pos));
|
||||
}
|
||||
i.expect_ident_matching("center")?;
|
||||
let x_pos = PositionComponent::Center;
|
||||
Ok((y_lop, x_pos))
|
||||
});
|
||||
if let Ok((y_lop, x_pos)) = lop_and_x_pos {
|
||||
let y_pos = PositionComponent::Side(y_keyword, y_lop);
|
||||
return Ok(Self::new(x_pos, y_pos));
|
||||
}
|
||||
let x_pos = PositionComponent::Center;
|
||||
let y_pos = PositionComponent::Side(y_keyword, None);
|
||||
Ok(Self::new(x_pos, y_pos))
|
||||
}
|
||||
|
||||
/// `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>`
|
||||
pub type HorizontalPosition = GenericHorizontalPosition<PositionValue<LengthOrPercentage>>;
|
||||
|
||||
impl ToComputedValue for HorizontalPosition {
|
||||
type ComputedValue = computed_position::HorizontalPosition;
|
||||
|
||||
#[inline]
|
||||
fn to_computed_value(&self, context: &Context) -> computed_position::HorizontalPosition {
|
||||
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<S> HasViewportPercentage for PositionComponent<S> {
|
||||
fn has_viewport_percentage(&self) -> bool {
|
||||
match *self {
|
||||
PositionComponent::Length(ref lop) |
|
||||
PositionComponent::Side(_, Some(ref lop)) => {
|
||||
lop.has_viewport_percentage()
|
||||
},
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl HorizontalPosition {
|
||||
#[inline]
|
||||
/// Initial specified value for vertical position (`top` keyword).
|
||||
pub fn left() -> HorizontalPosition {
|
||||
GenericHorizontalPosition(PositionValue {
|
||||
keyword: Some(Keyword::Left),
|
||||
position: None,
|
||||
})
|
||||
impl<S: Parse> Parse for PositionComponent<S> {
|
||||
fn parse(context: &ParserContext, input: &mut Parser) -> Result<Self, ()> {
|
||||
Self::parse_quirky(context, input, AllowQuirks::No)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// The specified value of vertical `<position>`
|
||||
pub type VerticalPosition = GenericVerticalPosition<PositionValue<LengthOrPercentage>>;
|
||||
|
||||
impl ToComputedValue for VerticalPosition {
|
||||
type ComputedValue = computed_position::VerticalPosition;
|
||||
|
||||
#[inline]
|
||||
fn to_computed_value(&self, context: &Context) -> computed_position::VerticalPosition {
|
||||
GenericVerticalPosition(self.0.computed_value(context))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn from_computed_value(computed: &computed_position::VerticalPosition) -> VerticalPosition {
|
||||
GenericVerticalPosition(PositionValue {
|
||||
keyword: None,
|
||||
position: Some(ToComputedValue::from_computed_value(&computed.0)),
|
||||
})
|
||||
impl<S: Parse> PositionComponent<S> {
|
||||
/// Parses a component of a CSS position, with quirks.
|
||||
pub fn parse_quirky(context: &ParserContext,
|
||||
input: &mut Parser,
|
||||
allow_quirks: AllowQuirks)
|
||||
-> Result<Self, ()> {
|
||||
if input.try(|i| i.expect_ident_matching("center")).is_ok() {
|
||||
return Ok(PositionComponent::Center);
|
||||
}
|
||||
if let Ok(lop) = input.try(|i| LengthOrPercentage::parse_quirky(context, i, allow_quirks)) {
|
||||
return Ok(PositionComponent::Length(lop));
|
||||
}
|
||||
let keyword = S::parse(context, input)?;
|
||||
let lop = input.try(|i| LengthOrPercentage::parse_quirky(context, i, allow_quirks)).ok();
|
||||
Ok(PositionComponent::Side(keyword, lop))
|
||||
}
|
||||
}
|
||||
|
||||
impl VerticalPosition {
|
||||
#[inline]
|
||||
/// Initial specified value for vertical position (`top` keyword).
|
||||
pub fn top() -> VerticalPosition {
|
||||
GenericVerticalPosition(PositionValue {
|
||||
keyword: Some(Keyword::Top),
|
||||
position: None,
|
||||
})
|
||||
impl<S> PositionComponent<S> {
|
||||
/// `0%`
|
||||
pub fn zero() -> Self {
|
||||
PositionComponent::Length(LengthOrPercentage::Percentage(Percentage(0.)))
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -124,7 +124,7 @@ fn test_circle() {
|
|||
assert_roundtrip_basicshape!(Circle::parse, "circle(at right 5% bottom 0px)",
|
||||
"circle(at 95% 100%)");
|
||||
assert_roundtrip_basicshape!(Circle::parse, "circle(at right 5% bottom 1px)",
|
||||
"circle(at right 5% bottom 1px)");
|
||||
"circle(at left 95% bottom 1px)");
|
||||
|
||||
assert!(parse(Circle::parse, "circle(at 5% bottom 1px)").is_err());
|
||||
assert!(parse(Circle::parse, "circle(at top 40%)").is_err());
|
||||
|
|
|
@ -20,6 +20,10 @@ fn parse<T, F: Fn(&ParserContext, &mut Parser) -> Result<T, ()>>(f: F, s: &str)
|
|||
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
|
||||
// is preserved in the panic
|
||||
macro_rules! assert_roundtrip_with_context {
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
* 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/. */
|
||||
|
||||
use parsing::parse;
|
||||
use parsing::{parse, parse_entirely};
|
||||
use style::parser::Parse;
|
||||
use style::values::specified::position::*;
|
||||
use style_traits::ToCss;
|
||||
|
@ -28,8 +28,8 @@ fn test_position() {
|
|||
assert_roundtrip_with_context!(Position::parse, "right 10%", "right 10%");
|
||||
|
||||
// Only keywords can be reordered
|
||||
assert!(parse(Position::parse, "top 40%").is_err());
|
||||
assert!(parse(Position::parse, "40% left").is_err());
|
||||
assert!(parse_entirely(Position::parse, "top 40%").is_err());
|
||||
assert!(parse_entirely(Position::parse, "40% left").is_err());
|
||||
|
||||
// 3 and 4 value serialization
|
||||
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");
|
||||
|
||||
// Invalid 3 value positions
|
||||
assert!(parse(Position::parse, "20px 30px 20px").is_err());
|
||||
assert!(parse(Position::parse, "top 30px 20px").is_err());
|
||||
assert!(parse(Position::parse, "50% bottom 20%").is_err());
|
||||
assert!(parse_entirely(Position::parse, "20px 30px 20px").is_err());
|
||||
assert!(parse_entirely(Position::parse, "top 30px 20px").is_err());
|
||||
assert!(parse_entirely(Position::parse, "50% bottom 20%").is_err());
|
||||
|
||||
// Only horizontal and vertical keywords can have positions
|
||||
assert!(parse(Position::parse, "center 10px left 15px").is_err());
|
||||
assert!(parse(Position::parse, "center 10px 15px").is_err());
|
||||
assert!(parse(Position::parse, "center 10px bottom").is_err());
|
||||
assert!(parse_entirely(Position::parse, "center 10px left 15px").is_err());
|
||||
assert!(parse_entirely(Position::parse, "center 10px 15px").is_err());
|
||||
assert!(parse_entirely(Position::parse, "center 10px bottom").is_err());
|
||||
|
||||
// "Horizontal Horizontal" or "Vertical Vertical" positions cause error
|
||||
assert!(parse(Position::parse, "left right").is_err());
|
||||
assert!(parse(Position::parse, "left 10px right").is_err());
|
||||
assert!(parse(Position::parse, "left 10px right 15%").is_err());
|
||||
assert!(parse(Position::parse, "top bottom").is_err());
|
||||
assert!(parse(Position::parse, "top 10px bottom").is_err());
|
||||
assert!(parse(Position::parse, "top 10px bottom 15%").is_err());
|
||||
assert!(parse_entirely(Position::parse, "left right").is_err());
|
||||
assert!(parse_entirely(Position::parse, "left 10px right").is_err());
|
||||
assert!(parse_entirely(Position::parse, "left 10px right 15%").is_err());
|
||||
assert!(parse_entirely(Position::parse, "top bottom").is_err());
|
||||
assert!(parse_entirely(Position::parse, "top 10px bottom").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, "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, "y-start 20px").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());
|
||||
}
|
||||
|
||||
|
@ -82,29 +82,31 @@ fn test_horizontal_position() {
|
|||
assert_roundtrip_with_context!(HorizontalPosition::parse, "center", "center");
|
||||
assert_roundtrip_with_context!(HorizontalPosition::parse, "left", "left");
|
||||
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.
|
||||
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.
|
||||
assert!(parse(HorizontalPosition::parse, "top").is_err());
|
||||
assert!(parse(HorizontalPosition::parse, "bottom").is_err());
|
||||
assert!(parse(HorizontalPosition::parse, "y-start").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, "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, "left top").is_err());
|
||||
assert!(parse(HorizontalPosition::parse, "left right").is_err());
|
||||
assert!(parse(HorizontalPosition::parse, "20px 30px").is_err());
|
||||
assert!(parse_entirely(HorizontalPosition::parse, "20px y-end").is_err());
|
||||
assert!(parse_entirely(HorizontalPosition::parse, "20px top").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]
|
||||
|
@ -115,29 +117,31 @@ fn test_vertical_position() {
|
|||
assert_roundtrip_with_context!(VerticalPosition::parse, "center", "center");
|
||||
assert_roundtrip_with_context!(VerticalPosition::parse, "top", "top");
|
||||
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.
|
||||
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.
|
||||
assert!(parse(VerticalPosition::parse, "left").is_err());
|
||||
assert!(parse(VerticalPosition::parse, "right").is_err());
|
||||
assert!(parse(VerticalPosition::parse, "x-start").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, "20px right").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 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]
|
||||
|
|
|
@ -3,7 +3,6 @@
|
|||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
use app_units::Au;
|
||||
use parsing::parse;
|
||||
use style::values::HasViewportPercentage;
|
||||
use style::values::specified::{AbsoluteLength, NoCalcLength, ViewportPercentageLength};
|
||||
|
||||
|
|
|
@ -9,8 +9,10 @@ use style::properties::longhands::outline_color::computed_value::T as ComputedCo
|
|||
use style::properties::parse_property_declaration_list;
|
||||
use style::values::{RGBA, Auto};
|
||||
use style::values::CustomIdent;
|
||||
use style::values::specified::{BorderStyle, BorderWidth, CSSColor, Length, NoCalcLength};
|
||||
use style::values::specified::{LengthOrPercentage, LengthOrPercentageOrAuto, LengthOrPercentageOrAutoOrContent};
|
||||
use style::values::specified::{BorderStyle, BorderWidth, CSSColor, Length, LengthOrPercentage};
|
||||
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_traits::ToCss;
|
||||
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_repeat as repeat;
|
||||
use style::properties::longhands::mask_size as size;
|
||||
use style::values::generics::position::{HorizontalPosition, Keyword, PositionValue, VerticalPosition};
|
||||
use style::values::specified::Image;
|
||||
use super::*;
|
||||
|
||||
|
@ -833,16 +834,13 @@ mod shorthand_serialization {
|
|||
let mode = single_vec_keyword_value!(mode, luminance);
|
||||
|
||||
let position_x = single_vec_value_typedef!(position_x,
|
||||
HorizontalPosition(PositionValue {
|
||||
keyword: None,
|
||||
position: Some(LengthOrPercentage::Length(NoCalcLength::from_px(7f32))),
|
||||
})
|
||||
PositionComponent::Length(LengthOrPercentage::Length(NoCalcLength::from_px(7f32)))
|
||||
);
|
||||
let position_y = single_vec_value_typedef!(position_y,
|
||||
VerticalPosition(PositionValue {
|
||||
keyword: Some(Keyword::Bottom),
|
||||
position: Some(LengthOrPercentage::Length(NoCalcLength::from_px(4f32))),
|
||||
})
|
||||
PositionComponent::Side(
|
||||
Y::Bottom,
|
||||
Some(LengthOrPercentage::Length(NoCalcLength::from_px(4f32))),
|
||||
)
|
||||
);
|
||||
|
||||
let size = single_vec_variant_value!(size,
|
||||
|
@ -888,17 +886,11 @@ mod shorthand_serialization {
|
|||
let mode = single_vec_keyword_value!(mode, luminance);
|
||||
|
||||
let position_x = single_vec_value_typedef!(position_x,
|
||||
HorizontalPosition(PositionValue {
|
||||
keyword: None,
|
||||
position: Some(LengthOrPercentage::Length(NoCalcLength::from_px(7f32))),
|
||||
})
|
||||
PositionComponent::Length(LengthOrPercentage::Length(NoCalcLength::from_px(7f32)))
|
||||
);
|
||||
|
||||
let position_y = single_vec_value_typedef!(position_y,
|
||||
VerticalPosition(PositionValue {
|
||||
keyword: None,
|
||||
position: Some(LengthOrPercentage::Length(NoCalcLength::from_px(4f32))),
|
||||
})
|
||||
PositionComponent::Length(LengthOrPercentage::Length(NoCalcLength::from_px(4f32)))
|
||||
);
|
||||
|
||||
let size = single_vec_variant_value!(size,
|
||||
|
|
|
@ -25,7 +25,7 @@ use style::stylearc::Arc;
|
|||
use style::stylesheets::{Origin, Namespaces};
|
||||
use style::stylesheets::{Stylesheet, NamespaceRule, CssRule, CssRules, StyleRule, KeyframesRule};
|
||||
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
|
||||
where I: IntoIterator<Item=(PropertyDeclaration, Importance)> {
|
||||
|
@ -183,13 +183,11 @@ fn test_parse_stylesheet() {
|
|||
Importance::Normal),
|
||||
(PropertyDeclaration::BackgroundPositionX(
|
||||
longhands::background_position_x::SpecifiedValue(
|
||||
vec![longhands::background_position_x::single_value
|
||||
::get_initial_position_value()])),
|
||||
Importance::Normal),
|
||||
vec![PositionComponent::zero()])),
|
||||
Importance::Normal),
|
||||
(PropertyDeclaration::BackgroundPositionY(
|
||||
longhands::background_position_y::SpecifiedValue(
|
||||
vec![longhands::background_position_y::single_value
|
||||
::get_initial_position_value()])),
|
||||
vec![PositionComponent::zero()])),
|
||||
Importance::Normal),
|
||||
(PropertyDeclaration::BackgroundRepeat(
|
||||
longhands::background_repeat::SpecifiedValue(
|
||||
|
|
|
@ -25371,7 +25371,7 @@
|
|||
"testharness"
|
||||
],
|
||||
"mozilla/calc.html": [
|
||||
"028fc71bdc9a99d552ba552036d38fb4eef11bc1",
|
||||
"47507adabc0d3642154b3ed4b1ab64d726fa682d",
|
||||
"testharness"
|
||||
],
|
||||
"mozilla/canvas.initial.reset.2dstate.html": [
|
||||
|
|
|
@ -142,7 +142,7 @@ var otherProperties = [
|
|||
['border-width', 'calc(1px)', 'calc(1px)'],
|
||||
['border-spacing', 'calc(1px)', 'calc(1px)'],
|
||||
['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-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%)'],
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue