use cssparser::*; use crate::traits::{Parse, ToCss, TryAdd}; use crate::printer::Printer; use super::calc::Calc; use super::percentage::DimensionPercentage; use super::number::serialize_number; use crate::error::{ParserError, PrinterError}; use const_str; /// https://drafts.csswg.org/css-values-4/#typedef-length-percentage pub type LengthPercentage = DimensionPercentage; impl LengthPercentage { pub fn zero() -> LengthPercentage { LengthPercentage::px(0.0) } pub fn px(val: f32) -> LengthPercentage { LengthPercentage::Dimension(LengthValue::Px(val)) } pub(crate) fn to_css_unitless(&self, dest: &mut Printer) -> Result<(), PrinterError> where W: std::fmt::Write { match self { DimensionPercentage::Dimension(d) => d.to_css_unitless(dest), _ => self.to_css(dest) } } } /// ` | auto` #[derive(Debug, Clone, PartialEq)] pub enum LengthPercentageOrAuto { Auto, LengthPercentage(LengthPercentage) } impl<'i> Parse<'i> for LengthPercentageOrAuto { fn parse<'t>(input: &mut Parser<'i, 't>) -> Result>> { if input.try_parse(|i| i.expect_ident_matching("auto")).is_ok() { return Ok(LengthPercentageOrAuto::Auto); } if let Ok(percent) = input.try_parse(|input| LengthPercentage::parse(input)) { return Ok(LengthPercentageOrAuto::LengthPercentage(percent)) } Err(input.new_error_for_next_token()) } } impl ToCss for LengthPercentageOrAuto { fn to_css(&self, dest: &mut Printer) -> Result<(), PrinterError> where W: std::fmt::Write { use LengthPercentageOrAuto::*; match self { Auto => dest.write_str("auto"), LengthPercentage(l) => l.to_css(dest), } } } const PX_PER_IN: f32 = 96.0; const PX_PER_CM: f32 = PX_PER_IN / 2.54; const PX_PER_MM: f32 = PX_PER_CM / 10.0; const PX_PER_Q: f32 = PX_PER_CM / 40.0; const PX_PER_PT: f32 = PX_PER_IN / 72.0; const PX_PER_PC: f32 = PX_PER_IN / 6.0; macro_rules! define_length_units { ( $( $name: ident, )+ ) => { #[derive(Debug, Clone, PartialEq)] pub enum LengthValue { $( $name(f32), )+ } impl<'i> Parse<'i> for LengthValue { fn parse<'t>(input: &mut Parser<'i, 't>) -> Result>> { let location = input.current_source_location(); let token = input.next()?; match *token { Token::Dimension { value, ref unit, .. } => { Ok(match unit { $( s if s.eq_ignore_ascii_case(stringify!($name)) => LengthValue::$name(value), )+ _ => return Err(location.new_unexpected_token_error(token.clone())), }) }, Token::Number { value, .. } => { // TODO: quirks mode only? Ok(LengthValue::Px(value)) } ref token => return Err(location.new_unexpected_token_error(token.clone())), } } } impl LengthValue { pub fn to_unit_value(&self) -> (f32, &str) { match self { $( LengthValue::$name(value) => (*value, const_str::convert_ascii_case!(lower, stringify!($name))), )+ } } } impl TryAdd for LengthValue { fn try_add(&self, other: &LengthValue) -> Option { use LengthValue::*; match (self, other) { $( ($name(a), $name(b)) => Some($name(a + b)), )+ (a, b) => { if let (Some(a), Some(b)) = (a.to_px(), b.to_px()) { Some(Px(a + b)) } else { None } } } } } impl std::ops::Mul for LengthValue { type Output = Self; fn mul(self, other: f32) -> LengthValue { use LengthValue::*; match self { $( $name(value) => $name(value * other), )+ } } } impl std::cmp::PartialEq for LengthValue { fn eq(&self, other: &f32) -> bool { use LengthValue::*; match self { $( $name(value) => value == other, )+ } } } impl std::cmp::PartialOrd for LengthValue { fn partial_cmp(&self, other: &f32) -> Option { use LengthValue::*; match self { $( $name(value) => value.partial_cmp(other), )+ } } } impl std::cmp::PartialOrd for LengthValue { fn partial_cmp(&self, other: &LengthValue) -> Option { use LengthValue::*; match (self, other) { $( ($name(a), $name(b)) => a.partial_cmp(b), )+ (a, b) => { if let (Some(a), Some(b)) = (a.to_px(), b.to_px()) { a.partial_cmp(&b) } else { None } } } } } }; } define_length_units! { // https://www.w3.org/TR/css-values-4/#absolute-lengths Px, In, Cm, Mm, Q, Pt, Pc, // https://www.w3.org/TR/css-values-4/#font-relative-lengths Em, Rem, Ex, Rex, Ch, Rch, Cap, Rcap, Ic, Ric, Lh, Rlh, // https://www.w3.org/TR/css-values-4/#viewport-relative-units Vw, Lvw, Svw, Dvw, Vh, Lvh, Svh, Dvh, Vi, Svi, Lvi, Dvi, Vb, Svb, Lvb, Dvb, Vmin, Svmin, Lvmin, Dvmin, Vmax, Svmax, Lvmax, Dvmax, } impl ToCss for LengthValue { fn to_css(&self, dest: &mut Printer) -> Result<(), PrinterError> where W: std::fmt::Write { let (value, unit) = self.to_unit_value(); // The unit can be omitted if the value is zero, except inside calc() // expressions, where unitless numbers won't be parsed as dimensions. if !dest.in_calc && value == 0.0 { return dest.write_char('0') } serialize_dimension(value, unit, dest) } } impl LengthValue { pub(crate) fn to_css_unitless(&self, dest: &mut Printer) -> Result<(), PrinterError> where W: std::fmt::Write { match self { LengthValue::Px(value) => serialize_number(*value, dest), _ => self.to_css(dest) } } } pub(crate) fn serialize_dimension(value: f32, unit: &str, dest: &mut Printer) -> Result<(), PrinterError> where W: std::fmt::Write { use cssparser::ToCss; let int_value = if value.fract() == 0.0 { Some(value as i32) } else { None }; let token = Token::Dimension { has_sign: value < 0.0, value, int_value, unit: CowRcStr::from(unit) }; if value != 0.0 && value.abs() < 1.0 { let mut s = String::new(); token.to_css(&mut s)?; if value < 0.0 { dest.write_char('-')?; dest.write_str(s.trim_start_matches("-0")) } else { dest.write_str(s.trim_start_matches('0')) } } else { token.to_css(dest)?; Ok(()) } } impl LengthValue { pub fn to_px(&self) -> Option { use LengthValue::*; match self { Px(value) => Some(*value), In(value) => Some(value * PX_PER_IN), Cm(value) => Some(value * PX_PER_CM), Mm(value) => Some(value * PX_PER_MM), Q(value) => Some(value * PX_PER_Q), Pt(value) => Some(value * PX_PER_PT), Pc(value) => Some(value * PX_PER_PC), _ => None } } } #[derive(Debug, Clone, PartialEq)] pub enum Length { Value(LengthValue), Calc(Box>) } impl<'i> Parse<'i> for Length { fn parse<'t>(input: &mut Parser<'i, 't>) -> Result>> { match input.try_parse(Calc::parse) { Ok(Calc::Value(v)) => return Ok(*v), Ok(calc) => return Ok(Length::Calc(Box::new(calc))), _ => {} } let len = LengthValue::parse(input)?; Ok(Length::Value(len)) } } impl ToCss for Length { fn to_css(&self, dest: &mut Printer) -> Result<(), PrinterError> where W: std::fmt::Write { match self { Length::Value(a) => a.to_css(dest), Length::Calc(c) => c.to_css(dest) } } } impl std::ops::Mul for Length { type Output = Self; fn mul(self, other: f32) -> Length { match self { Length::Value(a) => Length::Value(a * other), Length::Calc(a) => Length::Calc(Box::new(*a * other)) } } } impl std::ops::Add for Length { type Output = Self; fn add(self, other: Length) -> Length { match self.try_add(&other) { Some(r) => r, None => self.add(other) } } } impl Length { pub fn zero() -> Length { Length::Value(LengthValue::Px(0.0)) } pub fn px(px: f32) -> Length { Length::Value(LengthValue::Px(px)) } pub fn to_px(&self) -> Option { match self { Length::Value(a) => a.to_px(), _ => None } } fn add(self, other: Length) -> Length { let mut a = self; let mut b = other; if a == 0.0 { return b } if b == 0.0 { return a } if a < 0.0 && b > 0.0 { std::mem::swap(&mut a, &mut b); } match (a, b) { (Length::Calc(a), Length::Calc(b)) => return Length::Calc(Box::new(*a + *b)), (Length::Calc(calc), b) => { if let Calc::Value(a) = *calc { a.add(b) } else { Length::Calc(Box::new(Calc::Sum(Box::new((*calc).into()), Box::new(b.into())))) } } (a, Length::Calc(calc)) => { if let Calc::Value(b) = *calc { a.add(*b) } else { Length::Calc(Box::new(Calc::Sum(Box::new(a.into()), Box::new((*calc).into())))) } } (a, b) => Length::Calc(Box::new(Calc::Sum(Box::new(a.into()), Box::new(b.into())))) } } } impl TryAdd for Length { fn try_add(&self, other: &Length) -> Option { match (self, other) { (Length::Value(a), Length::Value(b)) => { if let Some(res) = a.try_add(b) { Some(Length::Value(res)) } else { None } }, (Length::Calc(a), other) => { match &**a { Calc::Value(v) => v.try_add(other), Calc::Sum(a, b) => { if let Some(res) = Length::Calc(Box::new(*a.clone())).try_add(other) { return Some(res.add(Length::from(*b.clone()))) } if let Some(res) = Length::Calc(Box::new(*b.clone())).try_add(other) { return Some(Length::from(*a.clone()).add(res)) } None } _ => None } } (other, Length::Calc(b)) => { match &**b { Calc::Value(v) => other.try_add(&*v), Calc::Sum(a, b) => { if let Some(res) = other.try_add(&Length::Calc(Box::new(*a.clone()))) { return Some(res.add(Length::from(*b.clone()))) } if let Some(res) = other.try_add(&Length::Calc(Box::new(*b.clone()))) { return Some(Length::from(*a.clone()).add(res)) } None } _ => None } } } } } impl std::convert::Into> for Length { fn into(self) -> Calc { match self { Length::Calc(c) => *c, b => Calc::Value(Box::new(b)) } } } impl std::convert::From> for Length { fn from(calc: Calc) -> Length { Length::Calc(Box::new(calc)) } } impl std::cmp::PartialEq for Length { fn eq(&self, other: &f32) -> bool { match self { Length::Value(a) => *a == *other, Length::Calc(_) => false } } } impl std::cmp::PartialOrd for Length { fn partial_cmp(&self, other: &f32) -> Option { match self { Length::Value(a) => a.partial_cmp(other), Length::Calc(_) => None } } } impl std::cmp::PartialOrd for Length { fn partial_cmp(&self, other: &Length) -> Option { match (self, other) { (Length::Value(a), Length::Value(b)) => a.partial_cmp(b), _ => None } } } #[derive(Debug, Clone, PartialEq)] pub enum LengthOrNumber { Length(Length), Number(f32) } impl Default for LengthOrNumber { fn default() -> LengthOrNumber { LengthOrNumber::Number(0.0) } } impl<'i> Parse<'i> for LengthOrNumber { fn parse<'t>(input: &mut Parser<'i, 't>) -> Result>> { // Parse number first so unitless numbers are not parsed as lengths. if let Ok(number) = input.try_parse(f32::parse) { return Ok(LengthOrNumber::Number(number)) } if let Ok(length) = Length::parse(input) { return Ok(LengthOrNumber::Length(length)) } Err(input.new_error_for_next_token()) } } impl ToCss for LengthOrNumber { fn to_css(&self, dest: &mut Printer) -> Result<(), PrinterError> where W: std::fmt::Write { match self { LengthOrNumber::Length(length) => length.to_css(dest), LengthOrNumber::Number(number) => serialize_number(*number, dest) } } }