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#[derive(Default, Copy, Clone, Eq, PartialEq, Hash)]
15pub struct Sides<T> {
16 pub left: T,
18 pub top: T,
20 pub right: T,
22 pub bottom: T,
24}
25
26impl<T> Sides<T> {
27 pub const fn new(left: T, top: T, right: T, bottom: T) -> Self {
29 Self { left, top, right, bottom }
30 }
31
32 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 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 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 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 pub fn iter(&self) -> impl Iterator<Item = &T> {
80 [&self.left, &self.top, &self.right, &self.bottom].into_iter()
81 }
82
83 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 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 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 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 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 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 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 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 self.zip(outer).map(|(inner, outer)| inner.fold_or(outer))
286 }
287}
288
289#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
291pub enum Side {
292 Left,
294 Top,
296 Right,
298 Bottom,
300}
301
302impl Side {
303 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 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 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 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 pub fn end_corner(self) -> Corner {
345 self.next_cw().start_corner()
346 }
347
348 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}