|
| 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 | +} |
0 commit comments