1use super::length::LengthPercentage;
4use super::percentage::Percentage;
5use crate::error::{ParserError, PrinterError};
6use crate::macros::enum_property;
7use crate::printer::Printer;
8use crate::targets::Browsers;
9use crate::traits::{IsCompatible, Parse, ToCss, Zero};
10#[cfg(feature = "visitor")]
11use crate::visitor::Visit;
12use cssparser::*;
13
14#[cfg(feature = "serde")]
15use crate::serialization::ValueWrapper;
16
17#[derive(Debug, Clone, PartialEq)]
20#[cfg_attr(feature = "visitor", derive(Visit))]
21#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
22#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
23#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
24pub struct Position {
25 pub x: HorizontalPosition,
27 pub y: VerticalPosition,
29}
30
31impl Position {
32 pub fn center() -> Position {
34 Position {
35 x: HorizontalPosition::Center,
36 y: VerticalPosition::Center,
37 }
38 }
39
40 pub fn is_center(&self) -> bool {
42 self.x.is_center() && self.y.is_center()
43 }
44
45 pub fn is_zero(&self) -> bool {
47 self.x.is_zero() && self.y.is_zero()
48 }
49}
50
51impl Default for Position {
52 fn default() -> Position {
53 Position {
54 x: HorizontalPosition::Length(LengthPercentage::Percentage(Percentage(0.0))),
55 y: VerticalPosition::Length(LengthPercentage::Percentage(Percentage(0.0))),
56 }
57 }
58}
59
60impl<'i> Parse<'i> for Position {
61 fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {
62 match input.try_parse(HorizontalPosition::parse) {
63 Ok(HorizontalPosition::Center) => {
64 if let Ok(y) = input.try_parse(VerticalPosition::parse) {
66 return Ok(Position {
67 x: HorizontalPosition::Center,
68 y,
69 });
70 }
71
72 let x = input.try_parse(HorizontalPosition::parse).unwrap_or(HorizontalPosition::Center);
75 let y = VerticalPosition::Center;
76 return Ok(Position { x, y });
77 }
78 Ok(x @ HorizontalPosition::Length(_)) => {
79 if let Ok(y_keyword) = input.try_parse(VerticalPositionKeyword::parse) {
82 let y = VerticalPosition::Side {
83 side: y_keyword,
84 offset: None,
85 };
86 return Ok(Position { x, y });
87 }
88 if let Ok(y_lp) = input.try_parse(LengthPercentage::parse) {
89 let y = VerticalPosition::Length(y_lp);
90 return Ok(Position { x, y });
91 }
92 let y = VerticalPosition::Center;
93 let _ = input.try_parse(|i| i.expect_ident_matching("center"));
94 return Ok(Position { x, y });
95 }
96 Ok(HorizontalPosition::Side {
97 side: x_keyword,
98 offset: lp,
99 }) => {
100 if input.try_parse(|i| i.expect_ident_matching("center")).is_ok() {
103 let x = HorizontalPosition::Side {
104 side: x_keyword,
105 offset: lp,
106 };
107 let y = VerticalPosition::Center;
108 return Ok(Position { x, y });
109 }
110
111 if let Ok(y_keyword) = input.try_parse(VerticalPositionKeyword::parse) {
113 let y_lp = input.try_parse(LengthPercentage::parse).ok();
114 let x = HorizontalPosition::Side {
115 side: x_keyword,
116 offset: lp,
117 };
118 let y = VerticalPosition::Side {
119 side: y_keyword,
120 offset: y_lp,
121 };
122 return Ok(Position { x, y });
123 }
124
125 let x = HorizontalPosition::Side {
127 side: x_keyword,
128 offset: None,
129 };
130 let y = lp.map_or(VerticalPosition::Center, VerticalPosition::Length);
131 return Ok(Position { x, y });
132 }
133 _ => {}
134 }
135
136 let y_keyword = VerticalPositionKeyword::parse(input)?;
138 let lp_and_x_pos: Result<_, ParseError<()>> = input.try_parse(|i| {
139 let y_lp = i.try_parse(LengthPercentage::parse).ok();
140 if let Ok(x_keyword) = i.try_parse(HorizontalPositionKeyword::parse) {
141 let x_lp = i.try_parse(LengthPercentage::parse).ok();
142 let x_pos = HorizontalPosition::Side {
143 side: x_keyword,
144 offset: x_lp,
145 };
146 return Ok((y_lp, x_pos));
147 }
148 i.expect_ident_matching("center")?;
149 let x_pos = HorizontalPosition::Center;
150 Ok((y_lp, x_pos))
151 });
152
153 if let Ok((y_lp, x)) = lp_and_x_pos {
154 let y = VerticalPosition::Side {
155 side: y_keyword,
156 offset: y_lp,
157 };
158 return Ok(Position { x, y });
159 }
160
161 let x = HorizontalPosition::Center;
162 let y = VerticalPosition::Side {
163 side: y_keyword,
164 offset: None,
165 };
166 Ok(Position { x, y })
167 }
168}
169
170impl ToCss for Position {
171 fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
172 where
173 W: std::fmt::Write,
174 {
175 match (&self.x, &self.y) {
176 (x_pos @ &HorizontalPosition::Side { side, offset: Some(_) }, &VerticalPosition::Length(ref y_lp))
177 if side != HorizontalPositionKeyword::Left =>
178 {
179 x_pos.to_css(dest)?;
180 dest.write_str(" top ")?;
181 y_lp.to_css(dest)
182 }
183 (x_pos @ &HorizontalPosition::Side { side, offset: Some(_) }, y)
184 if side != HorizontalPositionKeyword::Left && y.is_center() =>
185 {
186 x_pos.to_css(dest)?;
188 dest.write_str(" center")
189 }
190 (&HorizontalPosition::Length(ref x_lp), y_pos @ &VerticalPosition::Side { side, offset: Some(_) })
191 if side != VerticalPositionKeyword::Top =>
192 {
193 dest.write_str("left ")?;
194 x_lp.to_css(dest)?;
195 dest.write_str(" ")?;
196 y_pos.to_css(dest)
197 }
198 (x, y) if x.is_center() && y.is_center() => {
199 x.to_css(dest)
201 }
202 (&HorizontalPosition::Length(ref x_lp), y) if y.is_center() => {
203 x_lp.to_css(dest)
205 }
206 (&HorizontalPosition::Side { side, offset: None }, y) if y.is_center() => {
207 let p: LengthPercentage = side.into();
208 p.to_css(dest)
209 }
210 (x, y_pos @ &VerticalPosition::Side { offset: None, .. }) if x.is_center() => y_pos.to_css(dest),
211 (&HorizontalPosition::Side { side: x, offset: None }, &VerticalPosition::Side { side: y, offset: None }) => {
212 let x: LengthPercentage = x.into();
213 let y: LengthPercentage = y.into();
214 x.to_css(dest)?;
215 dest.write_str(" ")?;
216 y.to_css(dest)
217 }
218 (x_pos, y_pos) => {
219 let zero = LengthPercentage::zero();
220 let fifty = LengthPercentage::Percentage(Percentage(0.5));
221 let x_len = match &x_pos {
222 HorizontalPosition::Side {
223 side: HorizontalPositionKeyword::Left,
224 offset,
225 } => {
226 if let Some(len) = offset {
227 if len.is_zero() {
228 Some(&zero)
229 } else {
230 Some(len)
231 }
232 } else {
233 Some(&zero)
234 }
235 }
236 HorizontalPosition::Length(len) if len.is_zero() => Some(&zero),
237 HorizontalPosition::Length(len) => Some(len),
238 HorizontalPosition::Center => Some(&fifty),
239 _ => None,
240 };
241
242 let y_len = match &y_pos {
243 VerticalPosition::Side {
244 side: VerticalPositionKeyword::Top,
245 offset,
246 } => {
247 if let Some(len) = offset {
248 if len.is_zero() {
249 Some(&zero)
250 } else {
251 Some(len)
252 }
253 } else {
254 Some(&zero)
255 }
256 }
257 VerticalPosition::Length(len) if len.is_zero() => Some(&zero),
258 VerticalPosition::Length(len) => Some(len),
259 VerticalPosition::Center => Some(&fifty),
260 _ => None,
261 };
262
263 if let (Some(x), Some(y)) = (x_len, y_len) {
264 x.to_css(dest)?;
265 dest.write_str(" ")?;
266 y.to_css(dest)
267 } else {
268 x_pos.to_css(dest)?;
269 dest.write_str(" ")?;
270 y_pos.to_css(dest)
271 }
272 }
273 }
274 }
275}
276
277impl IsCompatible for Position {
278 fn is_compatible(&self, _browsers: Browsers) -> bool {
279 true
280 }
281}
282
283#[derive(Debug, Clone, PartialEq)]
288#[cfg_attr(feature = "visitor", derive(Visit))]
289#[cfg_attr(
290 feature = "serde",
291 derive(serde::Serialize, serde::Deserialize),
292 serde(tag = "type", rename_all = "kebab-case")
293)]
294#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
295#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
296pub enum PositionComponent<S> {
297 Center,
299 #[cfg_attr(feature = "serde", serde(with = "ValueWrapper::<LengthPercentage>"))]
301 Length(LengthPercentage),
302 Side {
304 side: S,
306 offset: Option<LengthPercentage>,
308 },
309}
310
311impl<S> PositionComponent<S> {
312 fn is_center(&self) -> bool {
313 match self {
314 PositionComponent::Center => true,
315 PositionComponent::Length(LengthPercentage::Percentage(Percentage(p))) => *p == 0.5,
316 _ => false,
317 }
318 }
319
320 fn is_zero(&self) -> bool {
321 matches!(self, PositionComponent::Length(len) if len.is_zero())
322 }
323}
324
325impl<'i, S: Parse<'i>> Parse<'i> for PositionComponent<S> {
326 fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {
327 if input.try_parse(|i| i.expect_ident_matching("center")).is_ok() {
328 return Ok(PositionComponent::Center);
329 }
330
331 if let Ok(lp) = input.try_parse(|input| LengthPercentage::parse(input)) {
332 return Ok(PositionComponent::Length(lp));
333 }
334
335 let side = S::parse(input)?;
336 let offset = input.try_parse(|input| LengthPercentage::parse(input)).ok();
337 Ok(PositionComponent::Side { side, offset })
338 }
339}
340
341impl<S: ToCss> ToCss for PositionComponent<S> {
342 fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
343 where
344 W: std::fmt::Write,
345 {
346 use PositionComponent::*;
347 match &self {
348 Center => {
349 if dest.minify {
350 dest.write_str("50%")
351 } else {
352 dest.write_str("center")
353 }
354 }
355 Length(lp) => lp.to_css(dest),
356 Side { side, offset } => {
357 side.to_css(dest)?;
358 if let Some(lp) = offset {
359 dest.write_str(" ")?;
360 lp.to_css(dest)?;
361 }
362 Ok(())
363 }
364 }
365 }
366}
367
368enum_property! {
369 pub enum HorizontalPositionKeyword {
371 Left,
373 Right,
375 }
376}
377
378impl Into<LengthPercentage> for HorizontalPositionKeyword {
379 fn into(self) -> LengthPercentage {
380 match self {
381 HorizontalPositionKeyword::Left => LengthPercentage::zero(),
382 HorizontalPositionKeyword::Right => LengthPercentage::Percentage(Percentage(1.0)),
383 }
384 }
385}
386
387enum_property! {
388 pub enum VerticalPositionKeyword {
390 Top,
392 Bottom,
394 }
395}
396
397impl Into<LengthPercentage> for VerticalPositionKeyword {
398 fn into(self) -> LengthPercentage {
399 match self {
400 VerticalPositionKeyword::Top => LengthPercentage::zero(),
401 VerticalPositionKeyword::Bottom => LengthPercentage::Percentage(Percentage(1.0)),
402 }
403 }
404}
405
406pub type HorizontalPosition = PositionComponent<HorizontalPositionKeyword>;
408
409pub type VerticalPosition = PositionComponent<VerticalPositionKeyword>;