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;
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 Numeric for Length {
184 fn zero() -> Self {
185 Self::zero()
186 }
187
188 fn is_finite(self) -> bool {
189 self.abs.is_finite() && self.em.is_finite()
190 }
191}
192
193impl PartialOrd for Length {
194 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
195 if self.em.is_zero() && other.em.is_zero() {
196 self.abs.partial_cmp(&other.abs)
197 } else if self.abs.is_zero() && other.abs.is_zero() {
198 self.em.partial_cmp(&other.em)
199 } else {
200 None
201 }
202 }
203}
204
205impl From<Abs> for Length {
206 fn from(abs: Abs) -> Self {
207 Self { abs, em: Em::zero() }
208 }
209}
210
211impl From<Em> for Length {
212 fn from(em: Em) -> Self {
213 Self { abs: Abs::zero(), em }
214 }
215}
216
217impl Neg for Length {
218 type Output = Self;
219
220 fn neg(self) -> Self::Output {
221 Self { abs: -self.abs, em: -self.em }
222 }
223}
224
225impl Add for Length {
226 type Output = Self;
227
228 fn add(self, rhs: Self) -> Self::Output {
229 Self { abs: self.abs + rhs.abs, em: self.em + rhs.em }
230 }
231}
232
233typst_utils::sub_impl!(Length - Length -> Length);
234
235impl Mul<f64> for Length {
236 type Output = Self;
237
238 fn mul(self, rhs: f64) -> Self::Output {
239 Self { abs: self.abs * rhs, em: self.em * rhs }
240 }
241}
242
243impl Mul<Length> for f64 {
244 type Output = Length;
245
246 fn mul(self, rhs: Length) -> Self::Output {
247 rhs * self
248 }
249}
250
251impl Div<f64> for Length {
252 type Output = Self;
253
254 fn div(self, rhs: f64) -> Self::Output {
255 Self { abs: self.abs / rhs, em: self.em / rhs }
256 }
257}
258
259typst_utils::assign_impl!(Length += Length);
260typst_utils::assign_impl!(Length -= Length);
261typst_utils::assign_impl!(Length *= f64);
262typst_utils::assign_impl!(Length /= f64);
263
264impl Resolve for Length {
265 type Output = Abs;
266
267 fn resolve(self, styles: StyleChain) -> Self::Output {
268 self.abs + self.em.resolve(styles)
269 }
270}
271
272impl Fold for Length {
273 fn fold(self, _: Self) -> Self {
274 self
275 }
276}