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 (
207 &HorizontalPosition::Side {
208 side: HorizontalPositionKeyword::Left,
209 offset: Some(ref x_lp),
210 },
211 y,
212 ) if y.is_center() => {
213 x_lp.to_css(dest)
215 }
216 (&HorizontalPosition::Side { side, offset: None }, y) if y.is_center() => {
217 let p: LengthPercentage = side.into();
218 p.to_css(dest)
219 }
220 (x, y_pos @ &VerticalPosition::Side { offset: None, .. }) if x.is_center() => y_pos.to_css(dest),
221 (
222 &HorizontalPosition::Center,
223 y_pos @ &VerticalPosition::Side {
224 side: VerticalPositionKeyword::Bottom,
225 offset: Some(_),
226 },
227 ) => {
228 dest.write_str("center ")?;
230 y_pos.to_css(dest)
231 }
232 (&HorizontalPosition::Side { side: x, offset: None }, &VerticalPosition::Side { side: y, offset: None }) => {
233 let x: LengthPercentage = x.into();
234 let y: LengthPercentage = y.into();
235 x.to_css(dest)?;
236 dest.write_str(" ")?;
237 y.to_css(dest)
238 }
239 (x_pos, y_pos) => {
240 let zero = LengthPercentage::zero();
241 let fifty = LengthPercentage::Percentage(Percentage(0.5));
242 let x_len = match &x_pos {
243 HorizontalPosition::Side {
244 side: HorizontalPositionKeyword::Left,
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 HorizontalPosition::Length(len) if len.is_zero() => Some(&zero),
258 HorizontalPosition::Length(len) => Some(len),
259 HorizontalPosition::Center => Some(&fifty),
260 _ => None,
261 };
262
263 let y_len = match &y_pos {
264 VerticalPosition::Side {
265 side: VerticalPositionKeyword::Top,
266 offset,
267 } => {
268 if let Some(len) = offset {
269 if len.is_zero() {
270 Some(&zero)
271 } else {
272 Some(len)
273 }
274 } else {
275 Some(&zero)
276 }
277 }
278 VerticalPosition::Length(len) if len.is_zero() => Some(&zero),
279 VerticalPosition::Length(len) => Some(len),
280 VerticalPosition::Center => Some(&fifty),
281 _ => None,
282 };
283
284 if let (Some(x), Some(y)) = (x_len, y_len) {
285 x.to_css(dest)?;
286 dest.write_str(" ")?;
287 y.to_css(dest)
288 } else {
289 x_pos.to_css(dest)?;
290 dest.write_str(" ")?;
291 y_pos.to_css(dest)
292 }
293 }
294 }
295 }
296}
297
298impl IsCompatible for Position {
299 fn is_compatible(&self, _browsers: Browsers) -> bool {
300 true
301 }
302}
303
304#[derive(Debug, Clone, PartialEq)]
309#[cfg_attr(feature = "visitor", derive(Visit))]
310#[cfg_attr(
311 feature = "serde",
312 derive(serde::Serialize, serde::Deserialize),
313 serde(tag = "type", rename_all = "kebab-case")
314)]
315#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
316#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
317pub enum PositionComponent<S> {
318 Center,
320 #[cfg_attr(feature = "serde", serde(with = "ValueWrapper::<LengthPercentage>"))]
322 Length(LengthPercentage),
323 Side {
325 side: S,
327 offset: Option<LengthPercentage>,
329 },
330}
331
332impl<S> PositionComponent<S> {
333 fn is_center(&self) -> bool {
334 match self {
335 PositionComponent::Center => true,
336 PositionComponent::Length(LengthPercentage::Percentage(Percentage(p))) => *p == 0.5,
337 _ => false,
338 }
339 }
340
341 fn is_zero(&self) -> bool {
342 matches!(self, PositionComponent::Length(len) if len.is_zero())
343 }
344}
345
346impl<'i, S: Parse<'i>> Parse<'i> for PositionComponent<S> {
347 fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {
348 if input.try_parse(|i| i.expect_ident_matching("center")).is_ok() {
349 return Ok(PositionComponent::Center);
350 }
351
352 if let Ok(lp) = input.try_parse(|input| LengthPercentage::parse(input)) {
353 return Ok(PositionComponent::Length(lp));
354 }
355
356 let side = S::parse(input)?;
357 let offset = input.try_parse(|input| LengthPercentage::parse(input)).ok();
358 Ok(PositionComponent::Side { side, offset })
359 }
360}
361
362impl<S: ToCss> ToCss for PositionComponent<S> {
363 fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
364 where
365 W: std::fmt::Write,
366 {
367 use PositionComponent::*;
368 match &self {
369 Center => {
370 if dest.minify {
371 dest.write_str("50%")
372 } else {
373 dest.write_str("center")
374 }
375 }
376 Length(lp) => lp.to_css(dest),
377 Side { side, offset } => {
378 side.to_css(dest)?;
379 if let Some(lp) = offset {
380 dest.write_str(" ")?;
381 lp.to_css(dest)?;
382 }
383 Ok(())
384 }
385 }
386 }
387}
388
389enum_property! {
390 pub enum HorizontalPositionKeyword {
392 Left,
394 Right,
396 }
397}
398
399impl Into<LengthPercentage> for HorizontalPositionKeyword {
400 fn into(self) -> LengthPercentage {
401 match self {
402 HorizontalPositionKeyword::Left => LengthPercentage::zero(),
403 HorizontalPositionKeyword::Right => LengthPercentage::Percentage(Percentage(1.0)),
404 }
405 }
406}
407
408enum_property! {
409 pub enum VerticalPositionKeyword {
411 Top,
413 Bottom,
415 }
416}
417
418impl Into<LengthPercentage> for VerticalPositionKeyword {
419 fn into(self) -> LengthPercentage {
420 match self {
421 VerticalPositionKeyword::Top => LengthPercentage::zero(),
422 VerticalPositionKeyword::Bottom => LengthPercentage::Percentage(Percentage(1.0)),
423 }
424 }
425}
426
427pub type HorizontalPosition = PositionComponent<HorizontalPositionKeyword>;
429
430pub type VerticalPosition = PositionComponent<VerticalPositionKeyword>;