typst_library/layout/
corners.rs

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