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