diff --git a/Cargo.toml b/Cargo.toml index a9d8011dd..963857719 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,7 +24,6 @@ lto = true [dependencies] async-trait = "0.1" fontdock = { path = "../fontdock", default-features = false } -kurbo = "0.6.3" tide = { path = "../tide" } ttf-parser = "0.8.2" unicode-xid = "0.2" @@ -33,6 +32,7 @@ serde = { version = "1", features = ["derive"], optional = true } [dev-dependencies] criterion = "0.3" futures-executor = "0.3" +kurbo = "0.6.3" serde_json = "1" raqote = { version = "0.8", default-features = false } diff --git a/src/eval/convert.rs b/src/eval/convert.rs index 4c177c5b6..69ef25060 100644 --- a/src/eval/convert.rs +++ b/src/eval/convert.rs @@ -6,8 +6,7 @@ use fontdock::{FontStretch, FontStyle, FontWeight}; use super::{Value, ValueDict, ValueFunc}; use crate::diag::Diag; -use crate::geom::Linear; -use crate::layout::{Dir, SpecAlign}; +use crate::geom::{Dir, Length, Linear, Relative}; use crate::paper::Paper; use crate::syntax::{Ident, SpanWith, Spanned, SynTree}; @@ -37,26 +36,53 @@ impl Convert for Spanned { } } -/// A value type that matches [length] values. -/// -/// [length]: enum.Value.html#variant.Length -pub struct Absolute(pub f64); - -impl From for f64 { - fn from(abs: Absolute) -> f64 { - abs.0 - } +macro_rules! convert_match { + ($type:ty, $name:expr, $($p:pat => $r:expr),* $(,)?) => { + impl $crate::eval::Convert for $type { + fn convert( + value: $crate::syntax::Spanned<$crate::eval::Value> + ) -> (Result, Option<$crate::diag::Diag>) { + #[allow(unreachable_patterns)] + match value.v { + $($p => (Ok($r), None)),*, + v => { + let err = $crate::error!("expected {}, found {}", $name, v.ty()); + (Err(v), Some(err)) + }, + } + } + } + }; } -/// A value type that matches [relative] values. -/// -/// [relative]: enum.Value.html#variant.Relative -pub struct Relative(pub f64); - -impl From for f64 { - fn from(rel: Relative) -> f64 { - rel.0 - } +macro_rules! convert_ident { + ($type:ty, $name:expr, $parse:expr) => { + impl $crate::eval::Convert for $type { + fn convert( + value: $crate::syntax::Spanned<$crate::eval::Value>, + ) -> ( + Result, + Option<$crate::diag::Diag>, + ) { + match value.v { + Value::Ident(id) => { + if let Some(thing) = $parse(&id) { + (Ok(thing), None) + } else { + ( + Err($crate::eval::Value::Ident(id)), + Some($crate::error!("invalid {}", $name)), + ) + } + } + v => { + let err = $crate::error!("expected {}, found {}", $name, v.ty()); + (Err(v), Some(err)) + } + } + } + } + }; } /// A value type that matches [identifier] and [string] values. @@ -79,70 +105,31 @@ impl Deref for StringLike { } } -macro_rules! impl_match { - ($type:ty, $name:expr, $($p:pat => $r:expr),* $(,)?) => { - impl Convert for $type { - fn convert(value: Spanned) -> (Result, Option) { - #[allow(unreachable_patterns)] - match value.v { - $($p => (Ok($r), None)),*, - v => { - let err = error!("expected {}, found {}", $name, v.ty()); - (Err(v), Some(err)) - }, - } - } - } - }; -} - -impl_match!(Value, "value", v => v); -impl_match!(Ident, "identifier", Value::Ident(v) => v); -impl_match!(bool, "bool", Value::Bool(v) => v); -impl_match!(i64, "integer", Value::Int(v) => v); -impl_match!(f64, "float", +convert_match!(Value, "value", v => v); +convert_match!(Ident, "identifier", Value::Ident(v) => v); +convert_match!(bool, "bool", Value::Bool(v) => v); +convert_match!(i64, "integer", Value::Int(v) => v); +convert_match!(f64, "float", Value::Int(v) => v as f64, Value::Float(v) => v, ); -impl_match!(Absolute, "length", Value::Length(v) => Absolute(v)); -impl_match!(Relative, "relative", Value::Relative(v) => Relative(v)); -impl_match!(Linear, "linear", +convert_match!(Length, "length", Value::Length(v) => v); +convert_match!(Relative, "relative", Value::Relative(v) => v); +convert_match!(Linear, "linear", Value::Linear(v) => v, - Value::Length(v) => Linear::abs(v), - Value::Relative(v) => Linear::rel(v), + Value::Length(v) => v.into(), + Value::Relative(v) => v.into(), ); -impl_match!(String, "string", Value::Str(v) => v); -impl_match!(SynTree, "tree", Value::Content(v) => v); -impl_match!(ValueDict, "dictionary", Value::Dict(v) => v); -impl_match!(ValueFunc, "function", Value::Func(v) => v); -impl_match!(StringLike, "identifier or string", +convert_match!(String, "string", Value::Str(v) => v); +convert_match!(SynTree, "tree", Value::Content(v) => v); +convert_match!(ValueDict, "dictionary", Value::Dict(v) => v); +convert_match!(ValueFunc, "function", Value::Func(v) => v); +convert_match!(StringLike, "identifier or string", Value::Ident(Ident(v)) => StringLike(v), Value::Str(v) => StringLike(v), ); -macro_rules! impl_ident { - ($type:ty, $name:expr, $parse:expr) => { - impl Convert for $type { - fn convert(value: Spanned) -> (Result, Option) { - match value.v { - Value::Ident(id) => { - if let Some(thing) = $parse(&id) { - (Ok(thing), None) - } else { - (Err(Value::Ident(id)), Some(error!("invalid {}", $name))) - } - } - v => { - let err = error!("expected {}, found {}", $name, v.ty()); - (Err(v), Some(err)) - } - } - } - } - }; -} - -impl_ident!(Dir, "direction", |v| match v { +convert_ident!(Dir, "direction", |v| match v { "ltr" => Some(Self::LTR), "rtl" => Some(Self::RTL), "ttb" => Some(Self::TTB), @@ -150,18 +137,9 @@ impl_ident!(Dir, "direction", |v| match v { _ => None, }); -impl_ident!(SpecAlign, "alignment", |v| match v { - "left" => Some(Self::Left), - "right" => Some(Self::Right), - "top" => Some(Self::Top), - "bottom" => Some(Self::Bottom), - "center" => Some(Self::Center), - _ => None, -}); - -impl_ident!(FontStyle, "font style", Self::from_str); -impl_ident!(FontStretch, "font stretch", Self::from_str); -impl_ident!(Paper, "paper", Self::from_name); +convert_ident!(FontStyle, "font style", Self::from_str); +convert_ident!(FontStretch, "font stretch", Self::from_str); +convert_ident!(Paper, "paper", Self::from_name); impl Convert for FontWeight { fn convert(value: Spanned) -> (Result, Option) { diff --git a/src/eval/mod.rs b/src/eval/mod.rs index 2c6f4d7cf..fc8bbbd6b 100644 --- a/src/eval/mod.rs +++ b/src/eval/mod.rs @@ -1,7 +1,8 @@ //! Evaluation of syntax trees. -mod args; +#[macro_use] mod convert; +mod args; mod dict; mod scope; mod state; @@ -22,10 +23,10 @@ use fontdock::FontStyle; use crate::diag::Diag; use crate::diag::{Deco, Feedback, Pass}; -use crate::layout::nodes::{ +use crate::geom::{Gen, Length, Relative, Spec, Switch}; +use crate::layout::{ Document, LayoutNode, Pad, Pages, Par, Softness, Spacing, Stack, Text, }; -use crate::layout::{Gen2, Spec2, Switch}; use crate::syntax::*; /// Evaluate a syntax tree into a document. @@ -168,7 +169,7 @@ impl EvalContext { dirs, children, aligns, - expand: Spec2::new(true, true), + expand: Spec::new(true, true), }), }), }) @@ -195,7 +196,7 @@ impl EvalContext { line_spacing, children, aligns, - expand: Gen2::new(false, expand_cross).switch(dirs), + expand: Gen::new(false, expand_cross).switch(dirs), }); } } @@ -337,7 +338,7 @@ impl Eval for NodeRaw { dirs: ctx.state.dirs, children, aligns: ctx.state.aligns, - expand: Spec2::new(false, false), + expand: Spec::new(false, false), }); ctx.state.text.fallback = prev; @@ -366,8 +367,8 @@ impl Eval for Lit { Lit::Bool(v) => Value::Bool(v), Lit::Int(v) => Value::Int(v), Lit::Float(v) => Value::Float(v), - Lit::Length(v) => Value::Length(v.as_raw()), - Lit::Percent(v) => Value::Relative(v / 100.0), + Lit::Length(v, unit) => Value::Length(Length::with_unit(v, unit)), + Lit::Percent(v) => Value::Relative(Relative::new(v / 100.0)), Lit::Color(v) => Value::Color(v), Lit::Str(ref v) => Value::Str(v.clone()), Lit::Dict(ref v) => Value::Dict(v.eval(ctx)), @@ -473,7 +474,6 @@ fn neg(ctx: &mut EvalContext, span: Span, value: Value) -> Value { /// Compute the sum of two values. fn add(ctx: &mut EvalContext, span: Span, lhs: Value, rhs: Value) -> Value { - use crate::geom::Linear as Lin; use Value::*; match (lhs, rhs) { // Numbers to themselves. @@ -484,15 +484,15 @@ fn add(ctx: &mut EvalContext, span: Span, lhs: Value, rhs: Value) -> Value { // Lengths, relatives and linears to themselves. (Length(a), Length(b)) => Length(a + b), - (Length(a), Relative(b)) => Linear(Lin::abs(a) + Lin::rel(b)), - (Length(a), Linear(b)) => Linear(Lin::abs(a) + b), + (Length(a), Relative(b)) => Linear(a + b), + (Length(a), Linear(b)) => Linear(a + b), - (Relative(a), Length(b)) => Linear(Lin::rel(a) + Lin::abs(b)), + (Relative(a), Length(b)) => Linear(a + b), (Relative(a), Relative(b)) => Relative(a + b), - (Relative(a), Linear(b)) => Linear(Lin::rel(a) + b), + (Relative(a), Linear(b)) => Linear(a + b), - (Linear(a), Length(b)) => Linear(a + Lin::abs(b)), - (Linear(a), Relative(b)) => Linear(a + Lin::rel(b)), + (Linear(a), Length(b)) => Linear(a + b), + (Linear(a), Relative(b)) => Linear(a + b), (Linear(a), Linear(b)) => Linear(a + b), // Complex data types to themselves. @@ -509,7 +509,6 @@ fn add(ctx: &mut EvalContext, span: Span, lhs: Value, rhs: Value) -> Value { /// Compute the difference of two values. fn sub(ctx: &mut EvalContext, span: Span, lhs: Value, rhs: Value) -> Value { - use crate::geom::Linear as Lin; use Value::*; match (lhs, rhs) { // Numbers from themselves. @@ -520,13 +519,13 @@ fn sub(ctx: &mut EvalContext, span: Span, lhs: Value, rhs: Value) -> Value { // Lengths, relatives and linears from themselves. (Length(a), Length(b)) => Length(a - b), - (Length(a), Relative(b)) => Linear(Lin::abs(a) - Lin::rel(b)), - (Length(a), Linear(b)) => Linear(Lin::abs(a) - b), - (Relative(a), Length(b)) => Linear(Lin::rel(a) - Lin::abs(b)), + (Length(a), Relative(b)) => Linear(a - b), + (Length(a), Linear(b)) => Linear(a - b), + (Relative(a), Length(b)) => Linear(a - b), (Relative(a), Relative(b)) => Relative(a - b), - (Relative(a), Linear(b)) => Linear(Lin::rel(a) - b), - (Linear(a), Length(b)) => Linear(a - Lin::abs(b)), - (Linear(a), Relative(b)) => Linear(a - Lin::rel(b)), + (Relative(a), Linear(b)) => Linear(a - b), + (Linear(a), Length(b)) => Linear(a - b), + (Linear(a), Relative(b)) => Linear(a - b), (Linear(a), Linear(b)) => Linear(a - b), (a, b) => { @@ -561,8 +560,8 @@ fn mul(ctx: &mut EvalContext, span: Span, lhs: Value, rhs: Value) -> Value { (Float(a), Linear(b)) => Linear(a * b), // Integers with strings. - (Int(a), Str(b)) => Str(b.repeat(a.max(0) as usize)), - (Str(a), Int(b)) => Str(a.repeat(b.max(0) as usize)), + (Int(a), Str(b)) => Str(b.repeat(0.max(a) as usize)), + (Str(a), Int(b)) => Str(a.repeat(0.max(b) as usize)), (a, b) => { ctx.diag(error!(span, "cannot multiply {} with {}", a.ty(), b.ty())); diff --git a/src/eval/state.rs b/src/eval/state.rs index 7372b8513..3ae6b4142 100644 --- a/src/eval/state.rs +++ b/src/eval/state.rs @@ -5,9 +5,7 @@ use std::rc::Rc; use fontdock::{fallback, FallbackTree, FontStretch, FontStyle, FontVariant, FontWeight}; use super::Scope; -use crate::geom::{Linear, Size}; -use crate::layout::{Dir, Gen2, GenAlign, Sides}; -use crate::length::Length; +use crate::geom::{Align, Dir, Gen, Length, Linear, Relative, Sides, Size}; use crate::paper::{Paper, PaperClass, PAPER_A4}; /// The active evaluation state. @@ -20,9 +18,9 @@ pub struct State { /// The page state. pub page: PageState, /// The active layouting directions. - pub dirs: Gen2, + pub dirs: Gen, /// The active alignments. - pub aligns: Gen2, + pub aligns: Gen, } impl Default for State { @@ -31,8 +29,8 @@ impl Default for State { scope: crate::library::_std(), text: TextState::default(), page: PageState::default(), - dirs: Gen2::new(Dir::TTB, Dir::LTR), - aligns: Gen2::new(GenAlign::Start, GenAlign::Start), + dirs: Gen::new(Dir::TTB, Dir::LTR), + aligns: Gen::new(Align::Start, Align::Start), } } } @@ -62,22 +60,22 @@ pub struct TextState { impl TextState { /// The absolute font size. - pub fn font_size(&self) -> f64 { + pub fn font_size(&self) -> Length { self.font_size.eval() } /// The absolute word spacing. - pub fn word_spacing(&self) -> f64 { + pub fn word_spacing(&self) -> Length { self.word_spacing.eval(self.font_size()) } /// The absolute line spacing. - pub fn line_spacing(&self) -> f64 { + pub fn line_spacing(&self) -> Length { self.line_spacing.eval(self.font_size()) } /// The absolute paragraph spacing. - pub fn par_spacing(&self) -> f64 { + pub fn par_spacing(&self) -> Length { self.par_spacing.eval(self.font_size()) } } @@ -105,10 +103,10 @@ impl Default for TextState { }, strong: false, emph: false, - font_size: FontSize::abs(Length::pt(11.0).as_raw()), - word_spacing: Linear::rel(0.25), - line_spacing: Linear::rel(0.2), - par_spacing: Linear::rel(0.5), + font_size: FontSize::abs(Length::pt(11.0)), + word_spacing: Relative::new(0.25).into(), + line_spacing: Relative::new(0.2).into(), + par_spacing: Relative::new(0.5).into(), } } } @@ -117,7 +115,7 @@ impl Default for TextState { #[derive(Debug, Clone, PartialEq)] pub struct FontSize { /// The base font size, updated whenever the font size is set absolutely. - pub base: f64, + pub base: Length, /// The scale to apply on the base font size, updated when the font size /// is set relatively. pub scale: Linear, @@ -125,17 +123,17 @@ pub struct FontSize { impl FontSize { /// Create a new font size. - pub fn new(base: f64, scale: Linear) -> Self { - Self { base, scale } + pub fn new(base: Length, scale: impl Into) -> Self { + Self { base, scale: scale.into() } } /// Create a new font size with the given `base` and a scale of `1.0`. - pub fn abs(base: f64) -> Self { - Self::new(base, Linear::rel(1.0)) + pub fn abs(base: Length) -> Self { + Self::new(base, Relative::ONE) } /// Compute the absolute font size. - pub fn eval(&self) -> f64 { + pub fn eval(&self) -> Length { self.scale.eval(self.base) } } diff --git a/src/eval/value.rs b/src/eval/value.rs index c4b11ebe2..56dadfc3c 100644 --- a/src/eval/value.rs +++ b/src/eval/value.rs @@ -6,7 +6,7 @@ use std::rc::Rc; use super::{Args, Dict, Eval, EvalContext, SpannedEntry}; use crate::color::RgbaColor; -use crate::geom::Linear; +use crate::geom::{Length, Linear, Relative}; use crate::syntax::{Ident, SynTree}; /// A computational value. @@ -23,14 +23,9 @@ pub enum Value { /// A floating-point number: `1.2, 200%`. Float(f64), /// A length: `2cm, 5.2in`. - Length(f64), + Length(Length), /// A relative value: `50%`. - /// - /// _Note_: `50%` is represented as `0.5` here, but as `50.0` in the - /// corresponding [literal]. - /// - /// [literal]: ../syntax/ast/enum.Lit.html#variant.Percent - Relative(f64), + Relative(Relative), /// A combination of an absolute length and a relative value: `20% + 5cm`. Linear(Linear), /// A color value with alpha channel: `#f79143ff`. diff --git a/src/export/pdf.rs b/src/export/pdf.rs index 02b544717..722728ff4 100644 --- a/src/export/pdf.rs +++ b/src/export/pdf.rs @@ -14,8 +14,8 @@ use tide::{PdfWriter, Rect, Ref, Trailer, Version}; use ttf_parser::{name_id, GlyphId}; use crate::font::FontLoader; +use crate::geom::Length; use crate::layout::{BoxLayout, LayoutElement}; -use crate::length::Length; /// Export a list of layouts into a _PDF_ document. /// @@ -110,8 +110,8 @@ impl<'a, W: Write> PdfExporter<'a, W> { let rect = Rect::new( 0.0, 0.0, - Length::raw(page.size.width).as_pt() as f32, - Length::raw(page.size.height).as_pt() as f32, + page.size.width.to_pt() as f32, + page.size.height.to_pt() as f32, ); self.writer.write_obj( @@ -136,7 +136,7 @@ impl<'a, W: Write> PdfExporter<'a, W> { // Font switching actions are only written when the face used for // shaped text changes. Hence, we need to remember the active face. let mut face = FaceId::MAX; - let mut size = 0.0; + let mut size = Length::ZERO; for (pos, element) in &page.elements { match element { @@ -147,12 +147,12 @@ impl<'a, W: Write> PdfExporter<'a, W> { size = shaped.size; text.tf( self.to_pdf[&shaped.face] as u32 + 1, - Length::raw(size).as_pt() as f32, + size.to_pt() as f32, ); } - let x = Length::raw(pos.x).as_pt(); - let y = Length::raw(page.size.height - pos.y - size).as_pt(); + let x = pos.x.to_pt(); + let y = (page.size.height - pos.y - size).to_pt(); text.tm(1.0, 0.0, 0.0, 1.0, x as f32, y as f32); text.tj(shaped.encode_glyphs_be()); } diff --git a/src/geom.rs b/src/geom.rs deleted file mode 100644 index 90c7ac622..000000000 --- a/src/geom.rs +++ /dev/null @@ -1,235 +0,0 @@ -//! Geometrical types. - -#[doc(no_inline)] -pub use kurbo::*; - -use std::fmt::{self, Debug, Formatter}; -use std::ops::*; - -use crate::layout::{Dir, Gen2, GenAlign, Get, Side, Spec2, SpecAxis, Switch}; - -macro_rules! impl_2d { - ($t:ty, $x:ident, $y:ident) => { - impl Get for $t { - type Component = f64; - - fn get(self, axis: SpecAxis) -> f64 { - match axis { - SpecAxis::Horizontal => self.$x, - SpecAxis::Vertical => self.$y, - } - } - - fn get_mut(&mut self, axis: SpecAxis) -> &mut f64 { - match axis { - SpecAxis::Horizontal => &mut self.$x, - SpecAxis::Vertical => &mut self.$y, - } - } - } - - impl Switch for $t { - type Other = Gen2; - - fn switch(self, dirs: Gen2) -> Self::Other { - Spec2::new(self.$x, self.$y).switch(dirs) - } - } - }; -} - -impl_2d!(Point, x, y); -impl_2d!(Vec2, x, y); -impl_2d!(Size, width, height); - -impl Get for Rect { - type Component = f64; - - fn get(self, side: Side) -> f64 { - match side { - Side::Left => self.x0, - Side::Top => self.y0, - Side::Right => self.x1, - Side::Bottom => self.y1, - } - } - - fn get_mut(&mut self, side: Side) -> &mut f64 { - match side { - Side::Left => &mut self.x0, - Side::Top => &mut self.y0, - Side::Right => &mut self.x1, - Side::Bottom => &mut self.y1, - } - } -} - -/// Additional methods for [sizes]. -/// -/// [sizes]: ../../kurbo/struct.Size.html -pub trait SizeExt { - /// Whether the given size fits into this one, that is, both coordinate - /// values are smaller or equal. - fn fits(self, other: Self) -> bool; - - /// The anchor position for an object to be aligned in a container with this - /// size and the given directions. - fn anchor(self, dirs: Gen2, aligns: Gen2) -> Point; -} - -impl SizeExt for Size { - fn fits(self, other: Self) -> bool { - self.width >= other.width && self.height >= other.height - } - - fn anchor(self, dirs: Gen2, aligns: Gen2) -> Point { - fn anchor(length: f64, dir: Dir, align: GenAlign) -> f64 { - match if dir.is_positive() { align } else { align.inv() } { - GenAlign::Start => 0.0, - GenAlign::Center => length / 2.0, - GenAlign::End => length, - } - } - - let switched = self.switch(dirs); - let generic = Gen2::new( - anchor(switched.main, dirs.main, aligns.main), - anchor(switched.cross, dirs.cross, aligns.cross), - ); - - generic.switch(dirs).to_point() - } -} - -/// A function that depends linearly on one value. -/// -/// This represents a function `f(x) = rel * x + abs`. -#[derive(Copy, Clone, PartialEq)] -pub struct Linear { - /// The relative part. - pub rel: f64, - /// The absolute part. - pub abs: f64, -} - -impl Linear { - /// The constant zero function. - pub const ZERO: Linear = Linear { rel: 0.0, abs: 0.0 }; - - /// Create a new linear function. - pub fn new(rel: f64, abs: f64) -> Self { - Self { rel, abs } - } - - /// Create a new linear function with only a relative component. - pub fn rel(rel: f64) -> Self { - Self { rel, abs: 0.0 } - } - - /// Create a new linear function with only an absolute component. - pub fn abs(abs: f64) -> Self { - Self { rel: 0.0, abs } - } - - /// Evaluate the linear function with the given value. - pub fn eval(self, x: f64) -> f64 { - self.rel * x + self.abs - } -} - -impl Add for Linear { - type Output = Self; - - fn add(self, other: Self) -> Self { - Self { - rel: self.rel + other.rel, - abs: self.abs + other.abs, - } - } -} - -impl AddAssign for Linear { - fn add_assign(&mut self, other: Self) { - self.rel += other.rel; - self.abs += other.abs; - } -} - -impl Sub for Linear { - type Output = Self; - - fn sub(self, other: Self) -> Self { - Self { - rel: self.rel - other.rel, - abs: self.abs - other.abs, - } - } -} - -impl SubAssign for Linear { - fn sub_assign(&mut self, other: Self) { - self.rel -= other.rel; - self.abs -= other.abs; - } -} - -impl Mul for Linear { - type Output = Self; - - fn mul(self, other: f64) -> Self { - Self { - rel: self.rel * other, - abs: self.abs * other, - } - } -} - -impl MulAssign for Linear { - fn mul_assign(&mut self, other: f64) { - self.rel *= other; - self.abs *= other; - } -} - -impl Mul for f64 { - type Output = Linear; - - fn mul(self, other: Linear) -> Linear { - Linear { - rel: self * other.rel, - abs: self * other.abs, - } - } -} - -impl Div for Linear { - type Output = Self; - - fn div(self, other: f64) -> Self { - Self { - rel: self.rel / other, - abs: self.abs / other, - } - } -} - -impl DivAssign for Linear { - fn div_assign(&mut self, other: f64) { - self.rel /= other; - self.abs /= other; - } -} - -impl Neg for Linear { - type Output = Self; - - fn neg(self) -> Self { - Self { rel: -self.rel, abs: -self.abs } - } -} - -impl Debug for Linear { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - write!(f, "{}x + {}", self.rel, self.abs) - } -} diff --git a/src/geom/align.rs b/src/geom/align.rs new file mode 100644 index 000000000..1030a133c --- /dev/null +++ b/src/geom/align.rs @@ -0,0 +1,48 @@ +use super::*; + +/// Where to align something along a directed axis. +#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)] +pub enum Align { + /// Align at the start of the axis. + Start, + /// Align in the middle of the axis. + Center, + /// Align at the end of the axis. + End, +} + +impl Align { + /// Returns the position of this alignment in the given range. + pub fn apply(self, range: Range) -> Length { + match self { + Self::Start => range.start, + Self::Center => (range.start + range.end) / 2.0, + Self::End => range.end, + } + } + + /// The inverse alignment. + pub fn inv(self) -> Self { + match self { + Self::Start => Self::End, + Self::Center => Self::Center, + Self::End => Self::Start, + } + } +} + +impl Default for Align { + fn default() -> Self { + Self::Start + } +} + +impl Display for Align { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + f.pad(match self { + Self::Start => "start", + Self::Center => "center", + Self::End => "end", + }) + } +} diff --git a/src/geom/dir.rs b/src/geom/dir.rs new file mode 100644 index 000000000..cfcb4c09a --- /dev/null +++ b/src/geom/dir.rs @@ -0,0 +1,83 @@ +use super::*; + +/// The four directions into which content can be laid out. +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +pub enum Dir { + /// Left to right. + LTR, + /// Right to left. + RTL, + /// Top to bottom. + TTB, + /// Bottom to top. + BTT, +} + +impl Dir { + /// The side this direction starts at. + pub fn start(self) -> Side { + match self { + Self::LTR => Side::Left, + Self::RTL => Side::Right, + Self::TTB => Side::Top, + Self::BTT => Side::Bottom, + } + } + + /// The side this direction ends at. + pub fn end(self) -> Side { + match self { + Self::LTR => Side::Right, + Self::RTL => Side::Left, + Self::TTB => Side::Bottom, + Self::BTT => Side::Top, + } + } + + /// The specific axis this direction belongs to. + pub fn axis(self) -> SpecAxis { + match self { + Self::LTR | Self::RTL => SpecAxis::Horizontal, + Self::TTB | Self::BTT => SpecAxis::Vertical, + } + } + + /// Whether this direction points into the positive coordinate direction. + /// + /// The positive directions are left-to-right and top-to-bottom. + pub fn is_positive(self) -> bool { + match self { + Self::LTR | Self::TTB => true, + Self::RTL | Self::BTT => false, + } + } + + /// The factor for this direction. + /// + /// - `1.0` if the direction is positive. + /// - `-1.0` if the direction is negative. + pub fn factor(self) -> f64 { + if self.is_positive() { 1.0 } else { -1.0 } + } + + /// The inverse direction. + pub fn inv(self) -> Self { + match self { + Self::LTR => Self::RTL, + Self::RTL => Self::LTR, + Self::TTB => Self::BTT, + Self::BTT => Self::TTB, + } + } +} + +impl Display for Dir { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + f.pad(match self { + Self::LTR => "ltr", + Self::RTL => "rtl", + Self::TTB => "ttb", + Self::BTT => "btt", + }) + } +} diff --git a/src/geom/gen.rs b/src/geom/gen.rs new file mode 100644 index 000000000..d877713b7 --- /dev/null +++ b/src/geom/gen.rs @@ -0,0 +1,90 @@ +use super::*; + +/// A container with a main and cross component. +#[derive(Debug, Default, Copy, Clone, Eq, PartialEq)] +pub struct Gen { + /// The main component. + pub main: T, + /// The cross component. + pub cross: T, +} + +impl Gen { + /// Create a new instance from the two components. + pub fn new(main: T, cross: T) -> Self { + Self { main, cross } + } +} + +impl Gen { + /// The zero value. + pub const ZERO: Self = Self { main: Length::ZERO, cross: Length::ZERO }; +} + +impl Get for Gen { + type Component = T; + + fn get(self, axis: GenAxis) -> T { + match axis { + GenAxis::Main => self.main, + GenAxis::Cross => self.cross, + } + } + + fn get_mut(&mut self, axis: GenAxis) -> &mut T { + match axis { + GenAxis::Main => &mut self.main, + GenAxis::Cross => &mut self.cross, + } + } +} + +impl Switch for Gen { + type Other = Spec; + + fn switch(self, dirs: Gen) -> Self::Other { + match dirs.main.axis() { + SpecAxis::Horizontal => Spec::new(self.main, self.cross), + SpecAxis::Vertical => Spec::new(self.cross, self.main), + } + } +} + +/// The two generic layouting axes. +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +pub enum GenAxis { + /// The axis pages and paragraphs are set along. + Main, + /// The axis words and lines are set along. + Cross, +} + +impl GenAxis { + /// The other axis. + pub fn other(self) -> Self { + match self { + Self::Main => Self::Cross, + Self::Cross => Self::Main, + } + } +} + +impl Switch for GenAxis { + type Other = SpecAxis; + + fn switch(self, dirs: Gen) -> Self::Other { + match self { + Self::Main => dirs.main.axis(), + Self::Cross => dirs.cross.axis(), + } + } +} + +impl Display for GenAxis { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + f.pad(match self { + Self::Main => "main", + Self::Cross => "cross", + }) + } +} diff --git a/src/geom/length.rs b/src/geom/length.rs new file mode 100644 index 000000000..60ccce2be --- /dev/null +++ b/src/geom/length.rs @@ -0,0 +1,213 @@ +use super::*; + +/// An absolute length. +#[derive(Default, Copy, Clone, PartialEq, PartialOrd)] +pub struct Length { + /// The length in raw units. + raw: f64, +} + +impl Length { + /// The zero length. + pub const ZERO: Self = Self { raw: 0.0 }; + + /// Create a length from a number of points. + pub fn pt(pt: f64) -> Self { + Self::with_unit(pt, Unit::Pt) + } + + /// Create a length from a number of millimeters. + pub fn mm(mm: f64) -> Self { + Self::with_unit(mm, Unit::Mm) + } + + /// Create a length from a number of centimeters. + pub fn cm(cm: f64) -> Self { + Self::with_unit(cm, Unit::Cm) + } + + /// Create a length from a number of inches. + pub fn inches(inches: f64) -> Self { + Self::with_unit(inches, Unit::In) + } + + /// Create a length from a number of raw units. + pub fn raw(raw: f64) -> Self { + Self { raw } + } + + /// Convert this to a number of points. + pub fn to_pt(self) -> f64 { + self.to_unit(Unit::Pt) + } + + /// Convert this to a number of millimeters. + pub fn to_mm(self) -> f64 { + self.to_unit(Unit::Mm) + } + + /// Convert this to a number of centimeters. + pub fn to_cm(self) -> f64 { + self.to_unit(Unit::Cm) + } + + /// Convert this to a number of inches. + pub fn to_inches(self) -> f64 { + self.to_unit(Unit::In) + } + + /// Get the value of this length in raw units. + pub fn to_raw(self) -> f64 { + self.raw + } + + /// Create a length from a value in a unit. + pub fn with_unit(val: f64, unit: Unit) -> Self { + Self { raw: val * unit.raw_scale() } + } + + /// Get the value of this length in unit. + pub fn to_unit(self, unit: Unit) -> f64 { + self.raw / unit.raw_scale() + } + + /// The minimum of this and another length. + pub fn min(self, other: Self) -> Self { + Self { raw: self.raw.min(other.raw) } + } + + /// The maximum of this and another length. + pub fn max(self, other: Self) -> Self { + Self { raw: self.raw.max(other.raw) } + } +} + +impl Display for Length { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + // Format small lengths as points and large ones as centimeters. + let (val, unit) = if self.to_pt().abs() < 25.0 { + (self.to_pt(), Unit::Pt) + } else { + (self.to_cm(), Unit::Cm) + }; + write!(f, "{:.2}{}", val, unit) + } +} + +impl Debug for Length { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + Display::fmt(self, f) + } +} + +impl Neg for Length { + type Output = Self; + + fn neg(self) -> Self { + Self { raw: -self.raw } + } +} + +impl Add for Length { + type Output = Self; + + fn add(self, other: Self) -> Self { + Self { raw: self.raw + other.raw } + } +} + +sub_impl!(Length - Length -> Length); + +impl Mul for Length { + type Output = Self; + + fn mul(self, other: f64) -> Self { + Self { raw: self.raw * other } + } +} + +impl Mul for f64 { + type Output = Length; + + fn mul(self, other: Length) -> Length { + other * self + } +} + +impl Div for Length { + type Output = Self; + + fn div(self, other: f64) -> Self { + Self { raw: self.raw / other } + } +} + +assign_impl!(Length += Length); +assign_impl!(Length -= Length); +assign_impl!(Length *= f64); +assign_impl!(Length /= f64); + +impl Sum for Length { + fn sum>(iter: I) -> Self { + iter.fold(Length::ZERO, Add::add) + } +} + +/// Different units of measurement. +#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd)] +pub enum Unit { + /// Points. + Pt, + /// Millimeters. + Mm, + /// Centimeters. + Cm, + /// Inches. + In, +} + +impl Unit { + /// How many raw units correspond to a value of `1.0` in this unit. + fn raw_scale(self) -> f64 { + match self { + Unit::Pt => 1.0, + Unit::Mm => 2.83465, + Unit::Cm => 28.3465, + Unit::In => 72.0, + } + } +} + +impl Display for Unit { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + f.pad(match self { + Unit::Mm => "mm", + Unit::Pt => "pt", + Unit::Cm => "cm", + Unit::In => "in", + }) + } +} + +impl Debug for Unit { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + Display::fmt(self, f) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_length_formats_correctly() { + assert_eq!(Length::pt(-28.34).to_string(), "-1.00cm".to_string()); + assert_eq!(Length::pt(23.0).to_string(), "23.00pt".to_string()); + assert_eq!(Length::cm(12.728).to_string(), "12.73cm".to_string()); + } + + #[test] + fn test_length_unit_conversion() { + assert!((Length::mm(150.0).to_cm() - 15.0) < 1e-4); + } +} diff --git a/src/geom/linear.rs b/src/geom/linear.rs new file mode 100644 index 000000000..2567d264c --- /dev/null +++ b/src/geom/linear.rs @@ -0,0 +1,172 @@ +use super::*; + +/// A combined relative and absolute length. +#[derive(Default, Copy, Clone, PartialEq)] +pub struct Linear { + /// The relative part. + pub rel: Relative, + /// The absolute part. + pub abs: Length, +} + +impl Linear { + /// The zero linear. + pub const ZERO: Linear = Linear { rel: Relative::ZERO, abs: Length::ZERO }; + + /// Create a new linear. + pub fn new(rel: Relative, abs: Length) -> Self { + Self { rel, abs } + } + + /// Evaluate the linear length with `one` being `100%` for the relative + /// part. + pub fn eval(self, one: Length) -> Length { + self.rel.eval(one) + self.abs + } + + /// Whether this linear's relative component is zero. + pub fn is_absolute(self) -> bool { + self.rel == Relative::ZERO + } +} + +impl Display for Linear { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + write!(f, "{} + {}", self.rel, self.abs) + } +} + +impl Debug for Linear { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + Display::fmt(self, f) + } +} + +impl From for Linear { + fn from(abs: Length) -> Self { + Self { rel: Relative::ZERO, abs } + } +} + +impl From for Linear { + fn from(rel: Relative) -> Self { + Self { rel, abs: Length::ZERO } + } +} + +impl Neg for Linear { + type Output = Self; + + fn neg(self) -> Self { + Self { rel: -self.rel, abs: -self.abs } + } +} + +impl Add for Linear { + type Output = Self; + + fn add(self, other: Self) -> Self { + Self { + rel: self.rel + other.rel, + abs: self.abs + other.abs, + } + } +} + +impl Add for Length { + type Output = Linear; + + fn add(self, other: Relative) -> Linear { + Linear { rel: other, abs: self } + } +} + +impl Add for Relative { + type Output = Linear; + + fn add(self, other: Length) -> Linear { + other + self + } +} + +impl Add for Linear { + type Output = Self; + + fn add(self, other: Length) -> Self { + Self { rel: self.rel, abs: self.abs + other } + } +} + +impl Add for Length { + type Output = Linear; + + fn add(self, other: Linear) -> Linear { + other + self + } +} + +impl Add for Linear { + type Output = Self; + + fn add(self, other: Relative) -> Self { + Self { rel: self.rel + other, abs: self.abs } + } +} + +impl Add for Relative { + type Output = Linear; + + fn add(self, other: Linear) -> Linear { + other + self + } +} + +sub_impl!(Linear - Linear -> Linear); +sub_impl!(Length - Relative -> Linear); +sub_impl!(Relative - Length -> Linear); +sub_impl!(Linear - Length -> Linear); +sub_impl!(Length - Linear -> Linear); +sub_impl!(Linear - Relative -> Linear); +sub_impl!(Relative - Linear -> Linear); + +impl Mul for Linear { + type Output = Self; + + fn mul(self, other: f64) -> Self { + Self { + rel: self.rel * other, + abs: self.abs * other, + } + } +} + +impl Mul for f64 { + type Output = Linear; + + fn mul(self, other: Linear) -> Linear { + Linear { + rel: self * other.rel, + abs: self * other.abs, + } + } +} + +impl Div for Linear { + type Output = Self; + + fn div(self, other: f64) -> Self { + Self { + rel: self.rel / other, + abs: self.abs / other, + } + } +} + +assign_impl!(Linear += Linear); +assign_impl!(Linear += Length); +assign_impl!(Linear += Relative); +assign_impl!(Linear -= Linear); +assign_impl!(Linear -= Length); +assign_impl!(Linear -= Relative); +assign_impl!(Linear *= f64); +assign_impl!(Linear /= f64); diff --git a/src/geom/macros.rs b/src/geom/macros.rs new file mode 100644 index 000000000..615eb31c5 --- /dev/null +++ b/src/geom/macros.rs @@ -0,0 +1,47 @@ +/// Implement the `Sub` trait based on existing `Neg` and `Add` impls. +macro_rules! sub_impl { + ($a:ident - $b:ident -> $c:ident) => { + impl Sub<$b> for $a { + type Output = $c; + + fn sub(self, other: $b) -> $c { + self + -other + } + } + }; +} + +/// Implement an assign trait based on an existing non-assign trait. +macro_rules! assign_impl { + ($a:ident += $b:ident) => { + impl AddAssign<$b> for $a { + fn add_assign(&mut self, other: $b) { + *self = *self + other; + } + } + }; + + ($a:ident -= $b:ident) => { + impl SubAssign<$b> for $a { + fn sub_assign(&mut self, other: $b) { + *self = *self - other; + } + } + }; + + ($a:ident *= $b:ident) => { + impl MulAssign<$b> for $a { + fn mul_assign(&mut self, other: $b) { + *self = *self * other; + } + } + }; + + ($a:ident /= $b:ident) => { + impl DivAssign<$b> for $a { + fn div_assign(&mut self, other: $b) { + *self = *self / other; + } + } + }; +} diff --git a/src/geom/mod.rs b/src/geom/mod.rs new file mode 100644 index 000000000..c9c3040c8 --- /dev/null +++ b/src/geom/mod.rs @@ -0,0 +1,53 @@ +//! Geometrical primitivies. + +#[macro_use] +mod macros; +mod align; +mod dir; +mod gen; +mod length; +mod linear; +mod point; +mod relative; +mod sides; +mod size; +mod spec; + +pub use align::*; +pub use dir::*; +pub use gen::*; +pub use length::*; +pub use linear::*; +pub use point::*; +pub use relative::*; +pub use sides::*; +pub use size::*; +pub use spec::*; + +use std::fmt::{self, Debug, Display, Formatter}; +use std::iter::Sum; +use std::ops::*; + +/// Generic access to a structure's components. +pub trait Get { + /// The structure's component type. + type Component; + + /// Return the component for the specified index. + fn get(self, index: Index) -> Self::Component; + + /// Borrow the component for the specified index mutably. + fn get_mut(&mut self, index: Index) -> &mut Self::Component; +} + +/// Switch between the specific and generic representations of a type. +/// +/// The generic representation deals with main and cross axes while the specific +/// representation deals with horizontal and vertical axes. +pub trait Switch { + /// The type of the other version. + type Other; + + /// The other version of this type based on the current directions. + fn switch(self, dirs: Gen) -> Self::Other; +} diff --git a/src/geom/point.rs b/src/geom/point.rs new file mode 100644 index 000000000..31b84d81a --- /dev/null +++ b/src/geom/point.rs @@ -0,0 +1,102 @@ +use super::*; + +/// A point in 2D. +#[derive(Default, Copy, Clone, PartialEq)] +pub struct Point { + /// The x coordinate. + pub x: Length, + /// The y coordinate. + pub y: Length, +} + +impl Point { + /// The origin point. + pub const ZERO: Self = Self { x: Length::ZERO, y: Length::ZERO }; + + /// Create a new point from x and y coordinate. + pub fn new(x: Length, y: Length) -> Self { + Self { x, y } + } +} + +impl Get for Point { + type Component = Length; + + fn get(self, axis: SpecAxis) -> Length { + match axis { + SpecAxis::Horizontal => self.x, + SpecAxis::Vertical => self.y, + } + } + + fn get_mut(&mut self, axis: SpecAxis) -> &mut Length { + match axis { + SpecAxis::Horizontal => &mut self.x, + SpecAxis::Vertical => &mut self.y, + } + } +} + +impl Switch for Point { + type Other = Gen; + + fn switch(self, dirs: Gen) -> Self::Other { + match dirs.main.axis() { + SpecAxis::Horizontal => Gen::new(self.x, self.y), + SpecAxis::Vertical => Gen::new(self.y, self.x), + } + } +} + +impl Debug for Point { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + write!(f, "({}, {})", self.x, self.y) + } +} + +impl Neg for Point { + type Output = Self; + + fn neg(self) -> Self { + Self { x: -self.x, y: -self.y } + } +} + +impl Add for Point { + type Output = Self; + + fn add(self, other: Self) -> Self { + Self { x: self.x + other.x, y: self.y + other.y } + } +} + +sub_impl!(Point - Point -> Point); + +impl Mul for Point { + type Output = Self; + + fn mul(self, other: f64) -> Self { + Self { x: self.x * other, y: self.y * other } + } +} + +impl Mul for f64 { + type Output = Point; + + fn mul(self, other: Point) -> Point { + other * self + } +} + +impl Div for Point { + type Output = Self; + + fn div(self, other: f64) -> Self { + Self { x: self.x / other, y: self.y / other } + } +} + +assign_impl!(Point += Point); +assign_impl!(Point -= Point); +assign_impl!(Point *= f64); +assign_impl!(Point /= f64); diff --git a/src/geom/relative.rs b/src/geom/relative.rs new file mode 100644 index 000000000..037c83dc9 --- /dev/null +++ b/src/geom/relative.rs @@ -0,0 +1,92 @@ +use super::*; + +/// A relative length. +/// +/// _Note_: `50%` is represented as `0.5` here, but stored as `50.0` in the +/// corresponding [literal]. +/// +/// [literal]: ../syntax/ast/enum.Lit.html#variant.Percent +#[derive(Default, Copy, Clone, PartialEq, PartialOrd)] +pub struct Relative(f64); + +impl Relative { + /// A ratio of `0%` represented as `0.0`. + pub const ZERO: Self = Self(0.0); + + /// A ratio of `100%` represented as `1.0`. + pub const ONE: Self = Self(1.0); + + /// Create a new relative value. + pub fn new(ratio: f64) -> Self { + Self(ratio) + } + + /// Get the underlying ratio. + pub fn get(self) -> f64 { + self.0 + } + + /// Evaluate the relative length with `one` being `100%`. + pub fn eval(self, one: Length) -> Length { + self.get() * one + } +} + +impl Display for Relative { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + write!(f, "{:.2}%", self.0) + } +} + +impl Debug for Relative { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + Display::fmt(self, f) + } +} + +impl Neg for Relative { + type Output = Self; + + fn neg(self) -> Self { + Self(-self.0) + } +} + +impl Add for Relative { + type Output = Self; + + fn add(self, other: Self) -> Self { + Self(self.0 + other.0) + } +} + +sub_impl!(Relative - Relative -> Relative); + +impl Mul for Relative { + type Output = Self; + + fn mul(self, other: f64) -> Self { + Self(self.0 * other) + } +} + +impl Mul for f64 { + type Output = Relative; + + fn mul(self, other: Relative) -> Relative { + other * self + } +} + +impl Div for Relative { + type Output = Self; + + fn div(self, other: f64) -> Self { + Self(self.0 / other) + } +} + +assign_impl!(Relative += Relative); +assign_impl!(Relative -= Relative); +assign_impl!(Relative *= f64); +assign_impl!(Relative /= f64); diff --git a/src/geom/sides.rs b/src/geom/sides.rs new file mode 100644 index 000000000..770fad58b --- /dev/null +++ b/src/geom/sides.rs @@ -0,0 +1,101 @@ +use super::*; + +/// A container with left, top, right and bottom components. +#[derive(Debug, Default, Copy, Clone, Eq, PartialEq)] +pub struct Sides { + /// The value for the left side. + pub left: T, + /// The value for the top side. + pub top: T, + /// The value for the right side. + pub right: T, + /// The value for the bottom side. + pub bottom: T, +} + +impl Sides { + /// Create a new box from four sizes. + pub fn new(left: T, top: T, right: T, bottom: T) -> Self { + Self { left, top, right, bottom } + } + + /// Create an instance with all four components set to the same `value`. + pub fn uniform(value: T) -> Self + where + T: Clone, + { + Self { + left: value.clone(), + top: value.clone(), + right: value.clone(), + bottom: value, + } + } +} + +impl Sides { + /// Evaluate the linear values in this container. + pub fn eval(self, size: Size) -> Sides { + Sides { + left: self.left.eval(size.width), + top: self.top.eval(size.height), + right: self.right.eval(size.width), + bottom: self.bottom.eval(size.height), + } + } +} + +impl Sides { + /// A size with `left` and `right` summed into `width`, and `top` and + /// `bottom` summed into `height`. + pub fn size(self) -> Size { + Size::new(self.left + self.right, self.top + self.bottom) + } +} + +impl Get for Sides { + type Component = T; + + fn get(self, side: Side) -> T { + match side { + Side::Left => self.left, + Side::Top => self.top, + Side::Right => self.right, + Side::Bottom => self.bottom, + } + } + + fn get_mut(&mut self, side: Side) -> &mut T { + match side { + Side::Left => &mut self.left, + Side::Top => &mut self.top, + Side::Right => &mut self.right, + Side::Bottom => &mut self.bottom, + } + } +} + +/// The four sides of objects. +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +pub enum Side { + /// The left side. + Left, + /// The top side. + Top, + /// The right side. + Right, + /// The bottom side. + Bottom, +} + +impl Side { + /// The opposite side. + pub fn inv(self) -> Self { + match self { + Self::Left => Self::Right, + Self::Top => Self::Bottom, + Self::Right => Self::Left, + Self::Bottom => Self::Top, + } + } +} diff --git a/src/geom/size.rs b/src/geom/size.rs new file mode 100644 index 000000000..8a3951f7d --- /dev/null +++ b/src/geom/size.rs @@ -0,0 +1,119 @@ +use super::*; + +/// A size in 2D. +#[derive(Default, Copy, Clone, PartialEq)] +pub struct Size { + /// The width. + pub width: Length, + /// The height. + pub height: Length, +} + +impl Size { + /// The zero size. + pub const ZERO: Self = Self { + width: Length::ZERO, + height: Length::ZERO, + }; + + /// Create a new size from width and height. + pub fn new(width: Length, height: Length) -> Self { + Self { width, height } + } + + /// Whether the other size fits into this one (smaller width and height). + pub fn fits(self, other: Self) -> bool { + self.width >= other.width && self.height >= other.height + } +} + +impl Get for Size { + type Component = Length; + + fn get(self, axis: SpecAxis) -> Length { + match axis { + SpecAxis::Horizontal => self.width, + SpecAxis::Vertical => self.height, + } + } + + fn get_mut(&mut self, axis: SpecAxis) -> &mut Length { + match axis { + SpecAxis::Horizontal => &mut self.width, + SpecAxis::Vertical => &mut self.height, + } + } +} + +impl Switch for Size { + type Other = Gen; + + fn switch(self, dirs: Gen) -> Self::Other { + match dirs.main.axis() { + SpecAxis::Horizontal => Gen::new(self.width, self.height), + SpecAxis::Vertical => Gen::new(self.height, self.width), + } + } +} + +impl Debug for Size { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + write!(f, "({} x {})", self.width, self.height) + } +} + +impl Neg for Size { + type Output = Self; + + fn neg(self) -> Self { + Self { width: -self.width, height: -self.height } + } +} + +impl Add for Size { + type Output = Self; + + fn add(self, other: Self) -> Self { + Self { + width: self.width + other.width, + height: self.height + other.height, + } + } +} + +sub_impl!(Size - Size -> Size); + +impl Mul for Size { + type Output = Self; + + fn mul(self, other: f64) -> Self { + Self { + width: self.width * other, + height: self.height * other, + } + } +} + +impl Mul for f64 { + type Output = Size; + + fn mul(self, other: Size) -> Size { + other * self + } +} + +impl Div for Size { + type Output = Self; + + fn div(self, other: f64) -> Self { + Self { + width: self.width / other, + height: self.height / other, + } + } +} + +assign_impl!(Size -= Size); +assign_impl!(Size += Size); +assign_impl!(Size *= f64); +assign_impl!(Size /= f64); diff --git a/src/geom/spec.rs b/src/geom/spec.rs new file mode 100644 index 000000000..8a9519bc5 --- /dev/null +++ b/src/geom/spec.rs @@ -0,0 +1,105 @@ +use super::*; + +/// A container with a horizontal and vertical component. +#[derive(Debug, Default, Copy, Clone, Eq, PartialEq)] +pub struct Spec { + /// The horizontal component. + pub horizontal: T, + /// The vertical component. + pub vertical: T, +} + +impl Spec { + /// Create a new instance from the two components. + pub fn new(horizontal: T, vertical: T) -> Self { + Self { horizontal, vertical } + } +} + +impl Spec { + /// The zero value. + pub const ZERO: Self = Self { + horizontal: Length::ZERO, + vertical: Length::ZERO, + }; + + /// Convert to a point. + pub fn to_point(self) -> Point { + Point::new(self.horizontal, self.vertical) + } + + /// Convert to a size. + pub fn to_size(self) -> Size { + Size::new(self.horizontal, self.vertical) + } +} + +impl Get for Spec { + type Component = T; + + fn get(self, axis: SpecAxis) -> T { + match axis { + SpecAxis::Horizontal => self.horizontal, + SpecAxis::Vertical => self.vertical, + } + } + + fn get_mut(&mut self, axis: SpecAxis) -> &mut T { + match axis { + SpecAxis::Horizontal => &mut self.horizontal, + SpecAxis::Vertical => &mut self.vertical, + } + } +} + +impl Switch for Spec { + type Other = Gen; + + fn switch(self, dirs: Gen) -> Self::Other { + match dirs.main.axis() { + SpecAxis::Horizontal => Gen::new(self.horizontal, self.vertical), + SpecAxis::Vertical => Gen::new(self.vertical, self.horizontal), + } + } +} + +/// The two specific layouting axes. +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +pub enum SpecAxis { + /// The vertical layouting axis. + Vertical, + /// The horizontal layouting axis. + Horizontal, +} + +impl SpecAxis { + /// The other axis. + pub fn other(self) -> Self { + match self { + Self::Horizontal => Self::Vertical, + Self::Vertical => Self::Horizontal, + } + } +} + +impl Switch for SpecAxis { + type Other = GenAxis; + + fn switch(self, dirs: Gen) -> Self::Other { + if self == dirs.main.axis() { + GenAxis::Main + } else { + debug_assert_eq!(self, dirs.cross.axis()); + GenAxis::Cross + } + } +} + +impl Display for SpecAxis { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + f.pad(match self { + Self::Vertical => "vertical", + Self::Horizontal => "horizontal", + }) + } +} diff --git a/src/layout/nodes/document.rs b/src/layout/document.rs similarity index 97% rename from src/layout/nodes/document.rs rename to src/layout/document.rs index 5c7a24109..c2d7b38bb 100644 --- a/src/layout/nodes/document.rs +++ b/src/layout/document.rs @@ -1,6 +1,6 @@ use super::*; -/// The top-level layouting node. +/// The top-level layout node. #[derive(Debug, Clone, PartialEq)] pub struct Document { pub runs: Vec, diff --git a/src/layout/nodes/fixed.rs b/src/layout/fixed.rs similarity index 100% rename from src/layout/nodes/fixed.rs rename to src/layout/fixed.rs diff --git a/src/layout/mod.rs b/src/layout/mod.rs index bfd633d8c..2368c4416 100644 --- a/src/layout/mod.rs +++ b/src/layout/mod.rs @@ -1,17 +1,28 @@ //! Layouting of documents. -pub mod nodes; -pub mod primitive; - -pub use primitive::*; +mod document; +mod fixed; +mod node; +mod pad; +mod par; +mod spacing; +mod stack; +mod text; use async_trait::async_trait; use crate::font::SharedFontLoader; -use crate::geom::{Point, Rect, Size, SizeExt}; +use crate::geom::*; use crate::shaping::Shaped; -use nodes::Document; +pub use document::*; +pub use fixed::*; +pub use node::*; +pub use pad::*; +pub use par::*; +pub use spacing::*; +pub use stack::*; +pub use text::*; /// Layout a document and return the produced layouts. pub async fn layout(document: &Document, loader: SharedFontLoader) -> Vec { @@ -53,9 +64,9 @@ pub trait Layout { #[derive(Debug, Clone, PartialEq)] pub enum LayoutItem { /// Spacing that should be added to the parent. - Spacing(f64), + Spacing(Length), /// A box that should be aligned in the parent. - Box(BoxLayout, Gen2), + Box(BoxLayout, Gen), } /// The constraints for layouting a single node. @@ -101,7 +112,7 @@ impl BoxLayout { /// given position. pub fn push_layout(&mut self, pos: Point, more: Self) { for (subpos, element) in more.elements { - self.push(pos + subpos.to_vec2(), element); + self.push(pos + subpos, element); } } } diff --git a/src/layout/nodes/mod.rs b/src/layout/node.rs similarity index 93% rename from src/layout/nodes/mod.rs rename to src/layout/node.rs index a304e63c6..3230621fd 100644 --- a/src/layout/nodes/mod.rs +++ b/src/layout/node.rs @@ -1,27 +1,9 @@ //! Layout nodes. -mod document; -mod fixed; -mod pad; -mod par; -mod spacing; -mod stack; -mod text; - -pub use document::*; -pub use fixed::*; -pub use pad::*; -pub use par::*; -pub use spacing::*; -pub use stack::*; -pub use text::*; - use std::any::Any; use std::fmt::{self, Debug, Formatter}; use std::ops::Deref; -use async_trait::async_trait; - use super::*; /// A self-contained, styled layout node. diff --git a/src/layout/nodes/pad.rs b/src/layout/pad.rs similarity index 76% rename from src/layout/nodes/pad.rs rename to src/layout/pad.rs index 10a9e2c62..2e1817b7d 100644 --- a/src/layout/nodes/pad.rs +++ b/src/layout/pad.rs @@ -21,8 +21,8 @@ impl Layout for Pad { .spaces .into_iter() .map(|space| LayoutSpace { - base: space.base + self.padding.insets(space.base).size(), - size: space.size + self.padding.insets(space.size).size(), + base: space.base - self.padding.eval(space.base).size(), + size: space.size - self.padding.eval(space.size).size(), }) .collect(), repeat: constraints.repeat, @@ -31,11 +31,11 @@ impl Layout for Pad { .into_iter() .map(|item| match item { LayoutItem::Box(boxed, align) => { - let padding = self.padding.insets(boxed.size); - let padded = boxed.size - padding.size(); + let padding = self.padding.eval(boxed.size); + let padded = boxed.size + padding.size(); let mut outer = BoxLayout::new(padded); - let start = Point::new(-padding.x0, -padding.y0); + let start = Point::new(padding.left, padding.top); outer.push_layout(start, boxed); LayoutItem::Box(outer, align) diff --git a/src/layout/nodes/par.rs b/src/layout/par.rs similarity index 77% rename from src/layout/nodes/par.rs rename to src/layout/par.rs index 082ab9633..2a139760a 100644 --- a/src/layout/nodes/par.rs +++ b/src/layout/par.rs @@ -7,11 +7,11 @@ use super::*; /// the main axis by the height of the previous line plus extra line spacing. #[derive(Debug, Clone, PartialEq)] pub struct Par { - pub dirs: Gen2, - pub line_spacing: f64, + pub dirs: Gen, + pub line_spacing: Length, pub children: Vec, - pub aligns: Gen2, - pub expand: Spec2, + pub aligns: Gen, + pub expand: Spec, } #[async_trait(?Send)] @@ -73,17 +73,17 @@ struct LineLayouter { #[derive(Debug, Clone)] struct LineContext { /// The layout directions. - dirs: Gen2, + dirs: Gen, /// The spaces to layout into. spaces: Vec, /// Whether to spill over into copies of the last space or finish layouting /// when the last space is used up. repeat: bool, /// The spacing to be inserted between each pair of lines. - line_spacing: f64, + line_spacing: Length, /// Whether to expand the size of the resulting layout to the full size of /// this space or to shrink it to fit the content. - expand: Spec2, + expand: Spec, } impl LineLayouter { @@ -102,7 +102,7 @@ impl LineLayouter { } /// Add a layout. - fn push_box(&mut self, layout: BoxLayout, aligns: Gen2) { + fn push_box(&mut self, layout: BoxLayout, aligns: Gen) { let dirs = self.ctx.dirs; if let Some(prev) = self.run.aligns { if aligns.main != prev.main { @@ -124,9 +124,9 @@ impl LineLayouter { // FIXME: Alignment in non-expanding parent. rest_run.usable = Some(match aligns.cross { - GenAlign::Start => unreachable!("start > x"), - GenAlign::Center => usable - 2.0 * self.run.size.cross, - GenAlign::End => usable - self.run.size.cross, + Align::Start => unreachable!("start > x"), + Align::Center => usable - 2.0 * self.run.size.cross, + Align::End => usable - self.run.size.cross, }); self.finish_line(); @@ -160,7 +160,7 @@ impl LineLayouter { } /// Add spacing to the line. - fn push_spacing(&mut self, mut spacing: f64) { + fn push_spacing(&mut self, mut spacing: Length) { spacing = spacing.min(self.usable().cross); self.run.size.cross += spacing; } @@ -169,7 +169,7 @@ impl LineLayouter { /// /// This specifies how much more would fit before a line break would be /// needed. - fn usable(&self) -> Gen2 { + fn usable(&self) -> Gen { // The base is the usable space of the stack layouter. let mut usable = self.stack.usable().switch(self.ctx.dirs); @@ -192,7 +192,7 @@ impl LineLayouter { /// Whether the currently set line is empty. fn line_is_empty(&self) -> bool { - self.run.size == Gen2::ZERO && self.run.layouts.is_empty() + self.run.size == Gen::ZERO && self.run.layouts.is_empty() } /// Finish everything up and return the final collection of boxes. @@ -224,7 +224,7 @@ impl LineLayouter { self.run.size.cross - offset - child.size.get(dirs.cross.axis()) }; - let pos = Gen2::new(0.0, cross).switch(dirs).to_point(); + let pos = Gen::new(Length::ZERO, cross).switch(dirs).to_point(); layout.push_layout(pos, child); } @@ -244,25 +244,25 @@ impl LineLayouter { /// multiple runs with different alignments. struct LineRun { /// The so-far accumulated items of the run. - layouts: Vec<(f64, BoxLayout)>, + layouts: Vec<(Length, BoxLayout)>, /// The summed width and maximal height of the run. - size: Gen2, + size: Gen, /// The alignment of all layouts in the line. /// /// When a new run is created the alignment is yet to be determined and /// `None` as such. Once a layout is added, its alignment decides the /// alignment for the whole run. - aligns: Option>, + aligns: Option>, /// The amount of cross-space left by another run on the same line or `None` /// if this is the only run so far. - usable: Option, + usable: Option, } impl LineRun { fn new() -> Self { Self { layouts: vec![], - size: Gen2::ZERO, + size: Gen::ZERO, aligns: None, usable: None, } @@ -283,7 +283,7 @@ pub(super) struct StackLayouter { #[derive(Debug, Clone)] pub(super) struct StackContext { /// The layouting directions. - pub dirs: Gen2, + pub dirs: Gen, /// The spaces to layout into. pub spaces: Vec, /// Whether to spill over into copies of the last space or finish layouting @@ -291,7 +291,7 @@ pub(super) struct StackContext { pub repeat: bool, /// Whether to expand the size of the resulting layout to the full size of /// this space or to shrink it to fit the content. - pub expand: Spec2, + pub expand: Spec, } impl StackLayouter { @@ -306,7 +306,7 @@ impl StackLayouter { } /// Add a layout to the stack. - pub fn push_box(&mut self, layout: BoxLayout, aligns: Gen2) { + pub fn push_box(&mut self, layout: BoxLayout, aligns: Gen) { // If the alignment cannot be fitted in this space, finish it. // // TODO: Issue warning for non-fitting alignment in non-repeating @@ -331,20 +331,20 @@ impl StackLayouter { } /// Add spacing to the stack. - pub fn push_spacing(&mut self, mut spacing: f64) { + pub fn push_spacing(&mut self, mut spacing: Length) { // Reduce the spacing such that it definitely fits. let axis = self.ctx.dirs.main.axis(); spacing = spacing.min(self.space.usable.get(axis)); - let size = Gen2::new(spacing, 0.0); + let size = Gen::new(spacing, Length::ZERO); self.update_metrics(size); self.space.layouts.push(( BoxLayout::new(size.switch(self.ctx.dirs).to_size()), - Gen2::default(), + Gen::default(), )); } - fn update_metrics(&mut self, added: Gen2) { + fn update_metrics(&mut self, added: Gen) { let mut used = self.space.used.switch(self.ctx.dirs); used.cross = used.cross.max(added.cross); used.main += added.main; @@ -398,11 +398,7 @@ impl StackLayouter { /// Finish active current space and start a new one. pub fn finish_space(&mut self, hard: bool) { let dirs = self.ctx.dirs; - - // ------------------------------------------------------------------ // - // Step 1: Determine the full size of the space. - // (Mostly done already while collecting the boxes, but here we - // expand if necessary.) + let main = dirs.main.axis(); let space = self.ctx.spaces[self.space.index]; let layout_size = { @@ -416,64 +412,44 @@ impl StackLayouter { used_size }; + let mut sum = Length::ZERO; + let mut sums = Vec::with_capacity(self.space.layouts.len() + 1); + + for (boxed, _) in &self.space.layouts { + sums.push(sum); + sum += boxed.size.get(main); + } + + sums.push(sum); + let mut layout = BoxLayout::new(layout_size); - - // ------------------------------------------------------------------ // - // Step 2: Forward pass. Create a bounding box for each layout in which - // it will be aligned. Then, go forwards through the boxes and remove - // what is taken by previous layouts from the following layouts. - - let mut bounds = vec![]; - let mut bound = Rect { - x0: 0.0, - y0: 0.0, - x1: layout_size.width, - y1: layout_size.height, - }; - - for (layout, _) in &self.space.layouts { - // First, store the bounds calculated so far (which were reduced - // by the predecessors of this layout) as the initial bounding box - // of this layout. - bounds.push(bound); - - // Then, reduce the bounding box for the following layouts. This - // layout uses up space from the origin to the end. Thus, it reduces - // the usable space for following layouts at its origin by its - // main-axis extent. - *bound.get_mut(dirs.main.start()) += - dirs.main.factor() * layout.size.get(dirs.main.axis()); - } - - // ------------------------------------------------------------------ // - // Step 3: Backward pass. Reduce the bounding boxes from the previous - // layouts by what is taken by the following ones. - - let mut main_extent = 0.0; - for (child, bound) in self.space.layouts.iter().zip(&mut bounds).rev() { - let (layout, _) = child; - - // Reduce the bounding box of this layout by the following one's - // main-axis extents. - *bound.get_mut(dirs.main.end()) -= dirs.main.factor() * main_extent; - - // And then, include this layout's main-axis extent. - main_extent += layout.size.get(dirs.main.axis()); - } - - // ------------------------------------------------------------------ // - // Step 4: Align each layout in its bounding box and collect everything - // into a single finished layout. + let used = layout_size.switch(dirs); let children = std::mem::take(&mut self.space.layouts); - for ((child, aligns), bound) in children.into_iter().zip(bounds) { - // Align the child in its own bounds. - let local = - bound.size().anchor(dirs, aligns) - child.size.anchor(dirs, aligns); + for (i, (boxed, aligns)) in children.into_iter().enumerate() { + let size = boxed.size.switch(dirs); - // Make the local position in the bounds global. - let pos = bound.origin() + local; - layout.push_layout(pos, child); + let before = sums[i]; + let after = sum - sums[i + 1]; + let main_len = used.main - size.main; + let main_range = if dirs.main.is_positive() { + before .. main_len - after + } else { + main_len - before .. after + }; + + let cross_len = used.cross - size.cross; + let cross_range = if dirs.cross.is_positive() { + Length::ZERO .. cross_len + } else { + cross_len .. Length::ZERO + }; + + let main = aligns.main.apply(main_range); + let cross = aligns.cross.apply(cross_range); + let pos = Gen::new(main, cross).switch(dirs).to_point(); + + layout.push_layout(pos, boxed); } self.layouts.push(layout); @@ -503,7 +479,7 @@ pub(super) struct Space { /// Whether to include a layout for this space even if it would be empty. hard: bool, /// The so-far accumulated layouts. - layouts: Vec<(BoxLayout, Gen2)>, + layouts: Vec<(BoxLayout, Gen)>, /// The full size of this space. size: Size, /// The used size of this space. @@ -511,7 +487,7 @@ pub(super) struct Space { /// The remaining space. usable: Size, /// Which alignments for new boxes are still allowed. - pub(super) allowed_align: GenAlign, + pub(super) allowed_align: Align, } impl Space { @@ -523,7 +499,7 @@ impl Space { size, used: Size::ZERO, usable: size, - allowed_align: GenAlign::Start, + allowed_align: Align::Start, } } } diff --git a/src/layout/primitive.rs b/src/layout/primitive.rs deleted file mode 100644 index 30bd9363d..000000000 --- a/src/layout/primitive.rs +++ /dev/null @@ -1,510 +0,0 @@ -//! Layouting primitives. - -use std::fmt::{self, Display, Formatter}; -use std::ops::Range; - -use crate::geom::{Insets, Linear, Point, Size, Vec2}; - -/// Generic access to a structure's components. -pub trait Get { - /// The structure's component type. - type Component; - - /// Return the component for the specified index. - fn get(self, index: Index) -> Self::Component; - - /// Borrow the component for the specified index mutably. - fn get_mut(&mut self, index: Index) -> &mut Self::Component; -} - -/// Switch between the specific and generic representations of a type. -/// -/// The generic representation deals with main and cross axes while the specific -/// representation deals with horizontal and vertical axes. -pub trait Switch { - /// The type of the other version. - type Other; - - /// The other version of this type based on the current directions. - fn switch(self, dirs: Gen2) -> Self::Other; -} - -/// The four directions into which content can be laid out. -#[derive(Debug, Copy, Clone, Eq, PartialEq)] -pub enum Dir { - /// Left to right. - LTR, - /// Right to left. - RTL, - /// Top to bottom. - TTB, - /// Bottom to top. - BTT, -} - -impl Dir { - /// The specific axis this direction belongs to. - pub fn axis(self) -> SpecAxis { - match self { - Self::LTR | Self::RTL => SpecAxis::Horizontal, - Self::TTB | Self::BTT => SpecAxis::Vertical, - } - } - - /// The side this direction starts at. - pub fn start(self) -> Side { - match self { - Self::LTR => Side::Left, - Self::RTL => Side::Right, - Self::TTB => Side::Top, - Self::BTT => Side::Bottom, - } - } - - /// The side this direction ends at. - pub fn end(self) -> Side { - match self { - Self::LTR => Side::Right, - Self::RTL => Side::Left, - Self::TTB => Side::Bottom, - Self::BTT => Side::Top, - } - } - - /// Whether this direction points into the positive coordinate direction. - /// - /// The positive directions are left-to-right and top-to-bottom. - pub fn is_positive(self) -> bool { - match self { - Self::LTR | Self::TTB => true, - Self::RTL | Self::BTT => false, - } - } - - /// The factor for this direction. - /// - /// - `1.0` if the direction is positive. - /// - `-1.0` if the direction is negative. - pub fn factor(self) -> f64 { - if self.is_positive() { 1.0 } else { -1.0 } - } - - /// The inverse direction. - pub fn inv(self) -> Self { - match self { - Self::LTR => Self::RTL, - Self::RTL => Self::LTR, - Self::TTB => Self::BTT, - Self::BTT => Self::TTB, - } - } -} - -impl Display for Dir { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - f.pad(match self { - Self::LTR => "ltr", - Self::RTL => "rtl", - Self::TTB => "ttb", - Self::BTT => "btt", - }) - } -} - -/// A generic container with two components for the two generic axes. -#[derive(Debug, Default, Copy, Clone, Eq, PartialEq)] -pub struct Gen2 { - /// The main component. - pub main: T, - /// The cross component. - pub cross: T, -} - -impl Gen2 { - /// Create a new instance from the two components. - pub fn new(main: T, cross: T) -> Self { - Self { main, cross } - } -} - -impl Gen2 { - /// The instance that has both components set to zero. - pub const ZERO: Self = Self { main: 0.0, cross: 0.0 }; -} - -impl Get for Gen2 { - type Component = T; - - fn get(self, axis: GenAxis) -> T { - match axis { - GenAxis::Main => self.main, - GenAxis::Cross => self.cross, - } - } - - fn get_mut(&mut self, axis: GenAxis) -> &mut T { - match axis { - GenAxis::Main => &mut self.main, - GenAxis::Cross => &mut self.cross, - } - } -} - -impl Switch for Gen2 { - type Other = Spec2; - - fn switch(self, dirs: Gen2) -> Self::Other { - match dirs.main.axis() { - SpecAxis::Horizontal => Spec2::new(self.main, self.cross), - SpecAxis::Vertical => Spec2::new(self.cross, self.main), - } - } -} - -/// A generic container with two components for the two specific axes. -#[derive(Debug, Default, Copy, Clone, Eq, PartialEq)] -pub struct Spec2 { - /// The horizontal component. - pub horizontal: T, - /// The vertical component. - pub vertical: T, -} - -impl Spec2 { - /// Create a new instance from the two components. - pub fn new(horizontal: T, vertical: T) -> Self { - Self { horizontal, vertical } - } -} - -impl Spec2 { - /// The instance that has both components set to zero. - pub const ZERO: Self = Self { horizontal: 0.0, vertical: 0.0 }; - - /// Convert to a 2D vector. - pub fn to_vec2(self) -> Vec2 { - Vec2::new(self.horizontal, self.vertical) - } - - /// Convert to a point. - pub fn to_point(self) -> Point { - Point::new(self.horizontal, self.vertical) - } - - /// Convert to a size. - pub fn to_size(self) -> Size { - Size::new(self.horizontal, self.vertical) - } -} - -impl Get for Spec2 { - type Component = T; - - fn get(self, axis: SpecAxis) -> T { - match axis { - SpecAxis::Horizontal => self.horizontal, - SpecAxis::Vertical => self.vertical, - } - } - - fn get_mut(&mut self, axis: SpecAxis) -> &mut T { - match axis { - SpecAxis::Horizontal => &mut self.horizontal, - SpecAxis::Vertical => &mut self.vertical, - } - } -} - -impl Switch for Spec2 { - type Other = Gen2; - - fn switch(self, dirs: Gen2) -> Self::Other { - match dirs.main.axis() { - SpecAxis::Horizontal => Gen2::new(self.horizontal, self.vertical), - SpecAxis::Vertical => Gen2::new(self.vertical, self.horizontal), - } - } -} - -/// The two generic layouting axes. -#[derive(Debug, Copy, Clone, Eq, PartialEq)] -pub enum GenAxis { - /// The axis pages and paragraphs are set along. - Main, - /// The axis words and lines are set along. - Cross, -} - -impl GenAxis { - /// The other axis. - pub fn other(self) -> Self { - match self { - Self::Main => Self::Cross, - Self::Cross => Self::Main, - } - } -} - -impl Switch for GenAxis { - type Other = SpecAxis; - - fn switch(self, dirs: Gen2) -> Self::Other { - match self { - Self::Main => dirs.main.axis(), - Self::Cross => dirs.cross.axis(), - } - } -} - -impl Display for GenAxis { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - f.pad(match self { - Self::Main => "main", - Self::Cross => "cross", - }) - } -} - -/// The two specific layouting axes. -#[derive(Debug, Copy, Clone, Eq, PartialEq)] -pub enum SpecAxis { - /// The vertical layouting axis. - Vertical, - /// The horizontal layouting axis. - Horizontal, -} - -impl SpecAxis { - /// The other axis. - pub fn other(self) -> Self { - match self { - Self::Horizontal => Self::Vertical, - Self::Vertical => Self::Horizontal, - } - } -} - -impl Switch for SpecAxis { - type Other = GenAxis; - - fn switch(self, dirs: Gen2) -> Self::Other { - if self == dirs.main.axis() { - GenAxis::Main - } else { - debug_assert_eq!(self, dirs.cross.axis()); - GenAxis::Cross - } - } -} - -impl Display for SpecAxis { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - f.pad(match self { - Self::Vertical => "vertical", - Self::Horizontal => "horizontal", - }) - } -} - -/// Where to align content along an axis in a generic context. -#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)] -pub enum GenAlign { - Start, - Center, - End, -} - -impl GenAlign { - /// Returns the position of this alignment in the given length. - pub fn apply(self, range: Range) -> f64 { - match self { - Self::Start => range.start, - Self::Center => (range.start + range.end) / 2.0, - Self::End => range.end, - } - } - - /// The inverse alignment. - pub fn inv(self) -> Self { - match self { - Self::Start => Self::End, - Self::Center => Self::Center, - Self::End => Self::Start, - } - } -} - -impl Default for GenAlign { - fn default() -> Self { - Self::Start - } -} - -impl Display for GenAlign { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - f.pad(match self { - Self::Start => "start", - Self::Center => "center", - Self::End => "end", - }) - } -} - -/// Where to align content along an axis in a specific context. -#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)] -pub enum SpecAlign { - Left, - Right, - Top, - Bottom, - Center, -} - -impl SpecAlign { - /// The specific axis this alignment refers to. - /// - /// Returns `None` if this is `Center` since the axis is unknown. - pub fn axis(self) -> Option { - match self { - Self::Left => Some(SpecAxis::Horizontal), - Self::Right => Some(SpecAxis::Horizontal), - Self::Top => Some(SpecAxis::Vertical), - Self::Bottom => Some(SpecAxis::Vertical), - Self::Center => None, - } - } - - /// The inverse alignment. - pub fn inv(self) -> Self { - match self { - Self::Left => Self::Right, - Self::Right => Self::Left, - Self::Top => Self::Bottom, - Self::Bottom => Self::Top, - Self::Center => Self::Center, - } - } -} - -impl Switch for SpecAlign { - type Other = GenAlign; - - fn switch(self, dirs: Gen2) -> Self::Other { - let get = |dir: Dir, at_positive_start| { - if dir.is_positive() == at_positive_start { - GenAlign::Start - } else { - GenAlign::End - } - }; - - let dirs = dirs.switch(dirs); - match self { - Self::Left => get(dirs.horizontal, true), - Self::Right => get(dirs.horizontal, false), - Self::Top => get(dirs.vertical, true), - Self::Bottom => get(dirs.vertical, false), - Self::Center => GenAlign::Center, - } - } -} - -impl Display for SpecAlign { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - f.pad(match self { - Self::Left => "left", - Self::Right => "right", - Self::Top => "top", - Self::Bottom => "bottom", - Self::Center => "center", - }) - } -} - -/// A generic container with left, top, right and bottom components. -#[derive(Debug, Default, Copy, Clone, Eq, PartialEq)] -pub struct Sides { - /// The value for the left side. - pub left: T, - /// The value for the top side. - pub top: T, - /// The value for the right side. - pub right: T, - /// The value for the bottom side. - pub bottom: T, -} - -impl Sides { - /// Create a new box from four sizes. - pub fn new(left: T, top: T, right: T, bottom: T) -> Self { - Self { left, top, right, bottom } - } - - /// Create an instance with all four components set to the same `value`. - pub fn uniform(value: T) -> Self - where - T: Clone, - { - Self { - left: value.clone(), - top: value.clone(), - right: value.clone(), - bottom: value, - } - } -} - -impl Sides { - /// The absolute insets. - pub fn insets(self, Size { width, height }: Size) -> Insets { - Insets { - x0: -self.left.eval(width), - y0: -self.top.eval(height), - x1: -self.right.eval(width), - y1: -self.bottom.eval(height), - } - } -} - -impl Get for Sides { - type Component = T; - - fn get(self, side: Side) -> T { - match side { - Side::Left => self.left, - Side::Top => self.top, - Side::Right => self.right, - Side::Bottom => self.bottom, - } - } - - fn get_mut(&mut self, side: Side) -> &mut T { - match side { - Side::Left => &mut self.left, - Side::Top => &mut self.top, - Side::Right => &mut self.right, - Side::Bottom => &mut self.bottom, - } - } -} - -/// A side of a container. -#[derive(Debug, Copy, Clone, Eq, PartialEq)] -pub enum Side { - Left, - Top, - Right, - Bottom, -} - -impl Side { - /// The opposite side. - pub fn inv(self) -> Self { - match self { - Self::Left => Self::Right, - Self::Top => Self::Bottom, - Self::Right => Self::Left, - Self::Bottom => Self::Top, - } - } -} diff --git a/src/layout/nodes/spacing.rs b/src/layout/spacing.rs similarity index 98% rename from src/layout/nodes/spacing.rs rename to src/layout/spacing.rs index 9d72f7ca2..9eac1ad55 100644 --- a/src/layout/nodes/spacing.rs +++ b/src/layout/spacing.rs @@ -5,7 +5,7 @@ use super::*; /// A node that inserts spacing. #[derive(Copy, Clone, PartialEq)] pub struct Spacing { - pub amount: f64, + pub amount: Length, pub softness: Softness, } diff --git a/src/layout/nodes/stack.rs b/src/layout/stack.rs similarity index 89% rename from src/layout/nodes/stack.rs rename to src/layout/stack.rs index cca64e621..6cbe03e3a 100644 --- a/src/layout/nodes/stack.rs +++ b/src/layout/stack.rs @@ -24,10 +24,10 @@ use super::*; /// sentence in the second box. #[derive(Debug, Clone, PartialEq)] pub struct Stack { - pub dirs: Gen2, + pub dirs: Gen, pub children: Vec, - pub aligns: Gen2, - pub expand: Spec2, + pub aligns: Gen, + pub expand: Spec, } #[async_trait(?Send)] @@ -90,17 +90,17 @@ impl Layout for Stack { } struct StackSpace { - dirs: Gen2, - expand: Spec2, - boxes: Vec<(BoxLayout, Gen2)>, + dirs: Gen, + expand: Spec, + boxes: Vec<(BoxLayout, Gen)>, full_size: Size, usable: Size, used: Size, - ruler: GenAlign, + ruler: Align, } impl StackSpace { - fn new(dirs: Gen2, expand: Spec2, size: Size) -> Self { + fn new(dirs: Gen, expand: Spec, size: Size) -> Self { Self { dirs, expand, @@ -108,14 +108,14 @@ impl StackSpace { full_size: size, usable: size, used: Size::ZERO, - ruler: GenAlign::Start, + ruler: Align::Start, } } fn push_box( &mut self, boxed: BoxLayout, - aligns: Gen2, + aligns: Gen, ) -> Result<(), BoxLayout> { let main = self.dirs.main.axis(); let cross = self.dirs.cross.axis(); @@ -133,15 +133,15 @@ impl StackSpace { Ok(()) } - fn push_spacing(&mut self, spacing: f64) { + fn push_spacing(&mut self, spacing: Length) { let main = self.dirs.main.axis(); let max = self.usable.get(main); let trimmed = spacing.min(max); *self.used.get_mut(main) += trimmed; *self.usable.get_mut(main) -= trimmed; - let size = Gen2::new(trimmed, 0.0).switch(self.dirs); - self.boxes.push((BoxLayout::new(size.to_size()), Gen2::default())); + let size = Gen::new(trimmed, Length::ZERO).switch(self.dirs); + self.boxes.push((BoxLayout::new(size.to_size()), Gen::default())); } fn finish(mut self) -> BoxLayout { @@ -156,7 +156,7 @@ impl StackSpace { self.used.height = self.full_size.height; } - let mut sum = 0.0; + let mut sum = Length::ZERO; let mut sums = Vec::with_capacity(self.boxes.len() + 1); for (boxed, _) in &self.boxes { @@ -183,14 +183,14 @@ impl StackSpace { let cross_len = used.cross - size.cross; let cross_range = if dirs.cross.is_positive() { - 0.0 .. cross_len + Length::ZERO .. cross_len } else { - cross_len .. 0.0 + cross_len .. Length::ZERO }; let main = aligns.main.apply(main_range); let cross = aligns.cross.apply(cross_range); - let pos = Gen2::new(main, cross).switch(dirs).to_point(); + let pos = Gen::new(main, cross).switch(dirs).to_point(); layout.push_layout(pos, boxed); } diff --git a/src/layout/nodes/text.rs b/src/layout/text.rs similarity index 95% rename from src/layout/nodes/text.rs rename to src/layout/text.rs index b0c4a458c..fafc1b14e 100644 --- a/src/layout/nodes/text.rs +++ b/src/layout/text.rs @@ -10,11 +10,11 @@ use crate::shaping; #[derive(Clone, PartialEq)] pub struct Text { pub text: String, - pub size: f64, + pub size: Length, pub dir: Dir, pub fallback: Rc, pub variant: FontVariant, - pub aligns: Gen2, + pub aligns: Gen, } #[async_trait(?Send)] diff --git a/src/length.rs b/src/length.rs deleted file mode 100644 index 437c741df..000000000 --- a/src/length.rs +++ /dev/null @@ -1,203 +0,0 @@ -//! A length type with a unit. - -use std::fmt::{self, Debug, Display, Formatter}; -use std::str::FromStr; - -/// A length with a unit. -#[derive(Copy, Clone, PartialEq)] -pub struct Length { - /// The length in the given unit. - pub val: f64, - /// The unit of measurement. - pub unit: Unit, -} - -/// Different units of measurement. -#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd)] -pub enum Unit { - /// Points. - Pt, - /// Millimeters. - Mm, - /// Centimeters. - Cm, - /// Inches. - In, - /// Raw units (the implicit unit of all bare `f64` lengths). - Raw, -} - -impl Length { - /// Create a length from a value with a unit. - pub const fn new(val: f64, unit: Unit) -> Self { - Self { val, unit } - } - - /// Create a length from a number of points. - pub const fn pt(pt: f64) -> Self { - Self::new(pt, Unit::Pt) - } - - /// Create a length from a number of millimeters. - pub const fn mm(mm: f64) -> Self { - Self::new(mm, Unit::Mm) - } - - /// Create a length from a number of centimeters. - pub const fn cm(cm: f64) -> Self { - Self::new(cm, Unit::Cm) - } - - /// Create a length from a number of inches. - pub const fn inches(inches: f64) -> Self { - Self::new(inches, Unit::In) - } - - /// Create a length from a number of raw units. - pub const fn raw(raw: f64) -> Self { - Self::new(raw, Unit::Raw) - } - - /// Convert this to a number of points. - pub fn as_pt(self) -> f64 { - self.with_unit(Unit::Pt).val - } - - /// Convert this to a number of millimeters. - pub fn as_mm(self) -> f64 { - self.with_unit(Unit::Mm).val - } - - /// Convert this to a number of centimeters. - pub fn as_cm(self) -> f64 { - self.with_unit(Unit::Cm).val - } - - /// Convert this to a number of inches. - pub fn as_inches(self) -> f64 { - self.with_unit(Unit::In).val - } - - /// Get the value of this length in raw units. - pub fn as_raw(self) -> f64 { - self.with_unit(Unit::Raw).val - } - - /// Convert this to a length with a different unit. - pub fn with_unit(self, unit: Unit) -> Self { - Self { - val: self.val * self.unit.raw_scale() / unit.raw_scale(), - unit, - } - } -} - -impl Unit { - /// How many raw units correspond to a value of `1.0` in this unit. - fn raw_scale(self) -> f64 { - match self { - Unit::Pt => 1.0, - Unit::Mm => 2.83465, - Unit::Cm => 28.3465, - Unit::In => 72.0, - Unit::Raw => 1.0, - } - } -} - -impl Display for Length { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - write!(f, "{:.2}{}", self.val, self.unit) - } -} - -impl Debug for Length { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - Display::fmt(self, f) - } -} - -impl Display for Unit { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - f.pad(match self { - Unit::Mm => "mm", - Unit::Pt => "pt", - Unit::Cm => "cm", - Unit::In => "in", - Unit::Raw => "rw", - }) - } -} - -impl Debug for Unit { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - Display::fmt(self, f) - } -} - -impl FromStr for Length { - type Err = ParseLengthError; - - fn from_str(src: &str) -> Result { - let len = src.len(); - - // We need at least some number and the unit. - if len <= 2 { - return Err(ParseLengthError); - } - - // We can view the string as bytes since a multibyte UTF-8 char cannot - // have valid ASCII chars as subbytes. - let split = len - 2; - let bytes = src.as_bytes(); - let unit = match &bytes[split ..] { - b"pt" => Unit::Pt, - b"mm" => Unit::Mm, - b"cm" => Unit::Cm, - b"in" => Unit::In, - _ => return Err(ParseLengthError), - }; - - src[.. split] - .parse::() - .map(|val| Self::new(val, unit)) - .map_err(|_| ParseLengthError) - } -} - -/// The error when parsing a length fails. -#[derive(Debug, Copy, Clone, Eq, PartialEq)] -pub struct ParseLengthError; - -impl std::error::Error for ParseLengthError {} - -impl Display for ParseLengthError { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - f.pad("invalid string for length") - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_length_from_str_parses_correct_value_and_unit() { - assert_eq!(Length::from_str("2.5cm"), Ok(Length::cm(2.5))); - } - - #[test] - fn test_length_from_str_works_with_non_ascii_chars() { - assert_eq!(Length::from_str("123🚚"), Err(ParseLengthError)); - } - - #[test] - fn test_length_formats_correctly() { - assert_eq!(Length::cm(12.728).to_string(), "12.73cm".to_string()); - } - - #[test] - fn test_length_unit_conversion() { - assert!((Length::mm(150.0).as_cm() - 15.0) < 1e-4); - } -} diff --git a/src/lib.rs b/src/lib.rs index 22e3b988e..381270c99 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -30,13 +30,13 @@ #[macro_use] pub mod diag; -pub mod color; +#[macro_use] pub mod eval; +pub mod color; pub mod export; pub mod font; pub mod geom; pub mod layout; -pub mod length; pub mod library; pub mod paper; pub mod parse; diff --git a/src/library/align.rs b/src/library/align.rs index acd3a85c0..d6b14692a 100644 --- a/src/library/align.rs +++ b/src/library/align.rs @@ -1,4 +1,5 @@ use crate::prelude::*; +use std::fmt::{self, Display, Formatter}; /// `align`: Align content along the layouting axes. /// @@ -18,10 +19,10 @@ pub fn align(mut args: Args, ctx: &mut EvalContext) -> Value { let snapshot = ctx.state.clone(); let body = args.find::(); - let first = args.get::<_, Spanned>(ctx, 0); - let second = args.get::<_, Spanned>(ctx, 1); - let hor = args.get::<_, Spanned>(ctx, "horizontal"); - let ver = args.get::<_, Spanned>(ctx, "vertical"); + let first = args.get::<_, Spanned>(ctx, 0); + let second = args.get::<_, Spanned>(ctx, 1); + let hor = args.get::<_, Spanned>(ctx, "horizontal"); + let ver = args.get::<_, Spanned>(ctx, "vertical"); args.done(ctx); let iter = first @@ -50,10 +51,10 @@ pub fn align(mut args: Args, ctx: &mut EvalContext) -> Value { /// Deduplicate alignments and deduce to which axes they apply. fn dedup_aligns( ctx: &mut EvalContext, - iter: impl Iterator, Spanned)>, -) -> Gen2 { + iter: impl Iterator, Spanned)>, +) -> Gen { let mut aligns = ctx.state.aligns; - let mut had = Gen2::new(false, false); + let mut had = Gen::new(false, false); let mut had_center = false; for (axis, Spanned { v: align, span }) in iter { @@ -77,15 +78,15 @@ fn dedup_aligns( } else { // We don't know the axis: This has to be a `center` alignment for a // positional argument. - debug_assert_eq!(align, SpecAlign::Center); + debug_assert_eq!(align, AlignArg::Center); if had.main && had.cross { ctx.diag(error!(span, "duplicate alignment")); } else if had_center { // Both this and the previous one are unspecified `center` // alignments. Both axes should be centered. - aligns = Gen2::new(GenAlign::Center, GenAlign::Center); - had = Gen2::new(true, true); + aligns = Gen::new(Align::Center, Align::Center); + had = Gen::new(true, true); } else { had_center = true; } @@ -95,10 +96,10 @@ fn dedup_aligns( // alignment. if had_center && (had.main || had.cross) { if had.main { - aligns.cross = GenAlign::Center; + aligns.cross = Align::Center; had.cross = true; } else { - aligns.main = GenAlign::Center; + aligns.main = Align::Center; had.main = true; } had_center = false; @@ -108,8 +109,77 @@ fn dedup_aligns( // If center has not been flushed by now, it is the only argument and then // we default to applying it to the cross axis. if had_center { - aligns.cross = GenAlign::Center; + aligns.cross = Align::Center; } aligns } + +/// An alignment argument. +#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)] +enum AlignArg { + Left, + Right, + Top, + Bottom, + Center, +} + +impl AlignArg { + /// The specific axis this alignment refers to. + /// + /// Returns `None` if this is `Center` since the axis is unknown. + pub fn axis(self) -> Option { + match self { + Self::Left => Some(SpecAxis::Horizontal), + Self::Right => Some(SpecAxis::Horizontal), + Self::Top => Some(SpecAxis::Vertical), + Self::Bottom => Some(SpecAxis::Vertical), + Self::Center => None, + } + } +} + +impl Switch for AlignArg { + type Other = Align; + + fn switch(self, dirs: Gen) -> Self::Other { + let get = |dir: Dir, at_positive_start| { + if dir.is_positive() == at_positive_start { + Align::Start + } else { + Align::End + } + }; + + let dirs = dirs.switch(dirs); + match self { + Self::Left => get(dirs.horizontal, true), + Self::Right => get(dirs.horizontal, false), + Self::Top => get(dirs.vertical, true), + Self::Bottom => get(dirs.vertical, false), + Self::Center => Align::Center, + } + } +} + +convert_ident!(AlignArg, "alignment", |v| match v { + "left" => Some(Self::Left), + "right" => Some(Self::Right), + "top" => Some(Self::Top), + "bottom" => Some(Self::Bottom), + "center" => Some(Self::Center), + _ => None, +}); + +impl Display for AlignArg { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + f.pad(match self { + Self::Left => "left", + Self::Right => "right", + Self::Top => "top", + Self::Bottom => "bottom", + Self::Center => "center", + }) + } +} diff --git a/src/library/boxed.rs b/src/library/boxed.rs index 6edb3b174..0c1ed30a7 100644 --- a/src/library/boxed.rs +++ b/src/library/boxed.rs @@ -1,5 +1,5 @@ use crate::geom::Linear; -use crate::layout::nodes::{Fixed, Stack}; +use crate::layout::{Fixed, Stack}; use crate::prelude::*; /// `box`: Layouts its contents into a box. @@ -33,7 +33,7 @@ pub fn boxed(mut args: Args, ctx: &mut EvalContext) -> Value { dirs, children, aligns, - expand: Spec2::new(width.is_some(), height.is_some()), + expand: Spec::new(width.is_some(), height.is_some()), }), }); diff --git a/src/library/font.rs b/src/library/font.rs index be6823c30..e6d9cd127 100644 --- a/src/library/font.rs +++ b/src/library/font.rs @@ -57,9 +57,9 @@ pub fn font(mut args: Args, ctx: &mut EvalContext) -> Value { let body = args.find::(); if let Some(linear) = args.find::() { - if linear.rel == 0.0 { + if linear.is_absolute() { ctx.state.text.font_size.base = linear.abs; - ctx.state.text.font_size.scale = Linear::rel(1.0); + ctx.state.text.font_size.scale = Relative::ONE.into(); } else { ctx.state.text.font_size.scale = linear; } diff --git a/src/library/page.rs b/src/library/page.rs index 3c6703bc9..570dbb106 100644 --- a/src/library/page.rs +++ b/src/library/page.rs @@ -1,7 +1,6 @@ use std::mem; -use crate::eval::Absolute; -use crate::geom::Linear; +use crate::geom::{Length, Linear}; use crate::paper::{Paper, PaperClass}; use crate::prelude::*; @@ -25,12 +24,12 @@ pub fn page(mut args: Args, ctx: &mut EvalContext) -> Value { ctx.state.page.size = paper.size(); } - if let Some(Absolute(width)) = args.get::<_, Absolute>(ctx, "width") { + if let Some(width) = args.get::<_, Length>(ctx, "width") { ctx.state.page.class = PaperClass::Custom; ctx.state.page.size.width = width; } - if let Some(Absolute(height)) = args.get::<_, Absolute>(ctx, "height") { + if let Some(height) = args.get::<_, Length>(ctx, "height") { ctx.state.page.class = PaperClass::Custom; ctx.state.page.size.height = height; } diff --git a/src/library/spacing.rs b/src/library/spacing.rs index 6d00bd1c4..a2b21e93c 100644 --- a/src/library/spacing.rs +++ b/src/library/spacing.rs @@ -1,5 +1,5 @@ use crate::geom::Linear; -use crate::layout::nodes::{Softness, Spacing}; +use crate::layout::{Softness, Spacing}; use crate::prelude::*; /// `h`: Add horizontal spacing. diff --git a/src/paper.rs b/src/paper.rs index 21762b17f..21f547568 100644 --- a/src/paper.rs +++ b/src/paper.rs @@ -1,18 +1,16 @@ //! Predefined papers. -use crate::geom::{Linear, Size}; -use crate::layout::Sides; -use crate::length::Length; +use crate::geom::{Length, Linear, Relative, Sides, Size}; /// Specification of a paper. #[derive(Debug, Copy, Clone, PartialEq)] pub struct Paper { /// The kind of paper, which defines the default margins. pub class: PaperClass, - /// The width of the paper. - pub width: Length, - /// The height of the paper. - pub height: Length, + /// The width of the paper in millimeters. + pub width: f64, + /// The height of the paper in millimeters. + pub height: f64, } impl Paper { @@ -23,7 +21,7 @@ impl Paper { /// The size of the paper. pub fn size(self) -> Size { - Size::new(self.width.as_raw(), self.height.as_raw()) + Size::new(Length::mm(self.width), Length::mm(self.height)) } } @@ -40,7 +38,7 @@ pub enum PaperClass { impl PaperClass { /// The default margins for this page class. pub fn default_margins(self) -> Sides { - let f = Linear::rel; + let f = |r| Relative::new(r).into(); let s = |l, r, t, b| Sides::new(f(l), f(r), f(t), f(b)); match self { Self::Custom => s(0.1190, 0.0842, 0.1190, 0.0842), @@ -69,9 +67,9 @@ macro_rules! papers { #[doc = $names] #[doc = "`."] pub const $var: Paper = Paper { - width: Length::mm($width), - height: Length::mm($height), class: PaperClass::$class, + width: $width, + height: $height, }; }; } diff --git a/src/parse/mod.rs b/src/parse/mod.rs index 3d980d7cd..90fdbf5dd 100644 --- a/src/parse/mod.rs +++ b/src/parse/mod.rs @@ -432,7 +432,7 @@ fn value(p: &mut Parser) -> Option { Token::Bool(b) => Expr::Lit(Lit::Bool(b)), Token::Int(i) => Expr::Lit(Lit::Int(i)), Token::Float(f) => Expr::Lit(Lit::Float(f)), - Token::Length(l) => Expr::Lit(Lit::Length(l)), + Token::Length(val, unit) => Expr::Lit(Lit::Length(val, unit)), Token::Percent(p) => Expr::Lit(Lit::Percent(p)), Token::Hex(hex) => Expr::Lit(Lit::Color(color(p, hex, start))), Token::Str(token) => Expr::Lit(Lit::Str(string(p, token))), diff --git a/src/parse/tests.rs b/src/parse/tests.rs index d76c6dcac..e0712b367 100644 --- a/src/parse/tests.rs +++ b/src/parse/tests.rs @@ -8,7 +8,7 @@ use super::parse; use crate::color::RgbaColor; use crate::diag::Deco; use crate::eval::DictKey; -use crate::length::Length; +use crate::geom::Unit; use crate::syntax::*; // ------------------------------ Construct Syntax Nodes ------------------------------ // @@ -51,6 +51,7 @@ macro_rules! F { use BinOp::*; use UnOp::*; +use Unit::*; fn Id(ident: &str) -> Expr { Expr::Lit(Lit::Ident(Ident(ident.to_string()))) @@ -67,8 +68,8 @@ fn Float(float: f64) -> Expr { fn Percent(percent: f64) -> Expr { Expr::Lit(Lit::Percent(percent)) } -fn Len(length: Length) -> Expr { - Expr::Lit(Lit::Length(length)) +fn Length(val: f64, unit: Unit) -> Expr { + Expr::Lit(Lit::Length(val, unit)) } fn Color(color: RgbaColor) -> Expr { Expr::Lit(Lit::Color(color)) @@ -347,10 +348,10 @@ fn test_parse_chaining() { // Things the parser has to make sense of t!("[hi: (5.0, 2.1 >> you]" => F!("hi"; Dict![Float(5.0), Float(2.1)], Tree![F!("you")])); t!("[box >> pad: 1pt][Hi]" => F!("box"; Tree![ - F!("pad"; Len(Length::pt(1.0)), Tree!(T("Hi"))) + F!("pad"; Length(1.0, Pt), Tree!(T("Hi"))) ])); t!("[bold: 400, >> emph >> sub: 1cm]" => F!("bold"; Int(400), Tree![ - F!("emph"; Tree!(F!("sub"; Len(Length::cm(1.0))))) + F!("emph"; Tree!(F!("sub"; Length(1.0, Cm)))) ])); // Errors for unclosed / empty predecessor groups @@ -411,8 +412,8 @@ fn test_parse_values() { v!("1.0e-4" => Float(1e-4)); v!("3.15" => Float(3.15)); v!("50%" => Percent(50.0)); - v!("4.5cm" => Len(Length::cm(4.5))); - v!("12e1pt" => Len(Length::pt(12e1))); + v!("4.5cm" => Length(4.5, Cm)); + v!("12e1pt" => Length(12e1, Pt)); v!("#f7a20500" => Color(RgbaColor::new(0xf7, 0xa2, 0x05, 0x00))); v!("\"a\n[]\\\"string\"" => Str("a\n[]\"string")); @@ -446,15 +447,15 @@ fn test_parse_expressions() { // Operations. v!("-1" => Unary(Neg, Int(1))); v!("-- 1" => Unary(Neg, Unary(Neg, Int(1)))); - v!("3.2in + 6pt" => Binary(Add, Len(Length::inches(3.2)), Len(Length::pt(6.0)))); + v!("3.2in + 6pt" => Binary(Add, Length(3.2, In), Length(6.0, Pt))); v!("5 - 0.01" => Binary(Sub, Int(5), Float(0.01))); - v!("(3mm * 2)" => Binary(Mul, Len(Length::mm(3.0)), Int(2))); - v!("12e-3cm/1pt" => Binary(Div, Len(Length::cm(12e-3)), Len(Length::pt(1.0)))); + v!("(3mm * 2)" => Binary(Mul, Length(3.0, Mm), Int(2))); + v!("12e-3cm/1pt" => Binary(Div, Length(12e-3, Cm), Length(1.0, Pt))); // More complex. v!("(3.2in + 6pt)*(5/2-1)" => Binary( Mul, - Binary(Add, Len(Length::inches(3.2)), Len(Length::pt(6.0))), + Binary(Add, Length(3.2, In), Length(6.0, Pt)), Binary(Sub, Binary(Div, Int(5), Int(2)), Int(1)) )); v!("(6.3E+2+4* - 3.2pt)/2" => Binary( @@ -462,7 +463,7 @@ fn test_parse_expressions() { Binary(Add, Float(6.3e2), Binary( Mul, Int(4), - Unary(Neg, Len(Length::pt(3.2))) + Unary(Neg, Length(3.2, Pt)) )), Int(2) )); @@ -483,11 +484,11 @@ fn test_parse_expressions() { ts!("[val: (1)]" => s(0, 10, F!(s(1, 4, "val"), 5 .. 9; s(6, 9, Int(1))))); // Invalid expressions. - v!("4pt--" => Len(Length::pt(4.0))); + v!("4pt--" => Length(4.0, Pt)); e!("[val: 4pt--]" => s(10, 11, "missing factor"), s(6, 10, "missing right summand")); - v!("3mm+4pt*" => Binary(Add, Len(Length::mm(3.0)), Len(Length::pt(4.0)))); + v!("3mm+4pt*" => Binary(Add, Length(3.0, Mm), Length(4.0, Pt))); e!("[val: 3mm+4pt*]" => s(10, 14, "missing right factor")); } @@ -525,7 +526,7 @@ fn test_parse_dicts_compute_func_calls() { // More complex. v!("css(1pt, rgb(90, 102, 254), \"solid\")" => Call!( "css"; - Len(Length::pt(1.0)), + Length(1.0, Pt), Call!("rgb"; Int(90), Int(102), Int(254)), Str("solid"), )); @@ -546,7 +547,7 @@ fn test_parse_dicts_nested() { Int(1), Dict!( "ab" => Dict![], - "d" => Dict!(Int(3), Len(Length::pt(14.0))), + "d" => Dict!(Int(3), Length(14.0, Pt)), ), ], Bool(false), diff --git a/src/parse/tokens.rs b/src/parse/tokens.rs index 2ff580b6c..f53ab586f 100644 --- a/src/parse/tokens.rs +++ b/src/parse/tokens.rs @@ -3,7 +3,7 @@ use std::fmt::{self, Debug, Formatter}; use super::{is_newline, Scanner}; -use crate::length::Length; +use crate::geom::Unit; use crate::syntax::token::*; use crate::syntax::{is_ident, Pos}; @@ -279,8 +279,8 @@ fn parse_expr(text: &str) -> Token<'_> { Token::Float(num) } else if let Some(percent) = parse_percent(text) { Token::Percent(percent) - } else if let Ok(length) = text.parse::() { - Token::Length(length) + } else if let Some((val, unit)) = parse_length(text) { + Token::Length(val, unit) } else if is_ident(text) { Token::Ident(text) } else { @@ -292,19 +292,41 @@ fn parse_percent(text: &str) -> Option { text.strip_suffix('%').and_then(|num| num.parse::().ok()) } +fn parse_length(text: &str) -> Option<(f64, Unit)> { + let len = text.len(); + + // We need at least some number and the unit. + if len <= 2 { + return None; + } + + // We can view the string as bytes since a multibyte UTF-8 char cannot + // have valid ASCII chars as subbytes. + let split = len - 2; + let bytes = text.as_bytes(); + let unit = match &bytes[split ..] { + b"pt" => Unit::Pt, + b"mm" => Unit::Mm, + b"cm" => Unit::Cm, + b"in" => Unit::In, + _ => return None, + }; + + text[.. split].parse::().ok().map(|val| (val, unit)) +} + #[cfg(test)] #[allow(non_snake_case)] mod tests { use super::*; - use crate::length::Length; use crate::parse::tests::check; use Token::{ - BlockComment as BC, Bool, Chain, Float, Hex, Hyphen as Min, Ident as Id, Int, - LeftBrace as LB, LeftBracket as L, LeftParen as LP, Length as Len, - LineComment as LC, NonBreakingSpace as Nbsp, Percent, Plus, RightBrace as RB, - RightBracket as R, RightParen as RP, Slash, Space as S, Star, Text as T, *, + BlockComment as BC, Hyphen as Min, Ident as Id, LeftBrace as LB, + LeftBracket as L, LeftParen as LP, LineComment as LC, NonBreakingSpace as Nbsp, + RightBrace as RB, RightBracket as R, RightParen as RP, Space as S, Text as T, *, }; + use Unit::*; fn Str(string: &str, terminated: bool) -> Token { Token::Str(TokenStr { string, terminated }) @@ -324,6 +346,16 @@ mod tests { } } + #[test] + fn test_length_from_str_parses_correct_value_and_unit() { + assert_eq!(parse_length("2.5cm"), Some((2.5, Cm))); + } + + #[test] + fn test_length_from_str_works_with_non_ascii_chars() { + assert_eq!(parse_length("123🚚"), None); + } + #[test] fn tokenize_whitespace() { t!(Body, "" => ); @@ -429,7 +461,7 @@ mod tests { t!(Header, "🌓, 🌍," => Invalid("🌓"), Comma, S(0), Invalid("🌍"), Comma); t!(Header, "{abc}" => LB, Id("abc"), RB); t!(Header, "(1,2)" => LP, Int(1), Comma, Int(2), RP); - t!(Header, "12_pt, 12pt" => Invalid("12_pt"), Comma, S(0), Len(Length::pt(12.0))); + t!(Header, "12_pt, 12pt" => Invalid("12_pt"), Comma, S(0), Length(12.0, Pt)); t!(Header, "f: arg >> g" => Id("f"), Colon, S(0), Id("arg"), S(0), Chain, S(0), Id("g")); t!(Header, "=3.15" => Equals, Float(3.15)); t!(Header, "arg, _b, _1" => Id("arg"), Comma, S(0), Id("_b"), Comma, S(0), Id("_1")); @@ -446,9 +478,9 @@ mod tests { t!(Header, "12.3e5" => Float(12.3e5)); t!(Header, "120%" => Percent(120.0)); t!(Header, "12e4%" => Percent(120000.0)); - t!(Header, "1e5in" => Len(Length::inches(100000.0))); - t!(Header, "2.3cm" => Len(Length::cm(2.3))); - t!(Header, "02.4mm" => Len(Length::mm(2.4))); + t!(Header, "1e5in" => Length(100000.0, In)); + t!(Header, "2.3cm" => Length(2.3, Cm)); + t!(Header, "02.4mm" => Length(2.4, Mm)); t!(Header, "2.4.cm" => Invalid("2.4.cm")); t!(Header, "#6ae6dd" => Hex("6ae6dd")); t!(Header, "#8A083c" => Hex("8A083c")); @@ -469,11 +501,11 @@ mod tests { #[test] fn tokenize_math() { - t!(Header, "12e-3in" => Len(Length::inches(12e-3))); + t!(Header, "12e-3in" => Length(12e-3, In)); t!(Header, "-1" => Min, Int(1)); t!(Header, "--1" => Min, Min, Int(1)); t!(Header, "- 1" => Min, S(0), Int(1)); - t!(Header, "6.1cm + 4pt,a=1*2" => Len(Length::cm(6.1)), S(0), Plus, S(0), Len(Length::pt(4.0)), + t!(Header, "6.1cm + 4pt,a=1*2" => Length(6.1, Cm), S(0), Plus, S(0), Length(4.0, Pt), Comma, Id("a"), Equals, Int(1), Star, Int(2)); t!(Header, "(5 - 1) / 2.1" => LP, Int(5), S(0), Min, S(0), Int(1), RP, S(0), Slash, S(0), Float(2.1)); diff --git a/src/prelude.rs b/src/prelude.rs index 82da91f4f..16b598ceb 100644 --- a/src/prelude.rs +++ b/src/prelude.rs @@ -3,8 +3,9 @@ pub use crate::diag::{Feedback, Pass}; #[doc(no_inline)] pub use crate::eval::{Args, Dict, Eval, EvalContext, Value, ValueDict}; -pub use crate::layout::nodes::LayoutNode; -pub use crate::layout::primitive::*; +pub use crate::geom::*; +#[doc(no_inline)] +pub use crate::layout::LayoutNode; #[doc(no_inline)] pub use crate::syntax::{Span, Spanned, SynTree}; pub use crate::{error, warning}; diff --git a/src/shaping.rs b/src/shaping.rs index c620f8729..bc6fb6b74 100644 --- a/src/shaping.rs +++ b/src/shaping.rs @@ -10,8 +10,8 @@ use fontdock::{FaceId, FaceQuery, FallbackTree, FontVariant}; use ttf_parser::{Face, GlyphId}; use crate::font::FontLoader; -use crate::geom::{Point, Size}; -use crate::layout::{BoxLayout, Dir, LayoutElement}; +use crate::geom::{Dir, Length, Point, Size}; +use crate::layout::{BoxLayout, LayoutElement}; /// A shaped run of text. #[derive(Clone, PartialEq)] @@ -24,14 +24,14 @@ pub struct Shaped { pub glyphs: Vec, /// The horizontal offsets of the glyphs. This is indexed parallel to `glyphs`. /// Vertical offets are not yet supported. - pub offsets: Vec, + pub offsets: Vec, /// The font size. - pub size: f64, + pub size: Length, } impl Shaped { /// Create a new shape run with empty `text`, `glyphs` and `offsets`. - pub fn new(face: FaceId, size: f64) -> Self { + pub fn new(face: FaceId, size: Length) -> Self { Self { text: String::new(), face, @@ -63,15 +63,15 @@ impl Debug for Shaped { /// [`Shaped`]: struct.Shaped.html pub async fn shape( text: &str, - size: f64, + size: Length, dir: Dir, loader: &mut FontLoader, fallback: &FallbackTree, variant: FontVariant, ) -> BoxLayout { - let mut layout = BoxLayout::new(Size::new(0.0, size)); + let mut layout = BoxLayout::new(Size::new(Length::ZERO, size)); let mut shaped = Shaped::new(FaceId::MAX, size); - let mut offset = 0.0; + let mut offset = Length::ZERO; // Create an iterator with conditional direction. let mut forwards = text.chars(); @@ -93,11 +93,11 @@ pub async fn shape( // Flush the buffer if we change the font face. if shaped.face != id && !shaped.text.is_empty() { - let pos = Point::new(layout.size.width, 0.0); + let pos = Point::new(layout.size.width, Length::ZERO); layout.push(pos, LayoutElement::Text(shaped)); layout.size.width += offset; shaped = Shaped::new(FaceId::MAX, size); - offset = 0.0; + offset = Length::ZERO; } shaped.face = id; @@ -110,7 +110,7 @@ pub async fn shape( // Flush the last buffered parts of the word. if !shaped.text.is_empty() { - let pos = Point::new(layout.size.width, 0.0); + let pos = Point::new(layout.size.width, Length::ZERO); layout.push(pos, LayoutElement::Text(shaped)); layout.size.width += offset; } @@ -120,7 +120,7 @@ pub async fn shape( /// Looks up the glyph for `c` and returns its index alongside its width at the /// given `size`. -fn lookup_glyph(face: &Face, c: char, size: f64) -> Option<(GlyphId, f64)> { +fn lookup_glyph(face: &Face, c: char, size: Length) -> Option<(GlyphId, Length)> { let glyph = face.glyph_index(c)?; // Determine the width of the char. diff --git a/src/syntax/ast/lit.rs b/src/syntax/ast/lit.rs index 4370345aa..40b360da9 100644 --- a/src/syntax/ast/lit.rs +++ b/src/syntax/ast/lit.rs @@ -3,7 +3,7 @@ use super::*; use crate::color::RgbaColor; use crate::eval::DictKey; -use crate::length::Length; +use crate::geom::Unit; /// A literal. #[derive(Debug, Clone, PartialEq)] @@ -17,13 +17,13 @@ pub enum Lit { /// A floating-point literal: `1.2`, `10e-4`. Float(f64), /// A length literal: `12pt`, `3cm`. - Length(Length), + Length(f64, Unit), /// A percent literal: `50%`. /// - /// _Note_: `50%` is represented as `50.0` here, but as `0.5` in the + /// _Note_: `50%` is stored as `50.0` here, but as `0.5` in the /// corresponding [value]. /// - /// [value]: ../../eval/enum.Value.html#variant.Relative + /// [value]: ../../geom/struct.Relative.html Percent(f64), /// A color literal: `#ffccee`. Color(RgbaColor), diff --git a/src/syntax/token.rs b/src/syntax/token.rs index cb0526c14..5b055e39f 100644 --- a/src/syntax/token.rs +++ b/src/syntax/token.rs @@ -1,6 +1,6 @@ //! Token definition. -use crate::length::Length; +use crate::geom::Unit; /// A minimal semantic entity of source code. #[derive(Debug, Copy, Clone, PartialEq)] @@ -72,10 +72,10 @@ pub enum Token<'s> { /// A floating-point number: `1.2`, `10e-4`. Float(f64), /// A length: `12pt`, `3cm`. - Length(Length), + Length(f64, Unit), /// A percentage: `50%`. /// - /// _Note_: `50%` is represented as `50.0` here, as in the corresponding + /// _Note_: `50%` is stored as `50.0` here, as in the corresponding /// [literal]. /// /// [literal]: ../ast/enum.Lit.html#variant.Percent @@ -159,7 +159,7 @@ impl<'s> Token<'s> { Self::Bool(_) => "bool", Self::Int(_) => "integer", Self::Float(_) => "float", - Self::Length(_) => "length", + Self::Length(..) => "length", Self::Percent(_) => "percentage", Self::Hex(_) => "hex value", Self::Str { .. } => "string", diff --git a/tests/test_typeset.rs b/tests/test_typeset.rs index 52f7a1fa6..3789d9785 100644 --- a/tests/test_typeset.rs +++ b/tests/test_typeset.rs @@ -15,7 +15,7 @@ use typstc::diag::{Feedback, Pass}; use typstc::eval::State; use typstc::export::pdf; use typstc::font::{FontLoader, SharedFontLoader}; -use typstc::geom::{Point, Vec2}; +use typstc::geom::{Length, Point}; use typstc::layout::{BoxLayout, LayoutElement}; use typstc::parse::LineMap; use typstc::shaping::Shaped; @@ -138,43 +138,40 @@ impl TestFilter { } fn render(layouts: &[BoxLayout], loader: &FontLoader, scale: f64) -> DrawTarget { - let pad = scale * 10.0; + let pad = Length::pt(scale * 10.0); let width = 2.0 * pad + layouts .iter() .map(|l| scale * l.size.width) .max_by(|a, b| a.partial_cmp(&b).unwrap()) - .unwrap() - .round(); + .unwrap(); - let height = pad - + layouts - .iter() - .map(|l| scale * l.size.height + pad) - .sum::() - .round(); + let height = + pad + layouts.iter().map(|l| scale * l.size.height + pad).sum::(); - let mut surface = DrawTarget::new(width as i32, height as i32); + let int_width = width.to_pt().round() as i32; + let int_height = height.to_pt().round() as i32; + let mut surface = DrawTarget::new(int_width, int_height); surface.clear(BLACK); - let mut offset = Vec2::new(pad, pad); + let mut offset = Point::new(pad, pad); for layout in layouts { surface.fill_rect( - offset.x as f32, - offset.y as f32, - (scale * layout.size.width) as f32, - (scale * layout.size.height) as f32, + offset.x.to_pt() as f32, + offset.y.to_pt() as f32, + (scale * layout.size.width).to_pt() as f32, + (scale * layout.size.height).to_pt() as f32, &Source::Solid(WHITE), &Default::default(), ); - for (pos, element) in &layout.elements { + for &(pos, ref element) in &layout.elements { match element { LayoutElement::Text(shaped) => render_shaped( &mut surface, loader, shaped, - (scale * pos.to_vec2() + offset).to_point(), + scale * pos + offset, scale, ), } @@ -205,8 +202,8 @@ fn render_shaped( let x = pos.x + scale * offset; let y = pos.y + scale * shaped.size; - let t = Transform::create_scale(s as f32, -s as f32) - .post_translate(Vector::new(x as f32, y as f32)); + let t = Transform::create_scale(s.to_pt() as f32, -s.to_pt() as f32) + .post_translate(Vector::new(x.to_pt() as f32, y.to_pt() as f32)); surface.fill( &path.transform(&t),