Skip to main content

typst_library/layout/
sides.rs

1use std::fmt::{self, Debug, Formatter};
2use std::ops::Add;
3
4use typst_utils::{Get, Numeric};
5
6use crate::diag::{HintedStrResult, bail};
7use crate::foundations::{
8    AlternativeFold, CastInfo, Dict, Fold, FromValue, IntoValue, Reflect, Resolve,
9    StyleChain, Value, cast,
10};
11use crate::layout::{Abs, Alignment, Axes, Axis, Corner, Corners, Rel, Size};
12
13/// A container with left, top, right and bottom components.
14#[derive(Default, Copy, Clone, Eq, PartialEq, Hash)]
15pub struct Sides<T> {
16    /// The value for the left side.
17    pub left: T,
18    /// The value for the top side.
19    pub top: T,
20    /// The value for the right side.
21    pub right: T,
22    /// The value for the bottom side.
23    pub bottom: T,
24}
25
26impl<T> Sides<T> {
27    /// Create a new instance from the four components.
28    pub const fn new(left: T, top: T, right: T, bottom: T) -> Self {
29        Self { left, top, right, bottom }
30    }
31
32    /// Create an instance with four equal components.
33    pub fn splat(value: T) -> Self
34    where
35        T: Clone,
36    {
37        Self {
38            left: value.clone(),
39            top: value.clone(),
40            right: value.clone(),
41            bottom: value,
42        }
43    }
44
45    /// Map the individual fields with `f`.
46    pub fn map<F, U>(self, mut f: F) -> Sides<U>
47    where
48        F: FnMut(T) -> U,
49    {
50        Sides {
51            left: f(self.left),
52            top: f(self.top),
53            right: f(self.right),
54            bottom: f(self.bottom),
55        }
56    }
57
58    /// Convert from `&Sides<T>` to `Sides<&T>`.
59    pub fn as_ref(&self) -> Sides<&T> {
60        Sides {
61            left: &self.left,
62            top: &self.top,
63            right: &self.right,
64            bottom: &self.bottom,
65        }
66    }
67
68    /// Zip two instances into one.
69    pub fn zip<U>(self, other: Sides<U>) -> Sides<(T, U)> {
70        Sides {
71            left: (self.left, other.left),
72            top: (self.top, other.top),
73            right: (self.right, other.right),
74            bottom: (self.bottom, other.bottom),
75        }
76    }
77
78    /// An iterator over the sides, starting with the left side, clockwise.
79    pub fn iter(&self) -> impl Iterator<Item = &T> {
80        [&self.left, &self.top, &self.right, &self.bottom].into_iter()
81    }
82
83    /// Map two adjacent sides into a corner `f`.
84    pub fn map_corners<F, U>(self, mut f: F) -> Corners<U>
85    where
86        F: FnMut(T, T) -> U,
87        T: Copy,
88    {
89        Corners {
90            top_left: f(self.left, self.top),
91            top_right: f(self.top, self.right),
92            bottom_right: f(self.right, self.bottom),
93            bottom_left: f(self.bottom, self.left),
94        }
95    }
96
97    /// Whether all sides are equal.
98    pub fn is_uniform(&self) -> bool
99    where
100        T: PartialEq,
101    {
102        self.left == self.top && self.top == self.right && self.right == self.bottom
103    }
104}
105
106impl<T: Add> Sides<T> {
107    /// Sums up `left` and `right` into `x`, and `top` and `bottom` into `y`.
108    pub fn sum_by_axis(self) -> Axes<T::Output> {
109        Axes::new(self.left + self.right, self.top + self.bottom)
110    }
111}
112
113impl<T> Sides<Option<T>> {
114    /// Unwrap-or the individual sides.
115    pub fn unwrap_or(self, default: T) -> Sides<T>
116    where
117        T: Clone,
118    {
119        self.map(|v| v.unwrap_or(default.clone()))
120    }
121
122    /// Unwrap-or-default the individual sides.
123    pub fn unwrap_or_default(self) -> Sides<T>
124    where
125        T: Default,
126    {
127        self.map(Option::unwrap_or_default)
128    }
129}
130
131impl Sides<Rel<Abs>> {
132    /// Evaluate the sides relative to the given `size`.
133    pub fn relative_to(&self, size: Size) -> Sides<Abs> {
134        Sides {
135            left: self.left.relative_to(size.x),
136            top: self.top.relative_to(size.y),
137            right: self.right.relative_to(size.x),
138            bottom: self.bottom.relative_to(size.y),
139        }
140    }
141}
142
143impl<T: Numeric> Sides<T> {
144    /// Whether all sides are zero.
145    pub fn is_zero(&self) -> bool {
146        self.left.is_zero()
147            && self.top.is_zero()
148            && self.right.is_zero()
149            && self.bottom.is_zero()
150    }
151}
152
153impl<T> Get<Side> for Sides<T> {
154    type Component = T;
155
156    fn get_ref(&self, side: Side) -> &T {
157        match side {
158            Side::Left => &self.left,
159            Side::Top => &self.top,
160            Side::Right => &self.right,
161            Side::Bottom => &self.bottom,
162        }
163    }
164
165    fn get_mut(&mut self, side: Side) -> &mut T {
166        match side {
167            Side::Left => &mut self.left,
168            Side::Top => &mut self.top,
169            Side::Right => &mut self.right,
170            Side::Bottom => &mut self.bottom,
171        }
172    }
173}
174
175impl<T: Debug + PartialEq> Debug for Sides<T> {
176    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
177        if self.is_uniform() {
178            f.write_str("Sides::splat(")?;
179            self.left.fmt(f)?;
180            f.write_str(")")
181        } else {
182            f.debug_struct("Sides")
183                .field("left", &self.left)
184                .field("top", &self.top)
185                .field("right", &self.right)
186                .field("bottom", &self.bottom)
187                .finish()
188        }
189    }
190}
191
192impl<T: Reflect> Reflect for Sides<Option<T>> {
193    fn input() -> CastInfo {
194        T::input() + Dict::input()
195    }
196
197    fn output() -> CastInfo {
198        T::output() + Dict::output()
199    }
200
201    fn castable(value: &Value) -> bool {
202        Dict::castable(value) || T::castable(value)
203    }
204}
205
206impl<T> IntoValue for Sides<Option<T>>
207where
208    T: PartialEq + IntoValue,
209{
210    fn into_value(self) -> Value {
211        if self.is_uniform()
212            && let Some(left) = self.left
213        {
214            return left.into_value();
215        }
216
217        let mut dict = Dict::new();
218        let mut handle = |key: &str, component: Option<T>| {
219            if let Some(c) = component {
220                dict.insert(key.into(), c.into_value());
221            }
222        };
223
224        handle("left", self.left);
225        handle("top", self.top);
226        handle("right", self.right);
227        handle("bottom", self.bottom);
228
229        Value::Dict(dict)
230    }
231}
232
233impl<T> FromValue for Sides<Option<T>>
234where
235    T: Default + FromValue + Clone,
236{
237    fn from_value(mut value: Value) -> HintedStrResult<Self> {
238        let expected_keys = ["left", "top", "right", "bottom", "x", "y", "rest"];
239        if let Value::Dict(dict) = &mut value {
240            if dict.is_empty() {
241                return Ok(Self::splat(None));
242            } else if dict.iter().any(|(key, _)| expected_keys.contains(&key.as_str())) {
243                let mut take = |key| dict.take(key).ok().map(T::from_value).transpose();
244                let rest = take("rest")?;
245                let x = take("x")?.or_else(|| rest.clone());
246                let y = take("y")?.or_else(|| rest.clone());
247                let sides = Sides {
248                    left: take("left")?.or_else(|| x.clone()),
249                    top: take("top")?.or_else(|| y.clone()),
250                    right: take("right")?.or_else(|| x.clone()),
251                    bottom: take("bottom")?.or_else(|| y.clone()),
252                };
253
254                dict.finish(&expected_keys)?;
255                return Ok(sides);
256            }
257        }
258
259        if T::castable(&value) {
260            Ok(Self::splat(Some(T::from_value(value)?)))
261        } else if let Value::Dict(dict) = &value {
262            let keys = dict.iter().map(|kv| kv.0.as_str()).collect();
263            // Do not hint at expected_keys, because T may be castable from Dict
264            // objects with other sets of expected keys.
265            Err(Dict::unexpected_keys(keys, None).into())
266        } else {
267            Err(Self::error(&value))
268        }
269    }
270}
271
272impl<T: Resolve> Resolve for Sides<T> {
273    type Output = Sides<T::Output>;
274
275    fn resolve(self, styles: StyleChain) -> Self::Output {
276        self.map(|v| v.resolve(styles))
277    }
278}
279
280impl<T: Fold> Fold for Sides<Option<T>> {
281    fn fold(self, outer: Self) -> Self {
282        // Usually, folding an inner `None` with an `outer` prefers the
283        // explicit `None`. However, here `None` means unspecified and thus
284        // we want `outer`, so we use `fold_or` to opt into such behavior.
285        self.zip(outer).map(|(inner, outer)| inner.fold_or(outer))
286    }
287}
288
289/// The four sides of objects.
290#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
291pub enum Side {
292    /// The left side.
293    Left,
294    /// The top side.
295    Top,
296    /// The right side.
297    Right,
298    /// The bottom side.
299    Bottom,
300}
301
302impl Side {
303    /// The opposite side.
304    pub fn inv(self) -> Self {
305        match self {
306            Self::Left => Self::Right,
307            Self::Top => Self::Bottom,
308            Self::Right => Self::Left,
309            Self::Bottom => Self::Top,
310        }
311    }
312
313    /// The next side, clockwise.
314    pub fn next_cw(self) -> Self {
315        match self {
316            Self::Left => Self::Top,
317            Self::Top => Self::Right,
318            Self::Right => Self::Bottom,
319            Self::Bottom => Self::Left,
320        }
321    }
322
323    /// The next side, counter-clockwise.
324    pub fn next_ccw(self) -> Self {
325        match self {
326            Self::Left => Self::Bottom,
327            Self::Top => Self::Left,
328            Self::Right => Self::Top,
329            Self::Bottom => Self::Right,
330        }
331    }
332
333    /// The first corner of the side in clockwise order.
334    pub fn start_corner(self) -> Corner {
335        match self {
336            Self::Left => Corner::BottomLeft,
337            Self::Top => Corner::TopLeft,
338            Self::Right => Corner::TopRight,
339            Self::Bottom => Corner::BottomRight,
340        }
341    }
342
343    /// The second corner of the side in clockwise order.
344    pub fn end_corner(self) -> Corner {
345        self.next_cw().start_corner()
346    }
347
348    /// Return the corresponding axis.
349    pub fn axis(self) -> Axis {
350        match self {
351            Self::Left | Self::Right => Axis::Y,
352            Self::Top | Self::Bottom => Axis::X,
353        }
354    }
355}
356
357cast! {
358    Side,
359    self => Alignment::from(self).into_value(),
360    align: Alignment => match align {
361        Alignment::LEFT => Self::Left,
362        Alignment::RIGHT => Self::Right,
363        Alignment::TOP => Self::Top,
364        Alignment::BOTTOM => Self::Bottom,
365        _ => bail!("cannot convert this alignment to a side"),
366    },
367}