1use std::fmt::{self, Debug, Formatter};
2use std::ops::Add;
3
4use typst_utils::Get;
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, 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 is_uniform(&self) -> bool
85 where
86 T: PartialEq,
87 {
88 self.left == self.top && self.top == self.right && self.right == self.bottom
89 }
90}
91
92impl<T: Add> Sides<T> {
93 pub fn sum_by_axis(self) -> Axes<T::Output> {
95 Axes::new(self.left + self.right, self.top + self.bottom)
96 }
97}
98
99impl<T> Sides<Option<T>> {
100 pub fn unwrap_or(self, default: T) -> Sides<T>
102 where
103 T: Clone,
104 {
105 self.map(|v| v.unwrap_or(default.clone()))
106 }
107
108 pub fn unwrap_or_default(self) -> Sides<T>
110 where
111 T: Default,
112 {
113 self.map(Option::unwrap_or_default)
114 }
115}
116
117impl Sides<Rel<Abs>> {
118 pub fn relative_to(&self, size: Size) -> Sides<Abs> {
120 Sides {
121 left: self.left.relative_to(size.x),
122 top: self.top.relative_to(size.y),
123 right: self.right.relative_to(size.x),
124 bottom: self.bottom.relative_to(size.y),
125 }
126 }
127
128 pub fn is_zero(&self) -> bool {
130 self.left.is_zero()
131 && self.top.is_zero()
132 && self.right.is_zero()
133 && self.bottom.is_zero()
134 }
135}
136
137impl<T> Get<Side> for Sides<T> {
138 type Component = T;
139
140 fn get_ref(&self, side: Side) -> &T {
141 match side {
142 Side::Left => &self.left,
143 Side::Top => &self.top,
144 Side::Right => &self.right,
145 Side::Bottom => &self.bottom,
146 }
147 }
148
149 fn get_mut(&mut self, side: Side) -> &mut T {
150 match side {
151 Side::Left => &mut self.left,
152 Side::Top => &mut self.top,
153 Side::Right => &mut self.right,
154 Side::Bottom => &mut self.bottom,
155 }
156 }
157}
158
159impl<T: Debug + PartialEq> Debug for Sides<T> {
160 fn fmt(&self, f: &mut Formatter) -> fmt::Result {
161 if self.is_uniform() {
162 f.write_str("Sides::splat(")?;
163 self.left.fmt(f)?;
164 f.write_str(")")
165 } else {
166 f.debug_struct("Sides")
167 .field("left", &self.left)
168 .field("top", &self.top)
169 .field("right", &self.right)
170 .field("bottom", &self.bottom)
171 .finish()
172 }
173 }
174}
175
176impl<T: Reflect> Reflect for Sides<Option<T>> {
177 fn input() -> CastInfo {
178 T::input() + Dict::input()
179 }
180
181 fn output() -> CastInfo {
182 T::output() + Dict::output()
183 }
184
185 fn castable(value: &Value) -> bool {
186 Dict::castable(value) || T::castable(value)
187 }
188}
189
190impl<T> IntoValue for Sides<Option<T>>
191where
192 T: PartialEq + IntoValue,
193{
194 fn into_value(self) -> Value {
195 if self.is_uniform()
196 && let Some(left) = self.left
197 {
198 return left.into_value();
199 }
200
201 let mut dict = Dict::new();
202 let mut handle = |key: &str, component: Option<T>| {
203 if let Some(c) = component {
204 dict.insert(key.into(), c.into_value());
205 }
206 };
207
208 handle("left", self.left);
209 handle("top", self.top);
210 handle("right", self.right);
211 handle("bottom", self.bottom);
212
213 Value::Dict(dict)
214 }
215}
216
217impl<T> FromValue for Sides<Option<T>>
218where
219 T: Default + FromValue + Clone,
220{
221 fn from_value(mut value: Value) -> HintedStrResult<Self> {
222 let expected_keys = ["left", "top", "right", "bottom", "x", "y", "rest"];
223 if let Value::Dict(dict) = &mut value {
224 if dict.is_empty() {
225 return Ok(Self::splat(None));
226 } else if dict.iter().any(|(key, _)| expected_keys.contains(&key.as_str())) {
227 let mut take = |key| dict.take(key).ok().map(T::from_value).transpose();
228 let rest = take("rest")?;
229 let x = take("x")?.or_else(|| rest.clone());
230 let y = take("y")?.or_else(|| rest.clone());
231 let sides = Sides {
232 left: take("left")?.or_else(|| x.clone()),
233 top: take("top")?.or_else(|| y.clone()),
234 right: take("right")?.or_else(|| x.clone()),
235 bottom: take("bottom")?.or_else(|| y.clone()),
236 };
237
238 dict.finish(&expected_keys)?;
239 return Ok(sides);
240 }
241 }
242
243 if T::castable(&value) {
244 Ok(Self::splat(Some(T::from_value(value)?)))
245 } else if let Value::Dict(dict) = &value {
246 let keys = dict.iter().map(|kv| kv.0.as_str()).collect();
247 Err(Dict::unexpected_keys(keys, None).into())
250 } else {
251 Err(Self::error(&value))
252 }
253 }
254}
255
256impl<T: Resolve> Resolve for Sides<T> {
257 type Output = Sides<T::Output>;
258
259 fn resolve(self, styles: StyleChain) -> Self::Output {
260 self.map(|v| v.resolve(styles))
261 }
262}
263
264impl<T: Fold> Fold for Sides<Option<T>> {
265 fn fold(self, outer: Self) -> Self {
266 self.zip(outer).map(|(inner, outer)| inner.fold_or(outer))
270 }
271}
272
273#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
275pub enum Side {
276 Left,
278 Top,
280 Right,
282 Bottom,
284}
285
286impl Side {
287 pub fn inv(self) -> Self {
289 match self {
290 Self::Left => Self::Right,
291 Self::Top => Self::Bottom,
292 Self::Right => Self::Left,
293 Self::Bottom => Self::Top,
294 }
295 }
296
297 pub fn next_cw(self) -> Self {
299 match self {
300 Self::Left => Self::Top,
301 Self::Top => Self::Right,
302 Self::Right => Self::Bottom,
303 Self::Bottom => Self::Left,
304 }
305 }
306
307 pub fn next_ccw(self) -> Self {
309 match self {
310 Self::Left => Self::Bottom,
311 Self::Top => Self::Left,
312 Self::Right => Self::Top,
313 Self::Bottom => Self::Right,
314 }
315 }
316
317 pub fn start_corner(self) -> Corner {
319 match self {
320 Self::Left => Corner::BottomLeft,
321 Self::Top => Corner::TopLeft,
322 Self::Right => Corner::TopRight,
323 Self::Bottom => Corner::BottomRight,
324 }
325 }
326
327 pub fn end_corner(self) -> Corner {
329 self.next_cw().start_corner()
330 }
331
332 pub fn axis(self) -> Axis {
334 match self {
335 Self::Left | Self::Right => Axis::Y,
336 Self::Top | Self::Bottom => Axis::X,
337 }
338 }
339}
340
341cast! {
342 Side,
343 self => Alignment::from(self).into_value(),
344 align: Alignment => match align {
345 Alignment::LEFT => Self::Left,
346 Alignment::RIGHT => Self::Right,
347 Alignment::TOP => Self::Top,
348 Alignment::BOTTOM => Self::Bottom,
349 _ => bail!("cannot convert this alignment to a side"),
350 },
351}