typst_library/layout/
length.rsuse std::cmp::Ordering;
use std::fmt::{self, Debug, Formatter};
use std::ops::{Add, Div, Mul, Neg};
use comemo::Tracked;
use ecow::{eco_format, EcoString};
use typst_syntax::Span;
use typst_utils::Numeric;
use crate::diag::{bail, HintedStrResult, SourceResult};
use crate::foundations::{func, scope, ty, Context, Fold, Repr, Resolve, StyleChain};
use crate::layout::{Abs, Em};
#[ty(scope, cast)]
#[derive(Default, Copy, Clone, Eq, PartialEq, Hash)]
pub struct Length {
pub abs: Abs,
pub em: Em,
}
impl Length {
pub const fn zero() -> Self {
Self { abs: Abs::zero(), em: Em::zero() }
}
pub fn try_abs(self) -> Option<Self> {
(self.abs.is_zero() || self.em.is_zero())
.then(|| Self { abs: self.abs.abs(), em: self.em.abs() })
}
pub fn try_div(self, other: Self) -> Option<f64> {
if self.abs.is_zero() && other.abs.is_zero() {
Some(self.em / other.em)
} else if self.em.is_zero() && other.em.is_zero() {
Some(self.abs / other.abs)
} else {
None
}
}
pub fn at(self, font_size: Abs) -> Abs {
self.abs + self.em.at(font_size)
}
fn ensure_that_em_is_zero(&self, span: Span, unit: &str) -> SourceResult<()> {
if self.em == Em::zero() {
return Ok(());
}
bail!(
span,
"cannot convert a length with non-zero em units (`{}`) to {unit}",
self.repr();
hint: "use `length.to-absolute()` to resolve its em component \
(requires context)";
hint: "or use `length.abs.{unit}()` instead to ignore its em component"
)
}
}
#[scope]
impl Length {
#[func(name = "pt", title = "Points")]
pub fn to_pt(&self, span: Span) -> SourceResult<f64> {
self.ensure_that_em_is_zero(span, "pt")?;
Ok(self.abs.to_pt())
}
#[func(name = "mm", title = "Millimeters")]
pub fn to_mm(&self, span: Span) -> SourceResult<f64> {
self.ensure_that_em_is_zero(span, "mm")?;
Ok(self.abs.to_mm())
}
#[func(name = "cm", title = "Centimeters")]
pub fn to_cm(&self, span: Span) -> SourceResult<f64> {
self.ensure_that_em_is_zero(span, "cm")?;
Ok(self.abs.to_cm())
}
#[func(name = "inches")]
pub fn to_inches(&self, span: Span) -> SourceResult<f64> {
self.ensure_that_em_is_zero(span, "inches")?;
Ok(self.abs.to_inches())
}
#[func]
pub fn to_absolute(&self, context: Tracked<Context>) -> HintedStrResult<Length> {
Ok(self.resolve(context.styles()?).into())
}
}
impl Debug for Length {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
match (self.abs.is_zero(), self.em.is_zero()) {
(false, false) => write!(f, "{:?} + {:?}", self.abs, self.em),
(true, false) => self.em.fmt(f),
(_, true) => self.abs.fmt(f),
}
}
}
impl Repr for Length {
fn repr(&self) -> EcoString {
match (self.abs.is_zero(), self.em.is_zero()) {
(false, false) => eco_format!("{} + {}", self.abs.repr(), self.em.repr()),
(true, false) => self.em.repr(),
(_, true) => self.abs.repr(),
}
}
}
impl Numeric for Length {
fn zero() -> Self {
Self::zero()
}
fn is_finite(self) -> bool {
self.abs.is_finite() && self.em.is_finite()
}
}
impl PartialOrd for Length {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
if self.em.is_zero() && other.em.is_zero() {
self.abs.partial_cmp(&other.abs)
} else if self.abs.is_zero() && other.abs.is_zero() {
self.em.partial_cmp(&other.em)
} else {
None
}
}
}
impl From<Abs> for Length {
fn from(abs: Abs) -> Self {
Self { abs, em: Em::zero() }
}
}
impl From<Em> for Length {
fn from(em: Em) -> Self {
Self { abs: Abs::zero(), em }
}
}
impl Neg for Length {
type Output = Self;
fn neg(self) -> Self::Output {
Self { abs: -self.abs, em: -self.em }
}
}
impl Add for Length {
type Output = Self;
fn add(self, rhs: Self) -> Self::Output {
Self { abs: self.abs + rhs.abs, em: self.em + rhs.em }
}
}
typst_utils::sub_impl!(Length - Length -> Length);
impl Mul<f64> for Length {
type Output = Self;
fn mul(self, rhs: f64) -> Self::Output {
Self { abs: self.abs * rhs, em: self.em * rhs }
}
}
impl Mul<Length> for f64 {
type Output = Length;
fn mul(self, rhs: Length) -> Self::Output {
rhs * self
}
}
impl Div<f64> for Length {
type Output = Self;
fn div(self, rhs: f64) -> Self::Output {
Self { abs: self.abs / rhs, em: self.em / rhs }
}
}
typst_utils::assign_impl!(Length += Length);
typst_utils::assign_impl!(Length -= Length);
typst_utils::assign_impl!(Length *= f64);
typst_utils::assign_impl!(Length /= f64);
impl Resolve for Length {
type Output = Abs;
fn resolve(self, styles: StyleChain) -> Self::Output {
self.abs + self.em.resolve(styles)
}
}
impl Fold for Length {
fn fold(self, _: Self) -> Self {
self
}
}