use std::fmt::{self, Debug, Formatter}; use std::ops::Add; use typst_utils::Get; use crate::diag::{bail, HintedStrResult}; use crate::foundations::{ cast, AlternativeFold, CastInfo, Dict, Fold, FromValue, IntoValue, Reflect, Resolve, StyleChain, Value, }; use crate::layout::{Abs, Alignment, Axes, Axis, Corner, Rel, Size}; /// A container with left, top, right and bottom components. #[derive(Default, Copy, Clone, Eq, PartialEq, Hash)] pub struct Sides { /// The value for the left side. pub left: T, /// The value for the top side. pub top: T, /// The value for the right side. pub right: T, /// The value for the bottom side. pub bottom: T, } impl Sides { /// Create a new instance from the four components. pub const fn new(left: T, top: T, right: T, bottom: T) -> Self { Self { left, top, right, bottom } } /// Create an instance with four equal components. pub fn splat(value: T) -> Self where T: Clone, { Self { left: value.clone(), top: value.clone(), right: value.clone(), bottom: value, } } /// Map the individual fields with `f`. pub fn map(self, mut f: F) -> Sides where F: FnMut(T) -> U, { Sides { left: f(self.left), top: f(self.top), right: f(self.right), bottom: f(self.bottom), } } /// Convert from `&Sides` to `Sides<&T>`. pub fn as_ref(&self) -> Sides<&T> { Sides { left: &self.left, top: &self.top, right: &self.right, bottom: &self.bottom, } } /// Zip two instances into one. pub fn zip(self, other: Sides) -> Sides<(T, U)> { Sides { left: (self.left, other.left), top: (self.top, other.top), right: (self.right, other.right), bottom: (self.bottom, other.bottom), } } /// An iterator over the sides, starting with the left side, clockwise. pub fn iter(&self) -> impl Iterator { [&self.left, &self.top, &self.right, &self.bottom].into_iter() } /// Whether all sides are equal. pub fn is_uniform(&self) -> bool where T: PartialEq, { self.left == self.top && self.top == self.right && self.right == self.bottom } } impl Sides { /// Sums up `left` and `right` into `x`, and `top` and `bottom` into `y`. pub fn sum_by_axis(self) -> Axes { Axes::new(self.left + self.right, self.top + self.bottom) } } impl Sides> { /// Unwrap-or-default the individual sides. pub fn unwrap_or_default(self) -> Sides where T: Default, { self.map(Option::unwrap_or_default) } } impl Sides> { /// Evaluate the sides relative to the given `size`. pub fn relative_to(&self, size: Size) -> Sides { Sides { left: self.left.relative_to(size.x), top: self.top.relative_to(size.y), right: self.right.relative_to(size.x), bottom: self.bottom.relative_to(size.y), } } /// Whether all sides are zero. pub fn is_zero(&self) -> bool { self.left.is_zero() && self.top.is_zero() && self.right.is_zero() && self.bottom.is_zero() } } impl Get for Sides { type Component = T; fn get_ref(&self, side: Side) -> &T { match side { Side::Left => &self.left, Side::Top => &self.top, Side::Right => &self.right, Side::Bottom => &self.bottom, } } fn get_mut(&mut self, side: Side) -> &mut T { match side { Side::Left => &mut self.left, Side::Top => &mut self.top, Side::Right => &mut self.right, Side::Bottom => &mut self.bottom, } } } impl Debug for Sides { fn fmt(&self, f: &mut Formatter) -> fmt::Result { if self.is_uniform() { f.write_str("Sides::splat(")?; self.left.fmt(f)?; f.write_str(")") } else { f.debug_struct("Sides") .field("left", &self.left) .field("top", &self.top) .field("right", &self.right) .field("bottom", &self.bottom) .finish() } } } impl Reflect for Sides> { fn input() -> CastInfo { T::input() + Dict::input() } fn output() -> CastInfo { T::output() + Dict::output() } fn castable(value: &Value) -> bool { Dict::castable(value) || T::castable(value) } } impl IntoValue for Sides> where T: PartialEq + IntoValue, { fn into_value(self) -> Value { if self.is_uniform() { if let Some(left) = self.left { return left.into_value(); } } let mut dict = Dict::new(); let mut handle = |key: &str, component: Option| { if let Some(c) = component { dict.insert(key.into(), c.into_value()); } }; handle("left", self.left); handle("top", self.top); handle("right", self.right); handle("bottom", self.bottom); Value::Dict(dict) } } impl FromValue for Sides> where T: Default + FromValue + Clone, { fn from_value(mut value: Value) -> HintedStrResult { let expected_keys = ["left", "top", "right", "bottom", "x", "y", "rest"]; if let Value::Dict(dict) = &mut value { if dict.is_empty() { return Ok(Self::splat(None)); } else if dict.iter().any(|(key, _)| expected_keys.contains(&key.as_str())) { let mut take = |key| dict.take(key).ok().map(T::from_value).transpose(); let rest = take("rest")?; let x = take("x")?.or_else(|| rest.clone()); let y = take("y")?.or_else(|| rest.clone()); let sides = Sides { left: take("left")?.or_else(|| x.clone()), top: take("top")?.or_else(|| y.clone()), right: take("right")?.or_else(|| x.clone()), bottom: take("bottom")?.or_else(|| y.clone()), }; dict.finish(&expected_keys)?; return Ok(sides); } } if T::castable(&value) { Ok(Self::splat(Some(T::from_value(value)?))) } else if let Value::Dict(dict) = &value { let keys = dict.iter().map(|kv| kv.0.as_str()).collect(); // Do not hint at expected_keys, because T may be castable from Dict // objects with other sets of expected keys. Err(Dict::unexpected_keys(keys, None).into()) } else { Err(Self::error(&value)) } } } impl Resolve for Sides { type Output = Sides; fn resolve(self, styles: StyleChain) -> Self::Output { self.map(|v| v.resolve(styles)) } } impl Fold for Sides> { fn fold(self, outer: Self) -> Self { // Usually, folding an inner `None` with an `outer` prefers the // explicit `None`. However, here `None` means unspecified and thus // we want `outer`, so we use `fold_or` to opt into such behavior. self.zip(outer).map(|(inner, outer)| inner.fold_or(outer)) } } /// The four sides of objects. #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] pub enum Side { /// The left side. Left, /// The top side. Top, /// The right side. Right, /// The bottom side. Bottom, } impl Side { /// The opposite side. pub fn inv(self) -> Self { match self { Self::Left => Self::Right, Self::Top => Self::Bottom, Self::Right => Self::Left, Self::Bottom => Self::Top, } } /// The next side, clockwise. pub fn next_cw(self) -> Self { match self { Self::Left => Self::Top, Self::Top => Self::Right, Self::Right => Self::Bottom, Self::Bottom => Self::Left, } } /// The next side, counter-clockwise. pub fn next_ccw(self) -> Self { match self { Self::Left => Self::Bottom, Self::Top => Self::Left, Self::Right => Self::Top, Self::Bottom => Self::Right, } } /// The first corner of the side in clockwise order. pub fn start_corner(self) -> Corner { match self { Self::Left => Corner::BottomLeft, Self::Top => Corner::TopLeft, Self::Right => Corner::TopRight, Self::Bottom => Corner::BottomRight, } } /// The second corner of the side in clockwise order. pub fn end_corner(self) -> Corner { self.next_cw().start_corner() } /// Return the corresponding axis. pub fn axis(self) -> Axis { match self { Self::Left | Self::Right => Axis::Y, Self::Top | Self::Bottom => Axis::X, } } } cast! { Side, self => Alignment::from(self).into_value(), align: Alignment => match align { Alignment::LEFT => Self::Left, Alignment::RIGHT => Self::Right, Alignment::TOP => Self::Top, Alignment::BOTTOM => Self::Bottom, _ => bail!("cannot convert this alignment to a side"), }, }