Skip to content

Commit 71c46e6

Browse files
committed
Background position parsing
1 parent 7645fd2 commit 71c46e6

File tree

4 files changed

+195
-3
lines changed

4 files changed

+195
-3
lines changed

src/properties/background.rs

Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
use cssparser::*;
2+
use crate::values::length::{HorizontalPosition, HorizontalPositionKeyword, VerticalPosition, VerticalPositionKeyword, LengthPercentage};
3+
use crate::values::traits::{Parse, ToCss};
4+
5+
/// https://www.w3.org/TR/css-backgrounds-3/#background-position
6+
#[derive(Debug, Clone, PartialEq)]
7+
pub struct BackgroundPosition {
8+
x: HorizontalPosition,
9+
y: VerticalPosition
10+
}
11+
12+
impl Parse for BackgroundPosition {
13+
fn parse<'i, 't>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ()>> {
14+
match input.try_parse(HorizontalPosition::parse) {
15+
Ok(HorizontalPosition::Center) => {
16+
// Try parsing a vertical position next.
17+
if let Ok(y) = input.try_parse(VerticalPosition::parse) {
18+
return Ok(BackgroundPosition {
19+
x: HorizontalPosition::Center,
20+
y
21+
})
22+
}
23+
24+
// If it didn't work, assume the first actually represents a y position,
25+
// and the next is an x position. e.g. `center left` rather than `left center`.
26+
let x = input
27+
.try_parse(HorizontalPosition::parse)
28+
.unwrap_or(HorizontalPosition::Center);
29+
let y = VerticalPosition::Center;
30+
return Ok(BackgroundPosition { x, y })
31+
},
32+
Ok(x @ HorizontalPosition::Length(_)) => {
33+
// If we got a length as the first component, then the second must
34+
// be a keyword or length (not a side offset).
35+
if let Ok(y_keyword) = input.try_parse(VerticalPositionKeyword::parse) {
36+
let y = VerticalPosition::Side(y_keyword, None);
37+
return Ok(BackgroundPosition { x, y });
38+
}
39+
if let Ok(y_lp) = input.try_parse(LengthPercentage::parse) {
40+
let y = VerticalPosition::Length(y_lp);
41+
return Ok(BackgroundPosition { x, y });
42+
}
43+
let y = VerticalPosition::Center;
44+
let _ = input.try_parse(|i| i.expect_ident_matching("center"));
45+
return Ok(BackgroundPosition { x, y });
46+
}
47+
Ok(HorizontalPosition::Side(x_keyword, lp)) => {
48+
// If we got a horizontal side keyword (and optional offset), expect another for the vertical side.
49+
// e.g. `left center` or `left 20px center`
50+
if input.try_parse(|i| i.expect_ident_matching("center")).is_ok() {
51+
let x = HorizontalPosition::Side(x_keyword, lp);
52+
let y = VerticalPosition::Center;
53+
return Ok(BackgroundPosition { x, y });
54+
}
55+
56+
// e.g. `left top`, `left top 20px`, `left 20px top`, or `left 20px top 20px`
57+
if let Ok(y_keyword) = input.try_parse(VerticalPositionKeyword::parse) {
58+
let y_lp = input.try_parse(LengthPercentage::parse).ok();
59+
let x = HorizontalPosition::Side(x_keyword, lp);
60+
let y = VerticalPosition::Side(y_keyword, y_lp);
61+
return Ok(BackgroundPosition { x, y });
62+
}
63+
64+
// If we didn't get a vertical side keyword (e.g. `left 20px`), then apply the offset to the vertical side.
65+
let x = HorizontalPosition::Side(x_keyword, None);
66+
let y = lp.map_or(VerticalPosition::Center, VerticalPosition::Length);
67+
return Ok(BackgroundPosition { x, y });
68+
}
69+
_ => {}
70+
}
71+
72+
// If the horizontal position didn't parse, then it must be out of order. Try vertical position keyword.
73+
let y_keyword = VerticalPositionKeyword::parse(input)?;
74+
let lp_and_x_pos: Result<_, ParseError<()>> = input.try_parse(|i| {
75+
let y_lp = i.try_parse(LengthPercentage::parse).ok();
76+
if let Ok(x_keyword) = i.try_parse(HorizontalPositionKeyword::parse) {
77+
let x_lp = i.try_parse(LengthPercentage::parse).ok();
78+
let x_pos = HorizontalPosition::Side(x_keyword, x_lp);
79+
return Ok((y_lp, x_pos));
80+
}
81+
i.expect_ident_matching("center")?;
82+
let x_pos = HorizontalPosition::Center;
83+
Ok((y_lp, x_pos))
84+
});
85+
86+
if let Ok((y_lp, x)) = lp_and_x_pos {
87+
let y = VerticalPosition::Side(y_keyword, y_lp);
88+
return Ok(BackgroundPosition { x, y });
89+
}
90+
91+
let x = HorizontalPosition::Center;
92+
let y = VerticalPosition::Side(y_keyword, None);
93+
Ok(BackgroundPosition { x, y })
94+
}
95+
}
96+
97+
impl ToCss for BackgroundPosition {
98+
fn to_css<W>(&self, dest: &mut W) -> std::fmt::Result where W: std::fmt::Write {
99+
match (&self.x, &self.y) {
100+
(
101+
x_pos @ &HorizontalPosition::Side(_, Some(_)),
102+
&VerticalPosition::Length(ref y_lp),
103+
) => {
104+
x_pos.to_css(dest)?;
105+
dest.write_str(" top ")?;
106+
y_lp.to_css(dest)
107+
},
108+
(
109+
&HorizontalPosition::Length(ref x_lp),
110+
y_pos @ &VerticalPosition::Side(_, Some(_)),
111+
) => {
112+
dest.write_str("left ")?;
113+
x_lp.to_css(dest)?;
114+
dest.write_str(" ")?;
115+
y_pos.to_css(dest)
116+
},
117+
(x_pos, y_pos) => {
118+
x_pos.to_css(dest)?;
119+
dest.write_str(" ")?;
120+
y_pos.to_css(dest)
121+
},
122+
}
123+
}
124+
}

src/properties/mod.rs

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,20 @@
11
mod custom;
22
pub mod margin_padding;
3+
pub mod background;
34

45
use cssparser::*;
56
use custom::*;
7+
use background::*;
68
use crate::values::{image::*, length::*, border::*, border_image::*, border_radius::*, rect::*, color::*};
79
use super::values::traits::{Parse, ToCss};
810

911
#[derive(Debug, Clone)]
1012
pub enum Property {
1113
BackgroundColor(CssColor),
1214
BackgroundImage(Vec<Image>),
13-
// BackgroundPositionX
14-
// BackgroundPositionY
15-
// BackgroundPosition
15+
BackgroundPositionX(HorizontalPosition),
16+
BackgroundPositionY(VerticalPosition),
17+
BackgroundPosition(BackgroundPosition),
1618
// BackgroundSize
1719
// BackgroundRepeat
1820
// BackgroundAttachment
@@ -191,6 +193,9 @@ impl Property {
191193
match name.as_ref() {
192194
"background-color" => property!(BackgroundColor, CssColor),
193195
"background-image" => property!(BackgroundImage, Image, true),
196+
"background-position-x" => property!(BackgroundPositionX, HorizontalPosition),
197+
"background-position-y" => property!(BackgroundPositionY, VerticalPosition),
198+
"background-position" => property!(BackgroundPosition, BackgroundPosition),
194199
"color" => property!(Color, CssColor),
195200
"width" => property!(Width, Size),
196201
"height" => property!(Height, Size),
@@ -350,6 +355,9 @@ impl Property {
350355
match self {
351356
BackgroundColor(color) => property!("background-color", color),
352357
BackgroundImage(image) => property!("background-image", image, true),
358+
BackgroundPositionX(val) => property!("background-position-x", val),
359+
BackgroundPositionY(val) => property!("background-position-y", val),
360+
BackgroundPosition(val) => property!("background-position", val),
353361
Color(color) => property!("color", color),
354362
Width(val) => property!("width", val),
355363
Height(val) => property!("height", val),

src/values/length.rs

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
use cssparser::*;
22
use super::traits::{Parse, ToCss};
3+
use super::macros::enum_property;
34

45
/// https://drafts.csswg.org/css-sizing-3/#specifying-sizes
56
@@ -417,3 +418,60 @@ pub fn serialize_number<W>(number: f32, dest: &mut W) -> std::fmt::Result where
417418
};
418419
tok.to_css(dest)
419420
}
421+
422+
#[derive(Debug, Clone, PartialEq)]
423+
pub enum Position<S> {
424+
/// `center`
425+
Center,
426+
/// `<length-percentage>`
427+
Length(LengthPercentage),
428+
/// `<side> <length-percentage>?`
429+
Side(S, Option<LengthPercentage>),
430+
}
431+
432+
impl<S: Parse> Parse for Position<S> {
433+
fn parse<'i, 't>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ()>> {
434+
if input.try_parse(|i| i.expect_ident_matching("center")).is_ok() {
435+
return Ok(Position::Center);
436+
}
437+
438+
if let Ok(lp) = input.try_parse(|input| LengthPercentage::parse(input)) {
439+
return Ok(Position::Length(lp));
440+
}
441+
442+
let keyword = S::parse(input)?;
443+
let lp = input.try_parse(|input| LengthPercentage::parse(input)).ok();
444+
Ok(Position::Side(keyword, lp))
445+
}
446+
}
447+
448+
impl<S: ToCss> ToCss for Position<S> {
449+
fn to_css<W>(&self, dest: &mut W) -> std::fmt::Result where W: std::fmt::Write {
450+
use Position::*;
451+
match &self {
452+
Center => dest.write_str("center"),
453+
Length(lp) => lp.to_css(dest),
454+
Side(s, lp) => {
455+
s.to_css(dest)?;
456+
if let Some(lp) = lp {
457+
dest.write_str(" ")?;
458+
lp.to_css(dest)?;
459+
}
460+
Ok(())
461+
}
462+
}
463+
}
464+
}
465+
466+
enum_property!(HorizontalPositionKeyword,
467+
Left,
468+
Right
469+
);
470+
471+
enum_property!(VerticalPositionKeyword,
472+
Top,
473+
Bottom
474+
);
475+
476+
pub type HorizontalPosition = Position<HorizontalPositionKeyword>;
477+
pub type VerticalPosition = Position<VerticalPositionKeyword>;

test.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ css.transform({
77
.foo + .bar:not(.baz) {
88
background-color: blue;
99
background-image: url(img.png), url('test.jpg');
10+
background-position-x: right 20px;
11+
background-position-y: top 20px;
1012
color: blue;
1113
width: 250px;
1214
height: 50%;

0 commit comments

Comments
 (0)