typst_library/layout/
rel.rs

1use std::cmp::Ordering;
2use std::fmt::{self, Debug, Formatter};
3use std::ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Neg, Sub, SubAssign};
4
5use ecow::{EcoString, eco_format};
6use typst_utils::Numeric;
7
8use crate::foundations::{Fold, Repr, Resolve, StyleChain, cast, ty};
9use crate::layout::{Abs, Em, Length, Ratio};
10
11/// A length in relation to some known length.
12///
13/// This type is a combination of a [length] with a [ratio]. It results from
14/// addition and subtraction of a length and a ratio. Wherever a relative length
15/// is expected, you can also use a bare length or ratio.
16///
17/// # Relative to the page
18/// A common use case is setting the width or height of a layout element (e.g.,
19/// [block], [rect], etc.) as a certain percentage of the width of the page.
20/// Here, the rectangle's width is set to `{25%}`, so it takes up one fourth of
21/// the page's _inner_ width (the width minus margins).
22///
23/// ```example
24/// #rect(width: 25%)
25/// ```
26///
27/// Bare lengths or ratios are always valid where relative lengths are expected,
28/// but the two can also be freely mixed:
29/// ```example
30/// #rect(width: 25% + 1cm)
31/// ```
32///
33/// If you're trying to size an element so that it takes up the page's _full_
34/// width, you have a few options (this highly depends on your exact use case):
35///
36/// 1. Set page margins to `{0pt}` (`[#set page(margin: 0pt)]`)
37/// 2. Multiply the ratio by the known full page width (`{21cm * 69%}`)
38/// 3. Use padding which will negate the margins (`[#pad(x: -2.5cm, ...)]`)
39/// 4. Use the page [background](page.background) or
40///    [foreground](page.foreground) field as those don't take margins into
41///    account (note that it will render the content outside of the document
42///    flow, see [place] to control the content position)
43///
44/// # Relative to a container
45/// When a layout element (e.g. a [rect]) is nested in another layout container
46/// (e.g. a [block]) instead of being a direct descendant of the page, relative
47/// widths become relative to the container:
48///
49/// ```example
50/// #block(
51///   width: 100pt,
52///   fill: aqua,
53///   rect(width: 50%),
54/// )
55/// ```
56///
57/// # Scripting
58/// You can multiply relative lengths by [ratios]($ratio), [integers]($int), and
59/// [floats]($float).
60///
61/// A relative length has the following fields:
62/// - `length`: Its length component.
63/// - `ratio`: Its ratio component.
64///
65/// ```example
66/// #(100% - 50pt).length \
67/// #(100% - 50pt).ratio
68/// ```
69#[ty(cast, name = "relative", title = "Relative Length")]
70#[derive(Default, Copy, Clone, Eq, PartialEq, Hash)]
71pub struct Rel<T: Numeric = Length> {
72    /// The relative part.
73    pub rel: Ratio,
74    /// The absolute part.
75    pub abs: T,
76}
77
78impl<T: Numeric> Rel<T> {
79    /// The zero relative.
80    pub fn zero() -> Self {
81        Self { rel: Ratio::zero(), abs: T::zero() }
82    }
83
84    /// A relative with a ratio of `100%` and no absolute part.
85    pub fn one() -> Self {
86        Self { rel: Ratio::one(), abs: T::zero() }
87    }
88
89    /// Create a new relative from its parts.
90    pub const fn new(rel: Ratio, abs: T) -> Self {
91        Self { rel, abs }
92    }
93
94    /// Whether both parts are zero.
95    pub fn is_zero(self) -> bool {
96        self.rel.is_zero() && self.abs == T::zero()
97    }
98
99    /// Whether the relative part is one and the absolute part is zero.
100    pub fn is_one(self) -> bool {
101        self.rel.is_one() && self.abs == T::zero()
102    }
103
104    /// Evaluate this relative to the given `whole`.
105    pub fn relative_to(self, whole: T) -> T {
106        self.rel.of(whole) + self.abs
107    }
108
109    /// Map the absolute part with `f`.
110    pub fn map<F, U>(self, f: F) -> Rel<U>
111    where
112        F: FnOnce(T) -> U,
113        U: Numeric,
114    {
115        Rel { rel: self.rel, abs: f(self.abs) }
116    }
117}
118
119impl Rel<Length> {
120    /// Try to divide two relative lengths.
121    pub fn try_div(self, other: Self) -> Option<f64> {
122        if self.rel.is_zero() && other.rel.is_zero() {
123            self.abs.try_div(other.abs)
124        } else if self.abs.is_zero() && other.abs.is_zero() {
125            Some(self.rel / other.rel)
126        } else {
127            None
128        }
129    }
130}
131
132impl<T: Numeric + Debug> Debug for Rel<T> {
133    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
134        match (self.rel.is_zero(), self.abs.is_zero()) {
135            (false, false) => write!(f, "{:?} + {:?}", self.rel, self.abs),
136            (false, true) => self.rel.fmt(f),
137            (true, _) => self.abs.fmt(f),
138        }
139    }
140}
141
142impl<T: Numeric + Repr> Repr for Rel<T> {
143    fn repr(&self) -> EcoString {
144        eco_format!("{} + {}", self.rel.repr(), self.abs.repr())
145    }
146}
147
148impl From<Abs> for Rel<Length> {
149    fn from(abs: Abs) -> Self {
150        Rel::from(Length::from(abs))
151    }
152}
153
154impl From<Em> for Rel<Length> {
155    fn from(em: Em) -> Self {
156        Rel::from(Length::from(em))
157    }
158}
159
160impl<T: Numeric> From<T> for Rel<T> {
161    fn from(abs: T) -> Self {
162        Self { rel: Ratio::zero(), abs }
163    }
164}
165
166impl<T: Numeric> From<Ratio> for Rel<T> {
167    fn from(rel: Ratio) -> Self {
168        Self { rel, abs: T::zero() }
169    }
170}
171
172impl<T: Numeric + PartialOrd> PartialOrd for Rel<T> {
173    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
174        if self.rel.is_zero() && other.rel.is_zero() {
175            self.abs.partial_cmp(&other.abs)
176        } else if self.abs.is_zero() && other.abs.is_zero() {
177            self.rel.partial_cmp(&other.rel)
178        } else {
179            None
180        }
181    }
182}
183
184impl<T: Numeric> Neg for Rel<T> {
185    type Output = Self;
186
187    fn neg(self) -> Self {
188        Self { rel: -self.rel, abs: -self.abs }
189    }
190}
191
192impl<T: Numeric> Add for Rel<T> {
193    type Output = Self;
194
195    fn add(self, other: Self) -> Self::Output {
196        Self {
197            rel: self.rel + other.rel,
198            abs: self.abs + other.abs,
199        }
200    }
201}
202
203impl<T: Numeric> Sub for Rel<T> {
204    type Output = Self;
205
206    fn sub(self, other: Self) -> Self::Output {
207        self + -other
208    }
209}
210
211impl<T: Numeric> Mul<f64> for Rel<T> {
212    type Output = Self;
213
214    fn mul(self, other: f64) -> Self::Output {
215        Self { rel: self.rel * other, abs: self.abs * other }
216    }
217}
218
219impl<T: Numeric> Mul<Rel<T>> for f64 {
220    type Output = Rel<T>;
221
222    fn mul(self, other: Rel<T>) -> Self::Output {
223        other * self
224    }
225}
226
227impl<T: Numeric> Div<f64> for Rel<T> {
228    type Output = Self;
229
230    fn div(self, other: f64) -> Self::Output {
231        Self { rel: self.rel / other, abs: self.abs / other }
232    }
233}
234
235impl<T: Numeric + AddAssign> AddAssign for Rel<T> {
236    fn add_assign(&mut self, other: Self) {
237        self.rel += other.rel;
238        self.abs += other.abs;
239    }
240}
241
242impl<T: Numeric + SubAssign> SubAssign for Rel<T> {
243    fn sub_assign(&mut self, other: Self) {
244        self.rel -= other.rel;
245        self.abs -= other.abs;
246    }
247}
248
249impl<T: Numeric + MulAssign<f64>> MulAssign<f64> for Rel<T> {
250    fn mul_assign(&mut self, other: f64) {
251        self.rel *= other;
252        self.abs *= other;
253    }
254}
255
256impl<T: Numeric + DivAssign<f64>> DivAssign<f64> for Rel<T> {
257    fn div_assign(&mut self, other: f64) {
258        self.rel /= other;
259        self.abs /= other;
260    }
261}
262
263impl<T: Numeric> Add<T> for Ratio {
264    type Output = Rel<T>;
265
266    fn add(self, other: T) -> Self::Output {
267        Rel::from(self) + Rel::from(other)
268    }
269}
270
271impl<T: Numeric> Add<T> for Rel<T> {
272    type Output = Self;
273
274    fn add(self, other: T) -> Self::Output {
275        self + Rel::from(other)
276    }
277}
278
279impl<T: Numeric> Add<Ratio> for Rel<T> {
280    type Output = Self;
281
282    fn add(self, other: Ratio) -> Self::Output {
283        self + Rel::from(other)
284    }
285}
286
287impl<T> Resolve for Rel<T>
288where
289    T: Resolve + Numeric,
290    <T as Resolve>::Output: Numeric,
291{
292    type Output = Rel<<T as Resolve>::Output>;
293
294    fn resolve(self, styles: StyleChain) -> Self::Output {
295        self.map(|abs| abs.resolve(styles))
296    }
297}
298
299impl<T> Fold for Rel<T>
300where
301    T: Numeric + Fold,
302{
303    fn fold(self, outer: Self) -> Self {
304        Self { rel: self.rel, abs: self.abs.fold(outer.abs) }
305    }
306}
307
308cast! {
309    Rel<Abs>,
310    self => self.map(Length::from).into_value(),
311}