typst_library/layout/
length.rs1use std::cmp::Ordering;
2use std::fmt::{self, Debug, Formatter};
3use std::ops::{Add, Div, Mul, Neg};
4
5use comemo::Tracked;
6use ecow::{EcoString, eco_format};
7use typst_syntax::Span;
8use typst_utils::{Numeric, NumericLength};
9
10use crate::diag::{HintedStrResult, SourceResult, bail};
11use crate::foundations::{Context, Fold, Repr, Resolve, StyleChain, func, scope, ty};
12use crate::layout::{Abs, Em};
13
14#[ty(scope, cast)]
43#[derive(Default, Copy, Clone, Eq, PartialEq, Hash)]
44pub struct Length {
45 pub abs: Abs,
47 pub em: Em,
49}
50
51impl Length {
52 pub const fn zero() -> Self {
54 Self { abs: Abs::zero(), em: Em::zero() }
55 }
56
57 pub fn try_abs(self) -> Option<Self> {
59 (self.abs.is_zero() || self.em.is_zero())
60 .then(|| Self { abs: self.abs.abs(), em: self.em.abs() })
61 }
62
63 pub fn try_div(self, other: Self) -> Option<f64> {
65 if self.abs.is_zero() && other.abs.is_zero() {
66 Some(self.em / other.em)
67 } else if self.em.is_zero() && other.em.is_zero() {
68 Some(self.abs / other.abs)
69 } else {
70 None
71 }
72 }
73
74 pub fn at(self, font_size: Abs) -> Abs {
76 self.abs + self.em.at(font_size)
77 }
78
79 fn ensure_that_em_is_zero(&self, span: Span, unit: &str) -> SourceResult<()> {
81 if self.em == Em::zero() {
82 return Ok(());
83 }
84
85 bail!(
86 span,
87 "cannot convert a length with non-zero em units (`{}`) to {unit}",
88 self.repr();
89 hint: "use `length.to-absolute()` to resolve its em component \
90 (requires context)";
91 hint: "or use `length.abs.{unit}()` instead to ignore its em component";
92 )
93 }
94}
95
96#[scope]
97impl Length {
98 #[func(name = "pt", title = "Points")]
105 pub fn to_pt(&self, span: Span) -> SourceResult<f64> {
106 self.ensure_that_em_is_zero(span, "pt")?;
107 Ok(self.abs.to_pt())
108 }
109
110 #[func(name = "mm", title = "Millimeters")]
115 pub fn to_mm(&self, span: Span) -> SourceResult<f64> {
116 self.ensure_that_em_is_zero(span, "mm")?;
117 Ok(self.abs.to_mm())
118 }
119
120 #[func(name = "cm", title = "Centimeters")]
125 pub fn to_cm(&self, span: Span) -> SourceResult<f64> {
126 self.ensure_that_em_is_zero(span, "cm")?;
127 Ok(self.abs.to_cm())
128 }
129
130 #[func(name = "inches")]
135 pub fn to_inches(&self, span: Span) -> SourceResult<f64> {
136 self.ensure_that_em_is_zero(span, "inches")?;
137 Ok(self.abs.to_inches())
138 }
139
140 #[func]
158 pub fn to_absolute(&self, context: Tracked<Context>) -> HintedStrResult<Length> {
159 Ok(self.resolve(context.styles()?).into())
160 }
161}
162
163impl Debug for Length {
164 fn fmt(&self, f: &mut Formatter) -> fmt::Result {
165 match (self.abs.is_zero(), self.em.is_zero()) {
166 (false, false) => write!(f, "{:?} + {:?}", self.abs, self.em),
167 (true, false) => self.em.fmt(f),
168 (_, true) => self.abs.fmt(f),
169 }
170 }
171}
172
173impl Repr for Length {
174 fn repr(&self) -> EcoString {
175 match (self.abs.is_zero(), self.em.is_zero()) {
176 (false, false) => eco_format!("{} + {}", self.abs.repr(), self.em.repr()),
177 (true, false) => self.em.repr(),
178 (_, true) => self.abs.repr(),
179 }
180 }
181}
182
183impl NumericLength for Length {}
184
185impl Numeric for Length {
186 fn zero() -> Self {
187 Self::zero()
188 }
189
190 fn is_finite(self) -> bool {
191 self.abs.is_finite() && self.em.is_finite()
192 }
193}
194
195impl PartialOrd for Length {
196 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
197 if self.em.is_zero() && other.em.is_zero() {
198 self.abs.partial_cmp(&other.abs)
199 } else if self.abs.is_zero() && other.abs.is_zero() {
200 self.em.partial_cmp(&other.em)
201 } else {
202 None
203 }
204 }
205}
206
207impl From<Abs> for Length {
208 fn from(abs: Abs) -> Self {
209 Self { abs, em: Em::zero() }
210 }
211}
212
213impl From<Em> for Length {
214 fn from(em: Em) -> Self {
215 Self { abs: Abs::zero(), em }
216 }
217}
218
219impl Neg for Length {
220 type Output = Self;
221
222 fn neg(self) -> Self::Output {
223 Self { abs: -self.abs, em: -self.em }
224 }
225}
226
227impl Add for Length {
228 type Output = Self;
229
230 fn add(self, rhs: Self) -> Self::Output {
231 Self { abs: self.abs + rhs.abs, em: self.em + rhs.em }
232 }
233}
234
235typst_utils::sub_impl!(Length - Length -> Length);
236
237impl Mul<f64> for Length {
238 type Output = Self;
239
240 fn mul(self, rhs: f64) -> Self::Output {
241 Self { abs: self.abs * rhs, em: self.em * rhs }
242 }
243}
244
245impl Mul<Length> for f64 {
246 type Output = Length;
247
248 fn mul(self, rhs: Length) -> Self::Output {
249 rhs * self
250 }
251}
252
253impl Div<f64> for Length {
254 type Output = Self;
255
256 fn div(self, rhs: f64) -> Self::Output {
257 Self { abs: self.abs / rhs, em: self.em / rhs }
258 }
259}
260
261typst_utils::assign_impl!(Length += Length);
262typst_utils::assign_impl!(Length -= Length);
263typst_utils::assign_impl!(Length *= f64);
264typst_utils::assign_impl!(Length /= f64);
265
266impl Resolve for Length {
267 type Output = Abs;
268
269 fn resolve(self, styles: StyleChain) -> Self::Output {
270 self.abs + self.em.resolve(styles)
271 }
272}
273
274impl Fold for Length {
275 fn fold(self, _: Self) -> Self {
276 self
277 }
278}