use super::{CssDimension, Dimension, Number, Unit};
use num_traits::one;
use std::fmt::{self, Display};
use std::ops::{Div, Mul};
#[derive(Clone, PartialEq, Eq)]
pub struct UnitSet {
units: Vec<(Unit, i8)>,
}
impl UnitSet {
pub fn scalar() -> Self {
Self { units: vec![] }
}
pub fn is_none(&self) -> bool {
self.units.iter().all(|(u, _)| *u == Unit::None)
}
pub(crate) fn is_pos(&self) -> bool {
self.units.iter().any(|(u, p)| *u != Unit::None && *p > 0)
}
pub fn is_known(&self) -> bool {
!self
.units
.iter()
.any(|(u, _)| matches!(u, Unit::Unknown(_)))
}
pub fn is_percent(&self) -> bool {
self.units == [(Unit::Percent, 1)]
}
pub fn is_compatible(&self, other: &Self) -> bool {
self.is_none()
|| other.is_none()
|| self.dimension() == other.dimension()
}
pub(crate) fn dimension(&self) -> Vec<(Dimension, i8)> {
use std::collections::BTreeMap;
self.units
.iter()
.map(|(unit, p)| (unit.dimension(), p))
.filter(|(dim, _p)| *dim != Dimension::None)
.fold(BTreeMap::new(), |mut map, (dim, power)| {
*map.entry(dim).or_insert(0) += *power;
map
})
.into_iter()
.filter(|(_d, power)| *power != 0)
.collect()
}
pub(crate) fn css_dimension(&self) -> CssDimensionSet {
use std::collections::BTreeMap;
let dim = self
.units
.iter()
.map(|(unit, p)| (CssDimension::from(unit.dimension()), p))
.filter(|(dim, _p)| *dim != CssDimension::None)
.fold(BTreeMap::new(), |mut map, (dim, power)| {
*map.entry(dim).or_insert(0) += *power;
map
})
.into_iter()
.filter(|(_d, power)| *power != 0)
.collect();
CssDimensionSet { dim }
}
pub(crate) fn valid_in_css(&self) -> bool {
self.units.len() < 2 && self.css_dimension().valid_in_css()
}
pub fn scale_to(&self, other: &Self) -> Option<Number> {
if let [(u, 1)] = other.units.as_slice() {
self.scale_to_unit(u)
} else if other.is_none() {
self.scale_to_unit(&Unit::None)
} else {
let quote = self / other;
if quote.dimension().is_empty() {
Some(quote.units.iter().fold(one(), |a, (unit, power)| {
a * unit.scale_factor().powi((*power).into())
}))
} else {
None
}
}
}
pub fn scale_to_unit(&self, other: &Unit) -> Option<Number> {
if let [(u, 1)] = self.units.as_slice() {
u.scale_to(other)
} else if self.is_none() {
Unit::None.scale_to(other)
} else {
None
}
}
pub fn simplify(&mut self) -> Number {
let mut factor = one();
if self.units.len() > 1 {
for i in 1..(self.units.len()) {
let (a, b) = self.units.split_at_mut(i);
let (au, ap) = a.last_mut().unwrap();
for (bu, bp) in b {
if let Some(f) = bu.scale_to(au) {
if ap.abs() > bp.abs() {
factor = factor * f.powi((*bp).into());
*ap += *bp;
*bp = 0;
} else {
factor = factor / f.powi((*ap).into());
*bp += *ap;
*ap = 0;
}
}
}
}
}
self.units.retain(|(_u, p)| *p != 0);
factor
}
}
impl Div for &UnitSet {
type Output = UnitSet;
fn div(self, rhs: Self) -> Self::Output {
let mut result = self.clone();
'rhs: for (ru, rp) in &rhs.units {
for (lu, lp) in &mut result.units {
if lu == ru {
*lp -= rp;
continue 'rhs;
}
}
result.units.push((ru.clone(), -rp));
}
result.units.retain(|(_u, p)| *p != 0);
result
}
}
impl Mul for &UnitSet {
type Output = UnitSet;
fn mul(self, rhs: Self) -> Self::Output {
let mut result = self.clone();
'rhs: for (ru, rp) in &rhs.units {
for (lu, lp) in &mut result.units {
if lu == ru {
*lp += rp;
continue 'rhs;
}
}
result.units.push((ru.clone(), *rp));
}
result.units.retain(|(_u, p)| *p != 0);
result
}
}
impl From<Unit> for UnitSet {
fn from(unit: Unit) -> Self {
Self {
units: if unit == Unit::None {
vec![]
} else {
vec![(unit, 1)]
},
}
}
}
impl Display for UnitSet {
fn fmt(&self, out: &mut fmt::Formatter) -> fmt::Result {
let short = out.alternate();
let mut pos = self.units.iter().filter(|(_u, p)| *p > 0);
let mut neg = self.units.iter().filter(|(_u, p)| *p < 0);
if let Some((u, p)) = pos.next() {
write_one(out, u, *p)?;
for (u, p) in pos {
out.write_str(if short { "*" } else { " * 1" })?;
write_one(out, u, p.abs())?;
}
if let Some((u, p)) = neg.next() {
out.write_str(if short { "/" } else { " / 1" })?;
write_one(out, u, p.abs())?;
for (u, p) in neg {
out.write_str(if short { "*" } else { " / 1" })?;
write_one(out, u, p.abs())?;
}
}
} else if short {
match (neg.next(), neg.next()) {
(None, _) => (),
(Some((u, p)), None) => {
write_one(out, u, *p)?;
}
(Some((u, p)), Some((nu, np))) => {
out.write_str("(")?;
write_one(out, u, p.abs())?;
out.write_str("*")?;
write_one(out, nu, np.abs())?;
for (u, p) in neg {
out.write_str("*")?;
write_one(out, u, p.abs())?;
}
out.write_str(")^-1")?;
}
}
} else {
for (u, p) in neg {
out.write_str(" / 1")?;
write_one(out, u, p.abs())?;
}
}
Ok(())
}
}
fn write_one(out: &mut fmt::Formatter, u: &Unit, p: i8) -> fmt::Result {
u.fmt(out)?;
if (0..=3).contains(&p) {
for _ in 1..p {
write!(out, " * 1{u}")?;
}
} else {
write!(out, "^{p}")?;
}
Ok(())
}
impl fmt::Debug for UnitSet {
fn fmt(&self, out: &mut fmt::Formatter) -> fmt::Result {
out.write_str("UnitSet ")?;
out.debug_list().entries(&self.units).finish()
}
}
#[derive(Debug, Default, PartialEq, Eq)]
pub struct CssDimensionSet {
dim: Vec<(CssDimension, i8)>,
}
impl CssDimensionSet {
pub fn is_empty(&self) -> bool {
self.dim.is_empty()
}
pub fn is_unknown(&self) -> bool {
self.dim
.iter()
.any(|(dim, _)| matches!(dim, CssDimension::Unknown(_)))
}
pub(crate) fn valid_in_css(&self) -> bool {
match &self.dim[..] {
[] => true,
[(_d, p)] => *p == 1,
_ => false,
}
}
}