Skip to main content

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